iOS常见面试知识点汇总

368 阅读30分钟

iOS的沙盒机制/文件系统

沙盒的特点:

  1. 每个应用程序都有自己的存储空间。
  2. 每个应用程序都不可以翻过自己的围墙去访问别的存储空间的内容。(已经越狱的除外)
  3. 在访问别人沙盒内的数据时需要访问权限。

应用程序沙盒目录下有三个文件夹Documents、Library(下面有Caches和Preferences目录)、tmp。

Documents

保存应用运行时生成的需要持久化的数据iTunes会自动备份该目录。苹果建议将在应用程序中浏览到的文件数据保存在该目录下。

Library/Caches

一般存储的是缓存文件,例如图片视频等,此目录下的文件不会再应用程序退出时删除,在手机备份的时候,iTunes不会备份该目录。

Library/Preferences

保存应用程序的所有偏好设置iOS的Settings(设置),我们不应该直接在这里创建文件,而是需要通过NSUserDefault这个类来访问应用程序的偏好设置。iTunes会自动备份该文件目录下的内容。 tmp:临时文件目录,在程序重新运行的时候,和开机的时候,会清空tmp文件夹。

原因

出于安全考虑,iPhone对于安装在上面的应用程序有所限制,这个限制就是应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。总体来说沙盒就是一种独立、安全、封闭的空间。沙盒(sandbox)的核心内容是:sandbox对应用程序执行各种操作的权限限制。

后期iOS添加了一个共享文件夹App,可以让App访问一个公用空间

事件响应链

当用户触摸屏幕时,触碰屏幕产生事件UIEvent并存入UIApplication中的事件队列中, 并且在整个视图结构中自上而下的进行分发。

iOS事件传递链.png

官方文档讲的比较清晰, App使用响应者对象接收和处理事件。响应者对象是 UIResponder 类的任何实例,常见的子类包括 UIView、UIViewControllerUIApplication还有UIWindow

响应者接收原始事件数据,并且必须处理该事件或将其转发给另一个响应者对象。当您的应用程序接收到一个事件时,UIKit 会自动将该事件定向到最合适的响应者对象,就是我们平时所谓的第一响应者。

未处理的事件在活动响应者链中从响应者传递到响应者,这是您应用的响应者对象的动态配置。

下图显示了一个应用程序中的响应程序,其界面包含一个UILabel、UITextField、UIButton和两个背景UIView。

该图还显示了事件如何按照响应者链从一个响应者移动到下一个响应者。

image.png

如果UITextField不处理事件,则UIKit会将事件发送到UITextField的父视图,然后再接下来会发送到UIWindow的根视图。从根视图开始,响应程序链在将事件定向到UIWindow之前转移到当前持有的UIViewController。如果UIWindow无法处理事件,则UIKit会将事件传递给UIApplication对象,如果该对象是UIResponder的实例并且还不是响应者链的一部分,则可能传递给Appdelegate

确定事件的第一响应者:响应链

UIKit 根据事件的类型将对象指定为事件的第一响应者。事件类型包括:

事件类型响应者
触摸事件发生触摸的视图。
新闻活动具有焦点的对象。
摇动事件您(或 UIKit)指定的对象。
遥控事件您(或 UIKit)指定的对象。
编辑菜单消息您(或 UIKit)指定的对象。

判断第一响应者的过程如下

HitTest第一响应者.png

其中主要用到如下两个方法:

  1. 方法返回一个可以响应event触摸事件的UIView
//判断当前点击事件是否存在第一响应者(First Responder)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
  1. 判断当前点击是否在控件的Bounds之内,我们可以重写该方法来做一些骚操作。 比如:
  • 扩大一个按钮的点击范围
  • 在我们写的loading 蒙层做处理,缩小点击捕获面积,这样就能实现允许用户点击导航栏部分返回页面但是不能在当前loading页面编辑表单。
  • 之前还做过一个点击地图出现泡泡,再点击另一个省份出现另一个泡泡
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

