iOS面试知识点

1、ios内存管理机制

iOS内存管理机制的原理是引用计数,当这块内存被创建后,它的引用计数0->1,表示有一个对象或指针持有这块内存,拥有这块内存的所有权,如果这时候有另外一个对象或指针指向这块内存,那么为了表示这个后来的对象或指针对这块内存的所有权,引用计数1->2,之后若有一个对象或指针不再指向这块内存时,引用计数-1,表示这个对象或指针不再拥有这块内存的所有权,当一块内存的引用计数变为0,表示没有任何对象或指针持有这块内存,系统便会立刻释放掉这块内存。

alloc、new :类初始化方法,开辟新的内存空间,引用计数+1;
retain :实例方法,不会开辟新的内存空间,引用计数+1;
copy : 实例方法,把一个对象复制到新的内存空间,新的内存空间引用计数+1,旧的不会;其中分为浅拷贝和深拷贝,浅拷贝只是拷贝地址,不会开辟新的内存空间;深拷贝是拷贝内容,会开辟新的内存空间;
strong :强引用; 引用计数+1;
release :实例方法,释放对象;引用计数-1;
autorelease : 延迟释放;autoreleasepool自动释放池;当执行完之后引用计数-1;
还有是initWithFormat和stringWithFormat 字符串长度大于9时,引用计数+1;
assign : 弱引用 ;weak也是弱引用,两者区别:assign不但能作用于对象还能作用于基本数据类型,但是所指向的对象销毁时不会将当前指向对象的指针指向nil,有野指针的生成;weak只能作用于对象,不能作用于基本数据类型,所指向的对象销毁时会将当前指向对象的指针指向nil,防止野指针的生成。

2、NSThread、GCD、NSOperation多线程

2.1、NSThread

NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大;

[NSThread isMultiThreaded];//BOOL 是否开启了多线程    
[NSThread currentThread];//NSThread 获取当前线程    
[NSThread mainThread];//NSThread 获取主线程    
[NSThread sleepForTimeInterval:1];//线程睡眠1s
2.2、GCD

GCD基于C语言封装的,遵循FIFO,Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。它让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务,一个任务可以是一个函数(function)或者是一个block。

dispatch_sync与dispatch_async//同步和异步操作
dispatch_queue_t;//主要有串行和并发两种;
    其中:
    dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT)并发;
    dispatch_queue_create("serial_queue", DISPATCH_QUEUE_SERIAL)串行;

dispatch_once_t;//代码只会被执行一次,用于单例
dispatch_after;//延迟操作
dispatch_get_main_queue;//回到主线程操作
2.2、NSOperation

NSOperation基于GCD封装的,比GCD可控性更强;可以加入操作依赖(addDependency)、设置操作队列最大可并发执行的操作个数(setMaxConcurrentOperationCount)、取消操作(cancel)等,需要使用两个它的实体子类:NSBlockOperation和NSInvocationOperation,或者继承NSOperation自定义子类;NSBlockOperation和NSInvocationOperation用法的主要区别是:前者执行指定的方法,后者执行代码块,相对来说后者更加灵活易用。NSOperation操作配置完成后便可调用start函数在当前线程执行,如果要异步执行避免阻塞当前线程则可以加入NSOperationQueue中异步执行,优点:不需要关心线程管理,数据同步的事情。

3、简述KVC和KVO

KVC : 键值编码(Key-Value Coding),它是一种通过key值访问类属性的机制,而不是通过setter/getter方法访问。其中 KVC 原理:当调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key时,KVC底层的执行机制如下:
首先搜索对应属性的setter方法
如果没有找到属性的setter方法,则会检查+ (BOOL)accessInstanceVariablesDirectly方法是否返回了YES(该方法默认返回YES),如果返回了YES, 则KVC机制会搜索类中是否存在该属性的成员变量,也就是_属性名,存在则对该成员变量赋值。搜索成员变量名的顺序是 _key,_isKey,key,isKey。
另外我们也可以通过重写+ (BOOL)accessInstanceVariablesDirectly方法返回NO,这个时候KVC机制就会调用 - (void)setValue:(id)value forUndefinedKey:(NSString *)key。
如果没有找到成员变量,调用 - (void)setValue:(id)value forUndefinedKey:(NSString *)key。

KVO : 键值观察者 (Key-Value Observer): KVO 是观察者模式的一种实现,观察者A监听被观察者B的某个属性,当B的属性发生更改时,A就会收到通知,执行相应的方法。实现原理:基本的原理:当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

4、Block实现原理,堆上和栈上的数据如何同步

block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。结构体,在栈上的情况, Block中的指针只是指向栈上的__block变量, 而当Block/__block变量被copy到堆上以后, 堆上Block会持有堆上__block变量. 而堆上的Block再次被调用copy时, 只是Block的引用计数+1而已, 而__block变量如果被多个堆上Block持有也只涉及到引用记数的变化. 一旦Block/__block变量的引用计数为0, 就会自动从堆上释放内存.这里Block/__block变量在堆上的内存管理与Objective-C对象完全一致.

5、iOS设计模式

5.1、观察者模式

