iOS面试合集+答案(二)
这个栏目将持续更新--请iOS的小伙伴关注!
(答案不唯一,仅供参考,文章最后有福利)
二十一:XIB与Storyboards的优缺点
优点:
- XIB:在编译前就提供了可视化界面,可以直接拖控件,也可以直接给控件添加约束,更直观一些,而且类文件中就少了创建控件的代码,确实简化不少,通常每个XIB对应一个类。
- Storyboard:在编译前提供了可视化界面,可拖控件,可加约束,在开发时比较直观,而且一个storyboard可以有很多的界面,每个界面对应一个类文件,通过storybard,可以直观地看出整个App的结构。
缺点:
- XIB:需求变动时,需要修改XIB很大,有时候甚至需要重新添加约束,导致开发周期变长。XIB载入相比纯代码自然要慢一些。对于比较复杂逻辑控制不同状态下显示不同内容时,使用XIB是比较困难的。当多人团队或者多团队开发时,如果XIB文件被发动,极易导致冲突,而且解决冲突相对要困难很多。
- Storyboard:需求变动时,需要修改storyboard上对应的界面的约束,与XIB一样可能要重新添加约束,或者添加约束会造成大量的冲突,尤其是多团队开发。对于复杂逻辑控制不同显示内容时,比较困难。当多人团队或者多团队开发时,大家会同时修改一个storyboard,导致大量冲突,解决起来相当困难。
二十二:内存的使用和优化的注意事项
-
重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用;
-
尽量把views设置为不透明:当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能;
-
不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能及内存就差了很多;
-
选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。 gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
-
延迟加载:对于不应该使用的数据,使用延迟加载方式。对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
-
数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。
-
处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉 重用大开销对象:一些objects的初始化很慢,比如
NSDateFormatter
和NSCalendar
,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。 -
避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要;
-
使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存;
-
正确选择图片加载方式:
UIImage
加载方式
二十三:基于CTMediator的组件化方案,有哪些核心组成?
假如主APP调用某业务A,那么需要以下组成部分:
- CTMediator类,该类提供了函数 - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget; 这个函数可以根据targetName生成对象,根据actionName构造selector,然后可以利用performSelector:withObject:方法,在目标上执行动作。
- 业务A的实现代码,另外要加一个专门的类,用于执行Target Action 类的名字的格式:Target_%@,这里就是Target_A。 这个类里面的方法,名字都以Action_开头,需要传参数时,都统一以NSDictionary*的形式传入。 CTMediator类会创建Target类的对象,并在对象上执行方法。
- 业务A的CTMediator扩展 扩展里声明了所有A业务的对外接口,参数明确,这样外部调用者可以很容易理解如何调用接口。 在扩展的实现里,对Target, Action需要通过硬编码进行指定。由于扩展的负责方和业务的负责方是相同的,所以这个不是问题。
二十四:为什么CTMediator方案优于基于Router的方案?
Router的缺点:
- 在组件化的实施过程中,注册URL并不是充分必要条件。组件是不需要向组件管理器注册URL的,注册了URL之后,会造成不必要的内存常驻。注册URL的目的其实是一个服务发现的过程,在iOS领域中,服务发现的方式是不需要通过主动注册的,使用runtime就可以了。另外,注册部分的代码的维护是一个相对麻烦的事情,每一次支持新调用时,都要去维护一次注册列表。如果有调用被弃用了,是经常会忘记删项目的。runtime由于不存在注册过程,那就也不会产生维护的操作,维护成本就降低了。 由于通过runtime做到了服务的自动发现,拓展调用接口的任务就仅在于各自的模块,任何一次新接口添加,新业务添加,都不必去主工程做操作,十分透明。
- 在iOS领域里,一定是组件化的中间件为openURL提供服务,而不是openURL方式为组件化提供服务。如果在给App实施组件化方案的过程中是基于openURL的方案的话,有一个致命缺陷:非常规对象(不能被字符串化到URL中的对象,例如UIImage)无法参与本地组件间调度。
- 在本地调用中使用URL的方式其实是不必要的,如果业务工程师在本地间调度时需要给出URL,那么就不可避免要提供params,在调用时要提供哪些params是业务工程师很容易懵逼的地方。
- 为了支持传递非常规参数,蘑菇街的方案采用了protocol,这个会侵入业务。由于业务中的某个对象需要被调用,因此必须要符合某个可被调用的protocol,然而这个protocol又不存在于当前业务领域,于是当前业务就不得不依赖public Protocol。这对于将来的业务迁移是有非常大的影响的。
CTMediator的优点:
- 调用时,区分了本地应用调用和远程应用调用。本地应用调用为远程应用调用提供服务。
- 组件仅通过Action暴露可调用接口,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。
- 方便传递各种类型的参数。
二十五:MVVM设计模式
组成部分如下图所示:
在这个设计模式里,核心是ViewModel,它是一种特殊类型的model,代表了应用中UI的状态。它包含如下内容:
- 每个UI控件的一些属性。例如,text field控件的当前文本,某个button是否是enable状态。
- 视图可以执行的动作,例如按钮点击或者是手势。
将ViewModel想象为视图的模型,会比较容易理解。
MVVM模式中,三个组件的关系比MVC模式的要简单,有下面的严格规则:
- 视图引用ViewModel,但反向不成立。
- ViewModel引用Model,但反向不成立。
如果违背了上面两条规则,那么就是错误的MVVM实施行为。
这种模式的好处:
- 轻量级的视图(控制器), 所有的UI逻辑都位于ViewModel中。
- 易测试性。可以在没有视图的情况下,运行整个应用。
二十六:weak修饰的释放则自动被置为nil的实现原理
-
Runtime维护着一个Weak表,用于存储指向某个对象的所有Weak指针
-
Weak表是Hash表,Key是所指对象的地址,Value是Weak指针地址的数组
-
在对象被回收的时候,经过层层调用,会最终触发下面的方法将所有Weak指针的值设为nil。* runtime源码,objc-weak.m 的 arr_clear_deallocating 函数
-
weak指针的使用涉及到Hash表的增删改查,有一定的性能开销.
二十七:HTTPS的加密原理
-
服务器端用非对称加密(RSA)生成公钥和私钥
-
然后把公钥发给客户端, 服务器则保存私钥
-
客户端拿到公钥后, 会生成一个密钥, 这个密钥就是将来客户端和服务器用来通信的钥匙
-
然后客户端用公钥对密钥进行加密, 再发给服务器
-
服务器拿到客户端发来的加密后的密钥后, 再使用私钥解密密钥, 到此双方都获得通信的钥匙
二十八:你认为开发中那些导致crash?
当iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上。crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈跟踪信息和内存映像,这样就能够通过解析这些信息进而定位crash发生时的代码逻辑,从而找到App闪退的原因。
通常来说,crash产生来源于两种问题:违反iOS系统规则导致的crash和App代码逻辑BUG导致的crash
1.应用逻辑的Bug
- SEGV:(Segmentation Violation,段违例),无效内存地址,比如空指针,未初始化指针,栈溢出等;
- SIGABRT:收到Abort信号,可能自身调用abort()或者收到外部发送过来的信号;
- SIGBUS:总线错误。与SIGSEGV不同的是,SIGSEGV访问的是无效地址(比如虚存映射不到物理内存),而SIGBUS访问的是有效地址,但总线访问异常(比如地址对齐问题);
- SIGILL:尝试执行非法的指令,可能不被识别或者没有权限;
- SIGFPE:Floating Point Error,数学计算相关问题(可能不限于浮点计算),比如除零操作;
- SIGPIPE:管道另一端没有进程接手数据; 常见的崩溃原因基本都是代码逻辑问题或资源问题,比如数组越界,访问野指针或者资源不存在,或资源大小写错误等
2.违反iOS系统规则产生crash的三种类型
-
内存报警闪退
- 当iOS检测到内存过低时,它的VM系统会发出低内存警告通知,尝试回收一些内存;如果情况没有得到足够的改善,iOS会终止后台应用以回收更多内存;最后,如果内存还是不足,那么正在运行的应用可能会被终止掉。在Debug模式下,可以主动将客户端执行的动作逻辑写入一个log文件中,这样程序童鞋可以将内存预警的逻辑写入该log文件,当发生如下截图中的内存报警时,就是提醒当前客户端性能内存吃紧,可以通过Instruments工具中的Allocations 和 Leaks模块库来发现内存分配问题和内存泄漏问题。
-
响应超时
- 当应用程序对一些特定的事件(比如启动、挂起、恢复、结束)响应不及时,苹果的Watchdog机制会把应用程序干掉,并生成一份相应的crash日志。
-
用户强制退出
- 一看到“用户强制退出”,首先可能想到的双击Home键,然后关闭应用程序。不过这种场景一般是不会产生crash日志的,因为双击Home键后,所有的应用程序都处于后台状态,而iOS随时都有可能关闭后台进程,当应用阻塞界面并停止响应时这种场景才会产生crash日志。这里指的“用户强制退出”场景,是稍微比较复杂点的操作:先按住电源键,直到出现“滑动关机”的界面时,再按住Home键,这时候当前应用程序会被终止掉,并且产生一份相应事件的crash日志。
二十九:分析下SDWebImage
1.SDWebImage 加载图片的流程
1.入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2.进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
3.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4.SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
5.如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
7.如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
8.如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
9.共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
10.图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
11.connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。
12.connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
14.在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
15.imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
16.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
19.SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
20.SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
SDWebImage原理图
SDWebImage原理图
2. SDImageCache是怎么做数据管理的?
SDImageCache分两个部分,一个是内存层面的,一个是硬盘层面的。内存层面的相当是个缓存器,以Key-Value的形式存储图片。当内存不够的时候会清除所有缓存图片。用搜索文件系统的方式做管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件。当SDWebImageManager向SDImageCache要资源时,先搜索内存层面的数据,如果有直接返回,没有的话去访问磁盘,将图片从磁盘读取出来,然后做Decoder,将图片对象放到内存层面做备份,再返回调用层。
3.内部做Decoder的原因 (典型的空间换时间)
由于UIImage的imageWithData函数是每次画图的时候才将Data解压成ARGB的图像,所以在每次画图的时候,会有一个解压操作,这样效率很低,但是只有瞬时的内存需求。为了提高效率通过SDWebImageDecoder将包装在Data下的资源解压,然后画在另外一张图片上,这样这张新图片就不再需要重复解压了
三十:SEL和Method和IMP分别说下再谈下对IMP的理解?
Method
先看下定义
1. runtime.h
2. /// An opaque type that represents a method in a class definition.代表类定义中一个方法的不透明类型
3. typedef struct objc_method *Method;
4.
5. struct objc_method {
6. SEL method_name OBJC2_UNAVAILABLE;
7. char *method_types OBJC2_UNAVAILABLE;
8. IMP method_imp OBJC2_UNAVAILABLE;
9. }
Method和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码,比如:
1. - (void)logName
2. {
3. NSLog(@"name");
4. }
这段代码,就是一个函数。
我们来看下objc_method
这个结构体的内容:
- SEL method_name 方法名
- char *method_types 方法类型
- IMP method_imp 方法实现
在这个结构体重,我们已经看到了SEL
和IMP
,说明SEL
和IMP
其实都是Method
的属性。
我们接着来看SEL
。
SEL
还是先看定义
1. Objc.h
2. /// An opaque type that represents a method selector.代表一个方法的不透明类型
3. typedef struct objc_selector *SEL;
这里要先说明下selector
和SEL
的关系,我在写本文的时候,其实搜索的是selector
,直到我看到了selector
的定义,才发现我理解一直不对。
1. @property SEL selector;
在文档中,selector
的定义都是这样声明,也就是说:selector
是SEL
的一个实例,只是在iOS中,selector
的使用是如此的频繁,我们才会把他当成一个概念。
selector
怎么理解呢?我们可以想想股票,比如市场上有如此多公司在纳斯达克上市,而且他们的名字又非常的长,或者有些公司的名称也是相似的,都是**有限公司。那当市场去指定一个股票的时候,效率会非常低,当你着急想买股票的时候,你会跟你的经纪人说:“hi,peter,给我买一百股Tuniu limited liability company的股票吗?”,也许等你说完,经纪人输入完,市场就变化了,所以纳斯达克通常用代码,比如“TOUR”.这里的selector
有类似的作用,就是让我们能够快速找到对应的函数。
文档中是这样讲的:
A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded. 在iOS中,
runtime
会在运行的时候,通过load
函数,将所有的method
hash然后map到set
中。这样在运行的时候,寻找selector
的速度就会非常快,不会因为runtime
特性牺牲太多的性能。
selector
既然是一个string,我觉得应该是类似className+method
的组合,命名规则有两条:
- 同一个类,selector不能重复
- 不同的类,selector可以重复
这也带来了一个弊端,我们在写C代码的时候,经常会用到函数重载,就是函数名相同,参数不同,但是这在Objective-C
中是行不通的,因为selector
只记了method
的name,没有参数,所以没法区分不同的method。
比如:
1. - (void)caculate(NSInteger)num;
2.
3. - (void)caculate(CGFloat)num;
是会报错的。
我们只能通过命名来区别:
1. - (void)caculateWithInt(NSInteger)num;
2.
3. - (void)caculateWithFloat(CGFloat)num;
IMP
看下IMP
的定义
1. /// A pointer to the function of a method implementation. 指向一个方法实现的指针
2. typedef id (*IMP)(id, SEL, ...);
3. #endif
这个就比较好理解了,就是指向最终实现程序的内存地址的指针。
综上,在iOS的runtime
中,Method
通过selector
和IMP
两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。
三十一:Autorelease的原理 ?
- ARC下面,我们使用@autoreleasepool{}来使用一个Autoreleasepool,实际上UIKit 通过RunLoopObserver 在RunLoop二次Sleep间Autoreleasepool进行Pop和Push,将这次Loop产生的autorelease对象释放 对编译器会编译大致如下:
1. void *DragonLiContext = objc_ AutoreleasepoolPush();
2. // {} 的 code
3. objc_ AutoreleasepoolPop(DragonLiContext);
- 释放时机: 当前RunLoop迭代结束时候释放.
三十二: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语句,确保生成的代码运行速度高于手动引用计数的代码
三十三:用户需要上传和下载一个重要的资料文件,应该如何判断用户本次是否上传成功和下载成功了?
- 用MD5验证文件的完整性!(仅仅通过代码来判断当前次的请求发送结束或者收到数据结束不可以的)
- 当客户端上传一个文件的时候,在请求body里面添加该文件的MD5值来告诉服务器,服务器接受文件完毕以后通过校验收到的文件的MD5值与请求body里面的MD5值来最终确定本次上传是否成功
- 当客户端下载一个文件的时候,在响应头里面收到了服务器附带的该文件的MD5值,文件下载结束以后,通过获取下载后文件的MD5值与本次请求服务器返回的响应头中的MD5值做一个比较,来最终判断本次下载是否成功
- MD5,是一个将任意长度的数据字符串转化成短的固定长度的值的单向操作。任意两个字符串不应有相同的散列值
- MD5校验可以应用在多个领域,比如说机密资料的检验,下载文件的检验,明文密码的加密等。MD5校验原理举例:如客户往我们数据中心同步一个文件,该文件使用MD5校验,那么客户在发送文件的同时会再发一个存有校验码的文件,我们拿到该文件后做MD5运算,得到的计算结果与客户发送的校验码相比较,如果一致则认为客户发送的文件没有出错,否则认为文件出错需要重新发送。
三十四:isa指针的作用
-
对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法
-
是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
三十五:与 NSURLConnection 相比,NSURLsession 改进哪些?
-
可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息
-
session task。它负责处理数据的加载以及文件和数据在客户端与服务端之间的上传和下载。NSURLSessionTask 与 NSURLConnection 最大的相似之处在于它也负责数据的加载,最大的不同之处在于所有的 task 共享其创造者 NSURLSession 这一公共委托者(common delegate)
三十六:使用drawRect有什么影响?
drawRect方法依赖Core Graphics框架来进行自定义的绘制
-
缺点:它处理touch事件时每次按钮被点击后,都会用setNeddsDisplay进行强制重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来说,对CPU和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的UIButton实例,那就会很糟糕了
-
这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会把当前图层标记为dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会将标记为 dirty 的图层重新建立Core Graphics上下文,然后将内存中的数据恢复出来, 再使用 CGContextRef 进行绘制
三十七:什么时候会报unrecognized selector的异常?如何避免?
-
当调用该对象上某个方法,而该对象上没有实现这个方法的时候, 可以通过“消息转发”进行解决,如果还是不行就会报unrecognized selector异常
-
objc是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
- objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类然后在该类中的方法列表以及其父类方法列表中寻找方法运行 如果,在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会
-
三次拯救程序崩溃的机会
- Method resolution:objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。 如果你添加了函数并返回 YES,那运行时系统就会重新启动一次消息发送的过程 如果 resolve 方法返回 NO ,运行时就会移到下一步,消息转发
- Fast forwarding:如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但Normal forwarding转发会创建一个NSInvocation对象,相对Normal forwarding转发更快点,所以这里叫Fast forwarding
- Normal forwarding 这一步是Runtime最后一次给你挽救的机会。 首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。 如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。 如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象
三十八:iOS中常用的数据存储方式有哪些?
-
综合
- 所有的本地持久化数据存储的本质都是写文件,而且只能存到沙盒中。
- 沙盒机制是苹果的一项安全机制,本质就是系统给每个应用分配了一个文件夹来存储数据,而且每个应用只能访问分配给自己的那个文件夹,其他应用的文件夹是不能访问的。
- 数据存储的核心都是写文件。主要有四种持久化方式:属性列表,对象序列化,SQLite 数据库, CoreData
- 属性列表:应用于少量数据存储,比如登陆的用户信息,应用程序配置信息等。只有NSString ,NSArray,NSDictory,NSData,可以WriteToFile;存储的依旧是plist文件,plist文件可以存储的7种数据类型:array,dictory,string,bool,data,date,number。
-
详细
- 对象序列化:最终也是存为属性列表文件,如果程序中,需要存储的时候,直接存储对象比较方便,例如有一个设置类,我们可以把设置类的对象直接存储,就没必要再把里面的每一个属性单独存到文件中。对象序列化是将一个实现了NSCoding协议的对象,通过序列化(NSKeydArchiver)的形式,将对象中的属性抽取出来,转化成二进制流,也就是NSData,NSData可以选择write to file 或者存储到NSUserdefault中。 必须实现的两个方法 encodeWithCoder,initWithCoder。对象序列化的本质就是 对象NSData。
- SQLite: 适合大量,重复,有规律的数据存储。而且频繁的读取,删除,过滤数据,这种适合使用数据库 (iOS 使用第三方FMDB)
- CoreData: Sqlite叫做关系型数据库,CoreData 是一中OR-Mapping的思想 ,O代表对象Object,R代表relationship,Mapping代表映射,直译过来就是对象关系映射,其实就是把对象的属性和表中的字段自动映射,简化程序员的负担,以面向对象的方式操作数据库。ORMapping是一种思想,CoreData实现了这种思想,在Java中,hibernate 也是对ORMapping的一种实现,只是利用java实现的。
- CoreData 本质还是数据库,只不过使用起来更加面向对象,不关注二维的表结构,而是只需要关注对象,纯面向对象的数据操作方式。我们直接使用数据库的时候,如果向数据库中插入数据,一般是把一个对象的属性和数据库中某个表的字段一一对应,然后把对象的属性存储到具体的表字段中.取一条数据的时候,把表中的一行数据取出,同样需要再封装到对象的属性中,这样的方式有点繁琐,不面向对象。CoreData解决的问题就是不需要这个中间的转换过程,看起来是直接把对象存储进去,并且取出来,不关心表的存在,实际内部帮你做好了映射关系。
三十九:描述一个ViewController的生命周期
- 当我们调用UIViewControlller的view时,
- 系统首先判断当前的 UIViewControlller是否存在* view,如果存在直接返回view,
- 如果不存在的话,会调用loadview方法,
- 然后判断loadview方法是否是自定义方法,
- 如果是自定义方法,就执行自定义方法,
- 如果不是自定义方法,判断当时视图控制器是否有* xib、stroyboard。
- 如果有xib、stroyboard 就加载xib、stroyboard。
- 如果没有创建一个空白的view。
- 调用viewDidLoad方法。
- 最后返回view
四十:Block中可以修改全局变量,全局静态变量,局部静态变量吗?
-
可以.深入研究Block捕获外部变量和__block实现原理
-
全局变量和静态全局变量的值改变,以及它们被Block捕获进去,因为是全局的,作用域很广
-
静态变量和自动变量,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量
-
自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。
-
Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量
-
Block就分为以下3种
-
_NSConcreteStackBlock:只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。 StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了,是不持有对象的
- _NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上
-
_NSConcreteMallocBlock:有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制,是持有对象的
-
_NSConcreteGlobalBlock:没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束,也不持有对象
-
-
-
ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上
-
ARC下,Block中引用id类型的数据有没有__block都一样都是retain,而对于基础变量而言,没有的话无法修改变量值,有的话就是修改其结构体令其内部的forwarding指针指向拷贝后的地址达到值的修改
传送门:
转载自:https://juejin.cn/post/6986599129099010078