细节: 发生触摸时,UIKit将创建一个UITouch对象并将其与视图关联。 随着触摸位置或其他参数的更改,UIKit会使用新信息更新那个UITouch对象。 唯一不变的属性是那个关联的视图。 (即使触摸位置移到原始视图之外,触摸的视图属性中的值也不会更改。)触摸结束时,UIKit会释放UITouch对象。

底层&异步&线程知识

RunLoop原理,RunLoop与线程的关系

  • RunLoop其实就是一个do-while循环,Runloop的存在保证了程序一直在前台运行。
  • RunLoop和线程是一一对应的关系,即开启一个线程,就会创建一个RunLoop对线程进行处理和管理,直到Runloop中没有需要处理的item,RunLoop才会进入休眠状态,如果休眠一段时间没有被唤醒的话,RunLoop将会被销毁掉。

RunLoop的执行逻辑见下图:

runloop.jpeg

官网文档点这里

下图帮助我们理解整个Runloop的过程 image.png

GCD与NSOperation两种管理多线程方式的异同点

  1. GCD是用C语言实现的,而NSOperation是用OC实现的。
  2. NSOperation可以设置最大线程并发数,可以设置线程依赖关系,可以设置线程的优先级。
  3. GCD方式管理多线程是一种对开发者非常友好的开发方式,开发者只需要关注同步异步,串行并发这些线程关系就可以轻松地进行线程管理了。另外,线程中执行的任务是通过Block的形式调用的,所以执行效率也是非常高的。

GCD的常用api

  1. dispatch_sync:同步线程函数
  2. dispatch_async:异步线程函数
  3. dispatch_group_async:群线程函数 notify配合使用
  4. dispatch_barrier_async:栅栏函数,阻塞所属的队列的线程
  5. dispatch_barrier_sync:栅栏函数,阻塞当前线程
  6. dispatch_apply:该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

GCD中的同步异步,串行并发的概念,GCD常见的线程死锁问题

  1. 同步:针对于线程而言的概念,阻塞当前线程,不执行结束,当前线程就不会继续往下执行。
  2. 异步:针对于线程而言的概念,不阻塞当前线程,当前线程会继续往下执行。
  3. 串行:针对于队列而言,串行队列遵守FIFO(first-in-first-out)原则,只能一个任务一个任务地顺序执行。
  4. 并发:针对于队列而言,并发队列可以在同一时间执行多个任务。 类似于

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"%@", @"123");
    });

这样的函数,就是典型的GCD死锁函数。 因为dispatch_sync阻塞的当前的线程,而当前线程是main_queue,也就是说是一个串行线程,当前线程只有先执行NSLog(@"%@", @"123")才能继续运行下去,但是当前线程又被阻塞掉了,无法向下继续执行,所以这就是一个死锁的GCD执行函数了。

iOS中常用的线程锁有哪些,分别具有哪些特点?

  1. @synchronized 关键字加锁 互斥锁,性能较差不推荐使用
  2. NSLock 互斥锁 不能多次调用 lock方法,会造成死锁
  3. NSRecursiveLock递归锁,NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
  4. NSConditionLock条件锁,顾名思义,这个锁对象有一个condition属性,只有condition的值相同,才能获取到该锁,并且执行解锁操作。
  5. POSIX互斥锁,POSIX是Unix/Linux平台上提供的一套条件互斥锁的API。用法和特点与NSLock类似。
  6. dispatch_semaphore信号量实现加锁,当信号量为0时,线程将会被卡死,通过信号量的增减来达到控制线程个数的目的。
  7. OSSpinLock自旋锁,用法类似于NSLock,可以自动检查线程锁是否已经打开,效率比较高,但是被证明不是线程安全的。
  8. GCD线程阻断dispatch_barrier_async/dispatch_barrier_sync,dispatch_barrier_async/dispatch_barrier_sync在一定的基础上也可以做线程同步,会在线程队列中打断其他线程执行当前任务。两个的区别是dispatch_barrier_async阻塞的是当前队列的线程,而dispatch_barrier_sync阻塞的是任务所插入队列的线程。

runtime

runtime的机制和应用