1.何为观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
2.如何使用观察者模式
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
3.观察者模式的优缺点
优点:观察者和被观察者是抽象耦合的。建立一套触发机制。缺点:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

5.2、代理模式

1.何为代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
2.如何使用代理模式
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
想在访问一个类时做一些控制。
3.代理模式的优缺点
优点:
职责清晰、高扩展性、智能化。
缺点:
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

5.3、单例模式

1.何为单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
2.如何使用单例模式
当您想控制实例数目,节省系统资源的时候。
3.单例模式的优缺点
优点:
在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
避免对资源的多重占用比如写文件操作。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

6、NSNotificationCenter通知中心的实现原理

NSNotificationCenter是类似一个广播中心站,使用defaultCenter来获取应用中的通知中心,它可以向应用任何地方发送和接收通知。在通知中心注册观察者,发送者使用通知中心广播时,以NSNotification的name和object来确定需要发送给哪个观察者。为保证观察者能接收到通知,所以应先向通知中心注册观察者,接着再发送通知这样才能在通知中心调度表中查找到相应观察者进行通知。

-(void)postNotification:(NSNotification *)notification;
-(void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
-(void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

发送通知通过name和object来确定来标识观察者,name和object两个参数的规则相同即当通知设置name为kChangeNotifition时,那么只会发送给符合name为kChangeNotifition的观察者,同理object指发送给某个特定对象通知,如果只设置了name,那么只有对应名称的通知会触发。如果同时设置name和object参数时就必须同时符合这两个条件的观察者才能接收到通知。

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id )addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);

第一种方式是比较常用的添加Oberver的方式,接到通知时执行aSelector。
第二种方式是基于Block来添加观察者,往通知中心的调度表中添加观察者,这个观察者包括一个queue和一个block,并且会返回这个观察者对象。当接到通知时执行block所在的线程为添加观察者时传入的queue参数,queue也可以为nil,那么block就在通知所在的线程同步执行。
这里需要注意的是如果使用第二种的方式创建观察者需要弱引用可能引起循环引用的对象,避免内存泄漏。

NSNotificatinonCenter实现原理:
NSNotificatinonCenter是使用观察者模式来实现的用于跨层传递消息,用来降低耦合度。
NSNotificatinonCenter用来管理通知,将观察者注册到NSNotificatinonCenter的通知调度表中,然后发送通知时利用标识符name和object识别出调度表中的观察者,然后调用相应的观察者的方法,即传递消息(在Objective-C中对象调用方法,就是传递消息,消息有name或者selector,可以接受参数,而且可能有返回值),如果是基于block创建的通知就调用NSNotification的block。

7、推送如何实现的

1.由App向iOS设备发送一个注册通知,用户需要同意系统发送推送。
2.iOS应用向APNS远程推送服务器发送App的Bundle Id和设备的UDID。
3.APNS根据设备的UDID和App的Bundle Id生成deviceToken再发回给App。
4.App再将deviceToken发送给远程推送服务器(自己的服务器), 由服务器保存在数据库中。
5.当自己的服务器想发送推送时, 在远程推送服务器中输入要发送的消息并选择发给哪些用户的deviceToken,由远程推送服务器发送给APNS。
6.APNS根据deviceToken发送给对应的用户。

8、SEL的使用和原理

SEL 类成员方法的指针
可以理解 @selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Object-C的类不能直接应用函数指针,这样只能做一个@selector语法来取.
它的结果是一个SEL类型。这个类型本质是类方法的编号(函数地址)

9、简述weak的实现原理

weak 关键字的作用弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为 nil;
weak是有Runtime维护的weak表;
3.weak释放为nil过程
weak被释放为nil,需要对对象整个释放过程了解,如下是对象释放的整体流程:
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating。

对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
其实Weak表是一个hash(哈希)表,然后里面的key是指向对象的地址,Value是Weak指针的地址的数组
总结
weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

10、列表页性能优化

10.1、如何检测

1)Instruments中:Core Animation;
2)FPS:CADisplayLink

10.2、优化方案

1、文本、布局计算,提前计算缓存;
2、对象创建;CALayer代替UIView;
3、使用CAShapeLayer和UIBezierPath设置圆角;
4、UIBezierPath和Core Graphics框架画出一个圆角;

11、HTTP post的body体使用form-urlencoded和multipart/form-data的区别

1)application/x-www-form-urlencoded:
窗体数据被编码为名称/值对,这是标准且默认的编码格式。当action为get时候,客户端把form数据转换成一个字串append到url后面,用?分割。当action为post时候,浏览器把form数据封装到http body中,然后发送到server。

2)multipart/form-data:
multipart表示的意思是单个消息头包含多个消息体的解决方案。multipart媒体类型对发送非文本的各媒体类型是有用的。一般多用于文件上传。multipart/form-data只是multipart的一种。目前常用的有以下这些类型(注:任何一种执行时无法识别的multipart子类型都被视为子类型"mixed")

12、单例的弊端