Objective-C是一门动态强类型语言,它会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。因此,我们还需要一个运行时系统(Runtime system)来处理编译后的代码。 例如OC中的类对象,都是在程序的运行期才知道这个对象是哪个类的对象,该类具有哪些特征,等等。

  • 我们执行的函数例如:[foo doSomeThing],其实是通过函数objc_msgSend(foo, doSomeThing)来调用的,编译器将会在foo类的方法树中进行查找。
  • 我们平时写的Category就会在运行期间给源Class添加方法
  • 这里顺便提一下,之前有看到过文章说Extension 就是匿名 Category,其实不然,Extension和有名字的Category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,Category在运行时生效。
  • 另外不经过特殊处理的情况下Category只能给源Class添加方法(类方法和实例方法)
  • 如果想要使用Category添加属性就要使用runtime提供的关联对象

关联对象

iOS动态关联属性(objc_setAssociatedObject,objc_getAssocicatedObject)的实现原理

  1. AssociationsManager 是顶级的对象,维护了一个从spinlock_t 锁到AssociationsHashMap哈希表的单例键值对映射;
  2. AssociationsHashMap是一个无序的哈希表,维护了从对象地址到 ObjectAssociationMap的映射;
  3. ObjectAssociationMap 是一个 C 中的 map ,维护了从 key 到 ObjcAssociation 的映射,即关联记录;
  4. ObjcAssociation是一个C的类,表示一个具体的关联结构,主要包括两个实例变量,_policy 表示关联策略,_value 表示关联对象。

简述OC中的消息转发机制

当objc_msgSend方法调用找不到响应的函数名称时就会进行消息转发,主要分为3步:

  • 动态方法解析 调用方法+(BOOL)resolveInstanceMethod:(SEL)sel(实例方法动态解析)和+ (BOOL)resolveClassMethod:(SEL)sel(类方法动态解析)。
  • 备援接收者 调用方法 - (id)forwardingTargetForSelector:(SEL)aSelector
  • 完全转发 调用方法- (void)forwardInvocation:(NSInvocation *)anInvocation和- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

KVO的原理

KVO也是基于runtime实现的

例如,A类的实例的name属性被B类的实例监听了。这时,OC的runtime机制生成了一个KVONotifying_A的类来替代原来的A类,重写了+ (Class)class方法,返回[A Class],从而把自己伪装成A类。重写了A类属性name的setter方法加入了NSObject的两个方法:willChangeValueForKey:(值改变之前)和didChangevlueForKey:(值改变之后)。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context:也会被调用。

网络传输

加密的种类,对称加密和非对称加密

对称加密算法:MD5,DES,AES。

对称加密是最快速、最简单的一种加密方式,加密与解密用的是同样的密钥,这种方法在密码学中叫做对称加密算法。

非对称加密算法:RSA等加密方式。

非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。

解释一下七层网络结构,三次握手协议和四次挥手协议

七层网络协议:

由低到高:物理层、数据链路层、网络层、传输层、表示层、会话层、应用层。

三次握手协议:

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

四次挥手协议:

第一次挥手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;

第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我也没有数据要发送了,可以进行关闭连接了;

第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入CLOSE_WAIT状态;

第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

http和https的区别

https协议需要到ca申请证书。http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。http的连接很简单,是无状态的。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议。

https双向验证原理

  • 1.浏览器将自己支持的一套加密规则发送给网站。

  • 2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。

  • 3.浏览器获得网站证书之后浏览器要做以下工作:

    a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。

    b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。

    c) 使用约定好的HASH算法计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。

  • 4.网站接收浏览器发来的数据之后要做以下的操作:

    a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。

    b) 使用密码加密一段握手消息,发送给浏览器。

  • 5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。

HTTP常用的头部字段,常见的返回状态码和意义

host头域

Host头域指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的位置。HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回。

Referer头域

Referer头域允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等。他也允许废除的或错误的连接由于维护的目的被追踪。如果请求的uri没有自己的uri地址,Referer不能被发送。如果指定的是部分uri地址,则此地址应该是一个相对地址。