+ (instancetype)sharedInstance {
    static ZZScreenshotsMonitor *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

优点:
1:一个类只被实例化一次,提供了对唯一实例的受控访问。
2:节省系统资源
3:允许可变数目的实例。

缺点:
1:一个类只有一个对象,可能造成责任过重,在一定程度上违背了“单一职责原则”。
2:由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
3:滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

13、App启动过慢,你可能想到的因素有哪些

影响启动性能的因素
main()函数之前耗时的影响因素
动态库加载越多,启动越慢。
ObjC类越多,启动越慢
C的constructor函数越多,启动越慢
C++静态对象越多,启动越慢
ObjC的+load越多,启动越慢
main()函数之后耗时的影响因素
执行main()函数的耗时
执行applicationWillFinishLaunching的耗时
rootViewController及其childViewController的加载、view及其subviews的加载

14、TCP和UDP的区别于联系

  • TCP为传输控制层协议,为面向连接、可靠的、点到点的通信;
  • UDP为用户数据报协议,非连接的不可靠的点到多点的通信;
  • TCP侧重可靠传输,UDP侧重快速传输。

15、TCP连接的三次握手

  • 第一次握手:客户端发送 syn 包(syn=j)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
  • 第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开 TCP 连接的请求,断开过程需要经过“四次握手”(过程就不细写了,就是服务器和客户端交互,最终确定断开)

16、Scoket连接和HTTP连接的区别

  • HTTP协议是基于TCP连接的,是应用层协议,主要解决如何包装数据。Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
  • HTTP连接:短连接,客户端向服务器发送一次请求,服务器响应后连接断开,节省资源。服务器不能主动给客户端响应(除非采用HTTP长连接技术),iPhone主要使用类NSURLConnection。
  • Socket连接:长连接,客户端跟服务器端直接使用Socket进行连接,没有规定连接后断开,因此客户端和服务器段保持连接通道,双方可以主动发送数据,一般多用于游戏.Socket默认连接超时时间是30秒,默认大小是8K(理解为一个数据包大小)

17、HTTP协议的特点,关于HTTP请求GET和POST的区别

GET和POST的区别:
  • HTTP超文本传输协议,是短连接,是客户端主动发送请求,服务器做出响应,服务器响应之后,链接断开。HTTP是一个属于应用层面向对象的协议,HTTP有两类报文:请求报文和响应报文。
  • HTTP请求报文:一个HTTP请求报文由请求行、请求头部、空行和请求数据4部分组成。
  • HTTP响应报文:由三部分组成:状态行、消息报头、响应正文。
  • GET请求:参数在地址后拼接,没有请求数据,不安全(因为所有参数都拼接在地址后面),不适合传输大量数据(长度有限制,为1024个字节)。

GET提交、请求的数据会附在URL之后,即把数据放置在HTTP协议头中。
以分割URL和传输数据,多个参数用&连接。如果数据是英文字母或数字,原样发送,
如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密。

  • POST请求:参数在请求数据区放着,相对GET请求更安全,并且数据大小没有限制。把提交的数据放置在HTTP包的包体中.
  • GET提交的数据会在地址栏显示出来,而POST提交,地址栏不会改变。
传输数据的大小:
  • GET提交时,传输数据就会受到URL长度限制,POST由于不是通过URL传值,理论上书不受限
安全性:
  • POST的安全性要比GET的安全性高;
  • 通过GET提交数据,用户名和密码将明文出现在URL上,比如登陆界面有可能被浏览器缓存。
  • HTTPS:安全超文本传输协议(Secure Hypertext Transfer Protocol),它是一个安全通信通道,基于HTTP开发,用于客户计算机和服务器之间交换信息,使用安全套结字层(SSI)进行信息交换,即HTTP的安全版。

18、ASIHttpRequest、AFNetWorking之间的区别

  • ASIHttpRequest功能强大,主要是在MRC下实现的,是对系统CFNetwork API进行了封装,支持HTTP协议的CFHTTP,配置比较复杂,并且ASIHttpRequest框架默认不会帮你监听网络改变,如果需要让ASIHttpRequest帮你监听网络状态改变,并且手动开始这个功能。
  • AFNetWorking构建于NSURLConnection、NSOperation以及其他熟悉的Foundation技术之上。拥有良好的架构,丰富的API及模块构建方式,使用起来非常轻松。它基于NSOperation封装的,AFURLConnectionOperation子类。
  • ASIHttpRequest是直接操作对象ASIHttpRequest是一个实现了NSCoding协议的NSOperation子类;AFNetWorking直接操作对象的AFHttpClient,是一个实现NSCoding和NSCopying协议的NSObject子类。
  • 同步请求:ASIHttpRequest直接通过调用一个startSynchronous方法;AFNetWorking默认没有封装同步请求,如果开发者需要使用同步请求,则需要重写getPath:paraments:success:failures方法,对于AFHttpRequestOperation进行同步处理。
  • 性能对比:AFNetworking请求优于ASIHttpRequest;

19、XML数据解析方式各有什么不同,JSON解析有哪些框架

XML数据解析的两种解析方式:DOM解析和SAX解析
  • DOM解析必须完成DOM树的构造,在处理规模较大的XML文档时就很耗内存,占用资源较多,读入整个XML文档并构建一个驻留内存的树结构(节点树),通过遍历树结构可以检索任意XML节点,读取它的属性和值,通常情况下,可以借助XPath查询XML节点;
  • SAX与DOM不同,它是事件驱动模型,解析XML文档时每遇到一个开始或者结束标签、属性或者一条指令时,程序就产生一个事件进行相应的处理,一边读取XML文档一边处理,不必等整个文档加载完才采取措施,当在读取解析过程中遇到需要处理的对象,会发出通知进行处理。因此,SAX相对于DOM来说更适合操作大的XML文档。
  • JSON解析:性能比较好的主要是第三方的JSONKIT和iOS自带的JSON解析类,其中自带的JSON解析性能最高,只能用于iOS5之后。

20、网络七层协议

  • 应用层:
    1.用户接口、应用程序;
    2.Application典型设备:网关;
    3.典型协议、标准和应用:TELNET、FTP、HTTP
  • 表示层:
    1.数据表示、压缩和加密presentation
    2.典型设备:网关
    3.典型协议、标准和应用:ASCLL、PICT、TIFF、JPEG|MPEG
    4.表示层相当于一个东西的表示,表示的一些协议,比如图片、声音和视频MPEG。
  • 会话层:
    1.会话的建立和结束;
    2.典型设备:网关;
    3.典型协议、标准和应用:RPC、SQL、NFS、X WINDOWS、ASP
  • 传输层:
    1.主要功能:端到端控制Transport;
    2.典型设备:网关;
    3.典型协议、标准和应用:TCP、UDP、SPX
  • 网络层:
    1.主要功能:路由、寻址Network;
    2.典型设备:路由器;
    3.典型协议、标准和应用:IP、IPX、APPLETALK、ICMP;
  • 数据链路层:
    1.主要功能:保证无差错的疏忽链路的data link;
    2.典型设备:交换机、网桥、网卡;
    3.典型协议、标准和应用:802.2、802.3ATM、HDLC、FRAME RELAY;
  • 物理层:
    1.主要功能:传输比特流Physical;
    2.典型设备:集线器、中继器
    3.典型协议、标准和应用:V.35、EIA/TIA-232.

21、XIB与Storyboards的优缺点

优点:
  • XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直观一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类。
  • Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个storyboard可以有很多的界面,每个界面对应一个类文件,通过storybard,可以直观地看出整个App的结构。
缺点:
  • XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长。XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下显示不同内容时,使用XIB是比较困难的。当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突相对要困难很多。
  • Storyboard:需求变动时,需要修改storyboard上对应的界面的约束,与XIB一样可能要重新添加约束,或者添加约束会造成大量的冲突,尤其是多团队开发。对于复杂逻辑控制不同显示内容时,比较困难。当多人团队或者多团队开发时,大家会同时修改一个storyboard,导致大量冲突,解决起来相当困难。

22、HTTPS的加密原理

  • 服务器端用非对称加密(RSA)生成公钥和私钥
  • 然后把公钥发给客户端, 服务器则保存私钥
  • 客户端拿到公钥后, 会生成一个密钥, 这个密钥就是将来客户端和服务器用来通信的钥匙
  • 然后客户端用公钥对密钥进行加密, 再发给服务器
  • 服务器拿到客户端发来的加密后的密钥后, 再使用私钥解密密钥, 到此双方都获得通信的钥匙

23、ARC的工作原理

  • Automatic Reference Counting,自动引用计数,即ARC,ARC会自动帮你插入retain和release语句,ARC编译器有两部分,分别是前端编译器和优化器
  • 前端编译器:前端编译器会为“拥有的”每一个对象插入相应的release语句。如果对象的所有权修饰符是__strong,那么它就是被拥有的。如果在某个方法内创建了一个对象,前端编译器会在方法末尾自动插入release语句以销毁它。而类拥有的对象(实例变量/属性)会在dealloc方法内被释放。事实上,你并不需要写dealloc方法或调用父类的dealloc方法,ARC会自动帮你完成一切。此外,由编译器生成的代码甚至会比你自己写的release语句的性能还要好,因为编辑器可以作出一些假设。在ARC中,没有类可以覆盖release方法,也没有调用它的必要。ARC会通过直接使用objc_release来优化调用过程。而对于retain也是同样的方法。ARC会调用objc_retain来取代保留消息
  • ARC优化器: 虽然前端编译器听起来很厉害的样子,但代码中有时仍会出现几个对retain和release的重复调用。ARC优化器负责移除多余的retain和release语句,确保生成的代码运行速度高于手动引用计数的代码

24、描述一个ViewController的生命周期

  • 当我们调用UIViewControlller的view时,
  • 系统首先判断当前的 UIViewControlller是否存在* view,如果存在直接返回view,
  • 如果不存在的话,会调用loadview方法,
  • 然后判断loadview方法是否是自定义方法,
  • 如果是自定义方法,就执行自定义方法,
  • 如果不是自定义方法,判断当时视图控制器是否有* xib、stroyboard。
  • 如果有xib、stroyboard 就加载xib、stroyboard。
  • 如果没有创建一个空白的view。
  • 调用viewDidLoad方法。
  • 最后返回view

25、内存的使用和优化的注意事项

  • 重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用;
    通过正确的设置 reuseIdentifier 来重用 Cell。
    尽量减少不必要的透明 View。
    尽量避免渐变效果、图片拉伸和离屏渲染。
    当不同的行的高度不一样时,尽量缓存它们的高度值。
    如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
    使用 shadowPath 来设置阴影效果。
    尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
    尽量优化 - [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
    对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。
  • 尽量把views设置为不透明:当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能;
    不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能及内存就差了很多;
  • 选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。
    gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
  • 延迟加载:对于不应该使用的数据,使用延迟加载方式。对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
  • 数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。
  • 处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉
    重用大开销对象:一些objects的初始化很慢,比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。
  • 避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要;
  • 使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存;
  • 正确选择图片加载方式:UIImage加载方式
  • 避免臃肿的 XIB 文件
  • 不要阻塞主线程
  • 使用复用机制
  • View 的复用和懒加载机制
  • 复用高开销的对象
  • 选择合适的数据存储方式
    在 iOS 中可以用来进行数据持有化的方案包括:
    NSUserDefaults。只适合用来存小数据。
    XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
    使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
    使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
    使用 CoreData。也是数据库技术,跟 SQLite 的性能差异比较小。但是 CoreData 是一个对象图谱模型,显得更面向对象;SQLite 就是常规的 DBMS。
  • 减少应用启动时间
    快速启动应用对于用户来说可以留下很好的印象。尤其是第一次使用时。
    保证应用快速启动的指导原则:
    尽量将启动过程中的处理分拆成各个异步处理流,比如:网络请求、数据库访问、数据解析等等。
    避免臃肿的 XIB 文件,因为它们会在你的主线程中进行加载。重申:Storyboard 没这个问题,放心使用。
    注意:在测试程序启动性能的时候,最好用与 Xcode 断开连接的设备进行测试。因为 watchdog 在使用 Xcode 进行调试的时候是不会启动的。

26、假如Controller太臃肿,如何优化

1.将网络请求抽象到单独的类中
方便在基类中处理公共逻辑;
方便在基类中处理缓存逻辑,以及其它一些公共逻辑;
方便做对象的持久化。
2.将界面的封装抽象到专门的类中
构造专门的 UIView 的子类,来负责这些控件的拼装。这是最彻底和优雅的方式,不过稍微麻烦一些的是,你需要把这些控件的事件回调先接管,再都一一暴露回 Controller。
3.构造 ViewModel
借鉴MVVM。具体做法就是将 ViewController 给 View 传递数据这个过程,抽象成构造 ViewModel 的过程。
4.专门构造存储类
专门来处理本地数据的存取。
5.整合常量

27、介绍下App启动的完成过程

  1. App启动过程
    • 解析Info.plist
    ▪ 加载相关信息,例如如闪屏
    ▪ 沙箱建立、权限检查

• Mach-O加载
▪ 如果是胖二进制文件,寻找合适当前CPU类别的部分
▪ 加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
▪ 定位内部、外部指针引用,例如字符串、函数等
▪ 执行声明为attribute((constructor))的C函数
▪ 加载类扩展(Category)中的方法
▪ C++静态对象加载、调用ObjC的 +load 函数

• 程序执行
· 1.main函数
· 2.执行UIApplicationMain函数
·   1.创建UIApplication对象
·   2.创建UIApplicationDelegate对象并复制
·   3.读取配置文件info.plist,设置程序启动的一些属性,(关于info.plist的内容可网上搜索下)
·   4.创建应用程序的Main Runloop循环
· 3.UIApplicationDelegate对象开始处理监听到的事件
·   1.程序启动成功之后,首先调用application:didFinishLaunchingWithOptions:方法,
·   如果info.plist文件中配置了启动storyboard文件名,则加载storyboard文件。
·   如果没有配置,则根据代码来创建UIWindow--->UIWindow的rootViewController-->显示

28、MVC和MVVM,MVP

  • MVC(Model View Controller)就是模型(Model)- 视图(View)-控制器(Controller)的缩写,Model是用来处理数据,View是用来展示界面,Cotroller是用来调节他们两者之间的交互。
    这个是最常用的。但是View和Model之间的直接交互,就导致了View和Model之间的耦合性比较大。
  • MVP (Model View Presenter)是MVC模式的变种,使用Presenter代替了Controller,而且改变了数据流向
    View和Model之间不再直接进行交互,而是通过Presenter来进行的。总体来说Presenter同时持有View和Model。
    优点:整体框架分层清晰,降低了耦合度。

缺点:需要加入Presenter来作为协调Model和View的桥梁,同时也导致了Presenter的臃肿。在维护起来不方便。

  • MVVM(Model View View-Model ViewModel)其实是对MVP的一种改进,他将Presenter替换成ViewModel,
    并通过双向数据绑定来实现视图和数据的交互。
    优点:使其数据流向更加清晰(脑补一下就是云对雨,x对风,大陆对长空)。一一对应起来。

29、如何提升 tableview 的流畅度

本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
  • CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
  • GPU:纹理的渲染
卡顿优化在 CPU 层面
  • 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
  • 不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
  • 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
  • Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
  • 图片的 size 最好刚好跟 UIImageView 的 size 保持一致
  • 控制一下线程的最大并发数量
  • 尽量把耗时的操作放到子线程
    文本处理(尺寸计算、绘制)
    图片处理(解码、绘制)
卡顿优化在 GPU层面
  • 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
  • GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
  • 尽量减少视图数量和层次
  • 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
  • 尽量避免出现离屏渲染

30、NSOperation 与 GCD 的主要区别

    1. GCD 的核心是 C 语言写的系统服务,执行和操作简单高效,因此 NSOperation 底层也通过 GCD 实现,换个说法就是 NSOperation 是对 GCD 更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用 NSOperation;
    1. 依赖关系,NSOperation 可以设置两个 NSOperation 之间的依赖,第二个任务依赖于第一个任务完成执行,GCD 无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;
    1. KVO(键值对观察),NSOperation 和容易判断 Operation 当前的状态(是否执行,是否取消),对此 GCD 无法通过 KVO 进行判断;
    1. 优先级,NSOperation 可以设置自身的优先级,但是优先级高的不一定先执行,GCD 只能设置队列的优先级,无法在执行的 block 设置优先级;
    1. 继承,NSOperation 是一个抽象类,实际开发中常用的两个类是 NSInvocationOperation 和 NSBlockOperation ,同样我们可以自定义 NSOperation,GCD 执行任务可以自由组装,没有继承那么高的代码复用度;
    1. 效率,直接使用 GCD 效率确实会更高效,NSOperation 会多一点开销,但是通过 NSOperation 可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;

31、请说明并比较以下关键词:strong, weak, assign, copy

  • strong 表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为 0 则不会被销毁。当然强行将其设为 nil 可以销毁它。
  • weak 表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
  • assign 主要用于修饰基本数据类型,如 NSInteger 和 CGFloat,这些数值主要存在于栈上。
  • weak 一般用来修饰对象,assign 一般用来修饰基本数据类型。原因是assign 修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
  • copy 与 strong 类似。不同之处是 strong 的复制是多个指针指向同一个地址,而 copy 的复制每次会在内存中拷贝一份对象,指针指向不同地址。copy 一般用在修饰有可变对应类型的不可变对象上,如 NSString , NSArray , NSDictionary 。
  • Objective-C 中,基本数据类型的默认关键字是 atomic , readwrite , assign ;普通属性的默认关键字是 atomic , readwrite , strong 。

32、ARC的底层原理,怎么实现自动释放的,和MRC的区别是什么

  • ARC管理原则:只要一个对象没有被强指针修饰就会被销毁,默认局部变量对象都是强指针,存放到堆里面,只是局部变量的强指针会在代码块结束后释放,对应所指向的内存空间也会被销毁。
  • MRC没有strong,weak,局部变量对象就是相当于基本数据类型。MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值,因为使用下划线是直接赋值(如_name = name),而set方法会多做影响引用计数方面的事情,比如retain。

33、有了线程,你觉得为什么还要有runloop?,runloop和线程有什么关系

解析:关于为什么要,我觉得runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
关于这两者的更多关系:

  • runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
  • runloop在第一次获取时被创建,在线程结束时被销毁。
  • 对于主线程来说,runloop在程序一启动就默认创建好了。
  • 对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

34、通知,代理,KVO的区别,以及通知的多线程问题

  1. delegate
    当我们第一次编写ios应用时,我们注意到不断的在使用“delegate”,并且贯穿于整个SDK。delegation模式不是IOS特有的模式,而是依赖与你过去拥有的编程背景。针对它的优势以及为什么经常使用到,这种模式可能不是很明显的。
    delegation的基本特征是:一个controller定义了一个协议(即一系列的方法定义)。该协议描述了一个delegate对象为了能够响应一个controller的事件而必须做的事情。协议就是delegator说,“如果你想作为我的delegate,那么你就必须实现这些方法”。实现这些方法就是允许controller在它的delegate能够调用这些方法,而它的delegate知道什么时候调用哪种方法。delegate可以是任何一种对象类型,因此controller不会与某种对象进行耦合,但是当该对象尝试告诉委托事情时,该对象能确定delegate将响应。
    delegate的优势:
    1.非常严格的语法。所有将听到的事件必须是在delegate协议中有清晰的定义。
    2.如果delegate中的一个方法没有实现那么就会出现编译警告/错误
    3.协议必须在controller的作用域范围内定义
    4.在一个应用中的控制流程是可跟踪的并且是可识别的;
    5.在一个控制器中可以定义定义多个不同的协议,每个协议有不同的delegates
    6.没有第三方对象要求保持/监视通信过程。
    7.能够接收调用的协议方法的返回值。这意味着delegate能够提供反馈信息给controller
    缺点:
    1.需要定义很多代码:1.协议定义;2.controller的delegate属性;3.在delegate本身中实现delegate方法定义
    2.在释放代理对象时,需要小心的将delegate改为nil。一旦设定失败,那么调用释放对象的方法将会出现内存crash
    3.在一个controller中有多个delegate对象,并且delegate是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。
  1. notification
    在iOS应用开发中有一个”Notification Center“的概念。它是一个单例对象,允许当事件发生时通知一些对象。它允许我们在低程度耦合的情况下,满足控制器与一个任意的对象进行通信的目的。这种模式的基本特征是为了让其他的对象能够接收到在该controller中发生某种事件而产生的消息,controller用一个key(通知名称)。这样对于controller来说是匿名的,其他的使用同样的key来注册了该通知的对象(即观察者)能够对通知的事件作出反应。
    通知优势:
    1.不需要编写多少代码,实现比较简单;
    2.对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
    3.controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息
    缺点:
    1.在编译期不会检查通知是否能够被观察者正确的处理;
    2.在释放注册的对象时,需要在通知中心取消注册;
    3.在调试的时候应用的工作以及控制过程难跟踪;
    4.需要第三方对喜爱那个来管理controller与观察者对象之间的联系;
    5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
    6.通知发出后,controller不能从观察者获得任何的反馈信息
  1. KVO
    KVO是一个对象能够观察另外一个对象的属性的值,并且能够发现值的变化。前面两种模式更加适合一个controller与任何其他的对象进行通信,而KVO更加适合任何类型的对象侦听另外一个任意对象的改变(这里也可以是controller,但一般不是controller)。这是一个对象与另外一个对象保持同步的一种方法,即当另外一种对象的状态发生改变时,观察对象马上作出反应。它只能用来对属性作出反应,而不会用来对方法或者动作作出反应。
    优点:
    1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
    2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
    3.能够提供观察的属性的最新值以及先前值;
    4.用key paths来观察属性,因此也可以观察嵌套对象;
    5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
    缺点:
    1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
    2.对属性重构将导致我们的观察代码不再可用;
    3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
    4.当释放观察者时不需要移除观察者。

总结:

  • 从上面的分析中可以看出3中设计模式都有各自的优点和缺点。其实任何一种事物都是这样,问题是如何在正确的时间正确的环境下选择正确的事物。下面就讲讲如何发挥他们各自的优势,在哪种情况下使用哪种模式。注意使用任何一种模式都没有对和错,只有更适合或者不适合。每一种模式都给对象提供一种方法来通知一个事件给其他对象,而且前者不需要知道侦听者。在这三种模式中,我认为KVO有最清晰的使用案例,而且针对某个需求有清晰的实用性。而另外两种模式有比较相似的用处,并且经常用来给controller间进行通信。那么我们在什么情况使用其中之一呢?
  • 根据我开发iOS应用的经历,我发现有些过分的使用通知模式。我个人不是很喜欢使用通知中心。我发现用通知中心很难把握应用的执行流程。UserInfo dictionaries的keys到处传递导致失去了同步,而且在公共空间需要定义太多的常量。对于一个工作于现有的项目的开发者来说,如果过分的使用通知中心,那么很难理解应用的流程。
  • 我觉得使用命名规则好的协议和协议方法定义对于清晰的理解controllers间的通信是很容易的。努力的定义这些协议方法将增强代码的可读性,以及更好的跟踪你的app。代理协议发生改变以及实现都可通过编译器检查出来,如果没有将会在开发的过程中至少会出现crash,而不仅仅是让一些事情异常工作。甚至在同一事件通知多控制器的场景中,只要你的应用在controller层次有着良好的结构,消息将在该层次上传递。该层次能够向后传递直至让所有需要知道事件的controllers都知道。
  • 当然会有delegation模式不适合的例外情况出现,而且notification可能更加有效。例如:应用中所有的controller需要知道一个事件。然而这些类型的场景很少出现。另外一个例子是当你建立了一个架构而且需要通知该事件给正在运行中应用。
  • 根据经验,如果是属性层的时间,不管是在不需要编程的对象还是在紧紧绑定一个view对象的model对象,我只使用观察。对于其他的事件,我都会使用delegate模式。如果因为某种原因我不能使用delegate,首先我将估计我的app架构是否出现了严重的错误。如果没有错误,然后才使用notification。

35、SDWebImage原理

SDWebImage原理

36、剖析Block

Block

37、为什么刷新UI要在主线程操作

37、为什么说iOS是一门动态编程语言?

动态语言:(Dynamic programming Language -动态语言或动态编程语言),动态语言是指程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除等在结构上的变化。
Objective-c是c语言的一个子类,所以objective-c是一个静态语言,但objective-c的三大特性之一的多态性让其拥有了动态性。
objective-c的动态性,让程序在运行时判断其该有的行为,而不是像c等静态语言在编译构建时就确定下来。它的动态性主要体现在3个方面:
1.动态类型:如id类型。实际上静态类型因为其
固定性和可预知性而使用的特别广泛。静态类型是强类型,动态类型是弱类型,运行时决定接收者。
2.动态绑定:让代码在运行时判断需要
调用什么方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起,而是在消息发送时才进行连接。运行时决定调用哪个方法。
3.动态载入。让程序在运行时添加代码模块以及其他资源。用户可以根据需要执行一些可执行代码和资源,而不是在启动时就加载所有
组件。可执行代码中可以含有和程序运行时整合的新类。

38、为什么给nil发送消息不会崩溃

OC的函数调用都是通过objc_msgSend进行消息发送来实现的,相对于C和C++来说,对于空指针的操作会引起Crash的问题,而objc_msgSend会通过判断self来决定是否发送消息,如果self为nil,那么selector也会为空,直接返回,所以不会出现问题。视方法返回值,向nil发消息可能会返回nil(返回值为对象)、0(返回值为一些基础数据类型)或0X0(返回值为id)等。但是对[NSNull
null]对象发送消息时,是会crash的,因为这个NSNull类只有一个null方法。
当然,如果一个对象已经被释放了(引用计数为0了),那么这个时候再去调用方法肯定是会Crash的,因为这个时候这个对象就是一个野指针(指向僵尸对象(对象的引用计数为0,指针指向的内存已经不可用)的指针)了,安全的做法是释放后将对象重新置为nil,使它成为一个空指针,大家可以在关闭ARC后手动release对象验证一下。

39、事件响应流程(响应链)

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图(最合适来处理的控件),这个过程称之为hit-test view。

以下为代码篇

1、输入一个字符串,判断这个字符串是否是有效的IP地址

+ (BOOL)isValidIP:(NSString *)ipStr {
    if (nil == ipStr) {
        return NO;
    }
    
    NSArray *ipArray = [ipStr componentsSeparatedByString:@"."];
    if (ipArray.count == 4) {
        for (NSString *ipnumberStr in ipArray) {
             if ([self isPureInt:ipnumberStr]) {
                int ipnumber = [ipnumberStr intValue];
              if (!(ipnumber>=0 && ipnumber<=255)) {
                  return NO;
              }
            }
        }
        return YES;
    }
    return NO;
}
// 是否整形
- (BOOL)isPureInt:(NSString*)string {
  NSScanner* scan = [NSScanner scannerWithString:string];
  int val;
  return[scan scanInt:&val] && [scan isAtEnd];
}
// 是否只含有数字
- (BOOL)validateNumber:(NSString*)number {
  BOOL res = YES;
  NSCharacterSet* tmpSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
  int i = 0;
  while (i < number.length) {
      NSString * string = [number substringWithRange:NSMakeRange(i, 1)];
      NSRange range = [string rangeOfCharacterFromSet:tmpSet];
      if (range.length == 0) {
          res = NO;
          break;
      }
      i++;
  }
  return res;
}

2、大数加法的实现

//两个大数相加算法
-(NSString *)addTwoNumberWithOneNumStr:(NSString *)one anotherNumStr:(NSString *)another
{
    int i = 0;
    int j = 0;
    int maxLength = 0;
    int sum = 0;
    int overflow = 0;
    int carryBit = 0;
    NSString *temp1 = @"";
    NSString *temp2 = @"";
    NSString *sums = @"";
    NSString *tempSum = @"";
    int length1 = (int)one.length;
    int length2 = (int)another.length;
    //1.反转字符串
    for (i = length1 - 1; i >= 0 ; i--) {
        NSRange range = NSMakeRange(i, 1);
        temp1 = [temp1 stringByAppendingString:[one substringWithRange:range]];
        NSLog(@"%@",temp1);
    }
    for (j = length2 - 1; j >= 0; j--) {
        NSRange range = NSMakeRange(j, 1);
        temp2 = [temp2 stringByAppendingString:[another substringWithRange:range]];
        NSLog(@"%@",temp2);
    }
    
    //2.补全缺少位数为0
    maxLength = length1 > length2 ? length1 : length2;
    if (maxLength == length1) {
        for (i = length2; i < length1; i++) {
            temp2 = [temp2 stringByAppendingString:@"0"];
            NSLog(@"i = %d --%@",i,temp2);
        }
    }else{
        for (j = length1; j < length2; j++) {
            temp1 = [temp1 stringByAppendingString:@"0"];
            NSLog(@"j = %d --%@",j,temp1);
        }
    }
    //3.取数做加法
    for (i = 0; i < maxLength; i++) {
        NSRange range = NSMakeRange(i, 1);
        int a = [temp1 substringWithRange:range].intValue;
        int b = [temp2 substringWithRange:range].intValue;
        sum = a + b + carryBit;
        if (sum > 9) {
            if (i == maxLength -1) {
                overflow = 1;
            }
            carryBit = 1;
            sum -= 10;
        }else{
            carryBit = 0;
        }
        tempSum = [tempSum stringByAppendingString:[NSString stringWithFormat:@"%d",sum]];
    }
    if (overflow == 1) {
        tempSum = [tempSum stringByAppendingString:@"1"];
    }
    int sumlength = (int)tempSum.length;
    for (i = sumlength - 1; i >= 0 ; i--) {
        NSRange range = NSMakeRange(i, 1);
        sums = [sums stringByAppendingString:[tempSum substringWithRange:range]];
    }
    NSLog(@"sums = %@",sums);
    return sums;
}

3、实现多个网络请求ABC执行完再执行D

方案1:使用group和semaphore
方案2:group_enter和group_leave也可以实现
下面使用方案1实现例子

  dispatch_group_t group = dispatch_group_create();
  dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_group_async(group, queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //异步执行A
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_async(group, queue, ^{
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
             //异步执行B
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
             //异步执行C
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    dispatch_group_notify(group, queue, ^{
           //执行D
    });

你可能感兴趣的:(iOS面试知识点)