User-Agent头域

User-Agent头域的内容包含发出请求的用户信息,User Agent也简称UA。用较为普通的一点来说,是一种向访问网站提供你所使用的浏览器类型、操作系统及版本、CPU类型、浏览器渲染引擎、浏览器语言、浏览器插件等信息的标识。

Cache-Control头域

Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。

Date头域

Date头域表示消息发送的时间,时间的描述格式由rfc822定义。例如,Date:Mon,31Dec200104:25:57GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。

状态码

200(成功) 服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
201(已创建) 请求成功且服务器已创建了新的资源。
202(已接受) 服务器已接受了请求,但尚未对其进行处理。
203(非授权信息) 服务器已成功处理了请求,但返回了可能来自另一来源的信息。
204(无内容) 服务器成功处理了请求,但未返回任何内容。
205(重置内容) 服务器成功处理了请求,但未返回任何内容。与 204 响应不同,此响应要求请求者重置文档视图(例如清除表单内容以输入新内容)。
206(部分内容) 服务器成功处理了部分 GET 请求。
300(多种选择) 服务器根据请求可执行多种操作。服务器可根据请求者 来选择一项操作,或提供操作列表供其选择。
301(永久移动) 请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。您应使用此代码通知搜索引擎蜘蛛网页或网站已被永久移动到新位置。
302(临时移动) 服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。会自动将请求者转到不同的位置。但由于搜索引擎会继续抓取原有位置并将其编入索引,因此您不应使用此代码来告诉搜索引擎页面或网站已被移动。
303(查看其他位置) 当请求者应对不同的位置进行单独的 GET 请求以检索响应时,服务器会返回此代码。对于除 HEAD ##### 304(未修改) 自从上次请求后,请求的网页未被修改过。服务器返回此响应时,不会返回网页内容。如果网页自请求者上次请求后再也没有更改过,您应当将服务器配置为返回此响应。由于服务器可以告诉 搜索引擎自从上次抓取后网页没有更改过,因此可节省带宽和开销。
305(使用代理) 请求者只能使用代理访问请求的网页。如果服务器返回此响应,那么,服务器还会指明请求者应当使用的代理。
307(临时重定向) 服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。会自动将请求者转到不同的位置。但由于搜索引擎会继续抓取原有位置并将其编入索引,因此您不应使用此代码来告诉搜索引擎某个页面或网站已被移动。
400(错误请求) 服务器不理解请求的语法。
401(身份验证错误) 此页要求授权。您可能不希望将此网页纳入索引。
403(禁止) 服务器拒绝请求。
404(未找到) 服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码。
405(方法禁用) 禁用请求中指定的方法。
406(不接受) 无法使用请求的内容特性响应请求的网页。
407(需要代理授权) 此状态码与 401 类似,但指定请求者必须授权使用代理。如果服务器返回此响应,还表示请求者应当使用代理。
408(请求超时) 服务器等候请求时发生超时。
409(冲突) 服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。服务器在响应与前一个请求相冲突的 PUT 请求时可能会返回此代码,以及两个请求的差异列表。
410(已删除) 请求的资源永久删除后,服务器返回此响应。该代码与 404(未找到)代码相似,但在资源以前存在而现在不存在的情况下,有时会用来替代 404 代码。如果资源已永久删除,您应当使用 301 指定资源的新位置。
411(需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。
412(未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。
413(请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
414(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
415(不支持的媒体类型) 请求的格式不受请求页面的支持。
416(请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态码。
417(未满足期望值) 服务器未满足"期望"请求标头字段的要求。
500(服务器内部错误) 服务器遇到错误,无法完成请求。
501(尚未实施) 服务器不具备完成请求的功能。例如,当服务器无法识别请求方法时,服务器可能会返回此代码。
502(错误网关) 服务器作为网关或代理,从上游服务器收到了无效的响应。
503(服务不可用) 目前无法使用服务器(由于超载或进行停机维护)。通常,这只是一种暂时的状态。
504(网关超时) 服务器作为网关或代理,未及时从上游服务器接收请求。
505(HTTP 版本不受支持) 服务器不支持请求中所使用的 HTTP 协议版本。

常见方法

+(void)load方法:

对于加入运行期系统的类及分类,必定会调用此方法,且仅调用一次。

iOS会在应用程序启动的时候调用load方法,在main函数之前调用 执行子类的load方法前,会先执行所有超类的load方法,顺序为父类->子类->分类 在load方法中使用其他类是不安全的,因为会调用其他类的load方法,而如果关系复杂的话,就无法判断出各个类的载入顺序,类只有初始化完成后,类实例才能进行正常使用 load 方法不遵从继承规则,如果类本身没有实现load方法,那么系统就不会调用,不管父类有没有实现(跟下文的initialize有明显区别) 尽可能的精简load方法,因为整个应用程序在执行load方法时会阻塞,即,程序会阻塞直到所有类的load方法执行完毕,才会继续load 方法中最常用的就是方法交换method swizzling。

+(void)initial方法:

在首次使用该类之前由运行期系统调用,且仅调用一次 惰性调用,只有当程序使用相关类时,才会调用 运行期系统会确保initialize方法是在线程安全的环境中执行,即,只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞,等待initialize执行完。如果类未实现initialize方法,而其超类实现了,那么会运行超类的实现代码,而且会运行两次。initialize方法是线程安全的,可以用来设置内部数据,比如,某个全局状态,如数组、字典等无法在编译期初始化,可以放在initialize里面。

-(BOOL)isKindOfClass和-(BOOL)isMemberOfClass的区别

-(BOOL) isKindOfClass: classObj 判断是否是这个类或者这个类的子类的实例

-(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例

常见生命周期函数

  1. UIViewController的生命周期方法调用顺序
  2. alloc   创建对象,分配空间。
  3. init   初始化对象,初始化数据。
  4. loadView   从nib载入视图,通常这一步不需要去干涉。除非你没有使用xib文件创建视图
  5. viewDidLoad   载入完成,可以进行自定义数据以及动态的创建其他空间。
  6. viewWillAppear  视图将出现在屏幕之前。
  7. viewDidAppear  视图在屏幕上渲染完成。
  8. viewWillDisappear  视图被移除之前。
  9. viewDidDisappear   视图被移除之后。
  10. dealloc   销毁视图。

继承关系

2467393-1b68cd5eb8574ad9.jpeg

数据持久化

数据持久化的几种方式和对应的应用场景

  1. plist文件(属性列表):即直接拖拽plist文件到程序目录当中。由NSBundle获取本地plist资源。存储一些本地的,且不会改变的数据到程序当中。
  2. preference(偏好设置):即NSUserDefaults,存储一些小型数据,设置参数,开关属性等等。
  3. NSKeyedArchiver(归档):存储一些不涉及增删改查的字典数组或者NSObject等,存储的对象一定要遵循NSCoder和NSDecoder协议。
  4. SQLite 3:存储一些涉及增删改查的字段数据。
  5. CoreData:效率比较高,存储一些涉及增删改查的且体积非常大的数据。

设计模式

通知,代理,block,KVO的使用场景分别是什么,有什么区别?

  1. 通知: 适用于毫无关联的页面之间或者系统消息的传递,属于一对多的信息传递关系。例如系统音量的改变,系统状态的改变,应用模式的设置和改变,都比较适合用通知去传递信息。
  2. 代理: 一对一的信息传递方式,适用于相互关联的页面之间的信息传递,例如push和present出来的页面和原页面之间的信息传递。
  3. block: 一对一的信息传递方式,效率会比代理要高(毕竟是直接取IMP指针的操作方式)。适用的场景和代理差不多,都是相互关联页面之间的页面传值。
  4. KVO: 属性监听,监听对象的某一属性值的变化状况,当需要监听对象属性改变的时候使用。例如在UIScrollView中,监听contentOffset,既可以用KVO,也可以用代理。但是其他一些情况,比如说UIWebView的加载进度,AVPlayer的播放进度,就只能用KVO来监听了,否则获取不到对应的属性值。

属性关键字解析

iOS中常见的属性和默认的对象属性

常见属性: atomic, nonatomic, assign, retain, strong, weak, copy, readonly, readwrite, unsafe_unretained, getter=, setter= 等。 默认属性: 继承于NSObject类的对象:(atomic, strong), 非继承于NSObject类的对象:(atomic, assign)

属性意义:

哪些属性需要声明成copy,为什么?

NSDictionary, NSArray, NSString, Block需要声明为copy属性修饰,block用copy修饰是因为block只有拷贝到堆上才能保证调用block的时候,block没有被系统提前释放掉。NSDictionary, NSArray, NSString用copy修饰的原因直接上代码(以NSString举例) 用了copy属性修饰之后,可以防止这些类型的对象被引用并且改变内容。

简述对OC中的isa指针的认识

	NSObject类引入了两个头文件:#include <objc/objc.h>、#include <objc/NSObjCRuntime.h>,第一个头文件引入的是objc结构体的构成方式即isa指针,第二个头文件引入的是Runtime消息查找机制。
	接下来又引入了三个类的声明:@class NSString, NSMethodSignature, NSInvocation;
	NSMethodSignatureNSInvacation和OC的方法转发机制有关,而NSString是与NSObject的+ (NSString *)description方法有关。

weak对象的管理方式

  1. 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  2. 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  3. 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。 weak是弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为nil。runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。

iOS的retain和release的操作是在编译期还是运行时进行的

retain和release是在编译期由编译器自动生成的代码,例如:

- (void) setUserName:(UITextField *)userName { 
    [_userName release]; 
    _userName = [userName retain]; 
}

内存相关

简述iOS中的内存管理方式

iOS的内存管理用的是引用计数的方法,分为MRC(手动引用计数)和ARC(自动引用计数)。

  • MRC:开发者手动地进行retain和release操作,对每个对象的retainCount进行+1,-1操作,当retainCount为0时,系统会自动释放对象内存。
  • ARC:开发者通过声明对象的属性为strong,weak,retain,assign来管理对象的引用计数,被strong和retain修饰的属性变量系统会自动对所修饰变量的引用计数进行自增自减操作,同样地,retainCount为0时,系统会释放对象内存。

block的分类__block的作用,block循环引用产生的原因及解决办法

blcok分为全局blcok,堆block,栈block。

  • 在 MRC下:只要没有访问外部变量,就是全局block。访问了外部变量,就是栈block。显示地调用[block copy]就是堆block。
  • 在 ARC下:只要没有访问外部变量,就是全局block。如果访问了外部变量,那么在访问外部变量之前存储在栈区,访问外部变量之后存储在堆区。

__block的作用:

  • 将外部变量的传递形式由值传递变为指针传递,从而可以获取并且修改外部变量的值。
  • 同样,外部变量的修改,也会影响block函数的输出。

block循环引用问题:

当一个类的对象持有block,block里面又引用了这个对象,那么就是一个循环引用的关系。可以用strong-weak-dance的方法解除循环引用。

深拷贝与浅拷贝

深拷贝就是开辟一块新的内存空间来存储原来内存空间的内容,对象指针指向新的内存空间。浅拷贝只是重新生成一个指针,指向的还是原来的内存空间。

  • copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。
  • mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。 注意:深拷贝和深复制不同,深拷贝的内存空间的元素还是指向原地址,但是深复制会开辟新的内存空间重新复制子元素。

如何化解NSTimer的循环引用关系

首先要理解NSTimer为什么会引起循环引用:NSTimer和使用Timer的ViewController相互持有。 解决办法有两个:

  1. 推荐使用NSTimer的带block而不是target的方法
  2. 引入第三方NSObject(如HWWeakTimer)管理和持有Timer,让Timer持有第三方的成员变量。这样就打破了互相引用的循环关系。

启动过程

APP启动主要流程: 点击icon -> 加载动态链接库等 -> 映像文件加载imageLoader -> runtime -> load -> main -> delegate