## iOS常用问题总结####
iOS基础知识回顾#####
1、为什么说Objective-C是一门动态的语言?静态、动态是相对的,这里动态语言指的是不需要在编译时确定所有的东西,在运行时还可以动态的添加变量、方法和类Objective-C 可以通过Runtime 这个运行时机制,在运行时动态的添加变量、方法、类等,所以说Objective-C 是一门动态的语言Objective-C 是C 的超集,在C 语言的基础上添加了面向对象特性,并且利用Runtime 这个运行时机制,为Objective-C 增添了动态的特性。Objective-C 使用的是 “消息结构” 并非 “函数调用”:使用消息结构的的语言,其运行时所应执行的代码由运行期决定;而使用函数调用的语言,则由编译器决定
> (1)动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言,其他的各种脚本语言如VBScript也多少属于动态类型语言
。> (2)静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确定类中是否有某个方法的实现。对于类无法处理一个消息就会触发消息转发机制消息转发分为两大阶段:1. “动态方法解析”:先征询接收者,所属的类,能否动态添加方法,来处理这个消息,若可以则结束,如不能则继续往下走2. “完整的消息转发机制”: - 请接收者看看有没其他对象能处理这条消息,若有,就把这个消息转发给那个对象然后结束 - 运行时系统会把与消息有关细节全部封装到NSInvocation 对象中,再给对象最后一次机会,令其设法解决当前还未处理的这条消息
#####2、讲一下MVC和MVVM,MVP?[MVC、MVVM、MVP](http://www.cocoachina.com/ios/20170313/18870.html)
#####3、为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别? 为了避免循环引用。weak指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制。strong该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用。DataSource是关于View的内容的东西包括属性,数据等等,而Delegate则是一些我们可以调用的方法,全是操作。block和代理都能解决对象间交互的问题,block更轻型,更简单,能够直接访问上下文,代码通常在同一个地方,这样读代码也连贯。缺点是容易引起循环引用。delegate更重一些,需要实现接口,它的方法分开来,很多时候需要存储一些临时数据,另外相关的代码需要分离到各处没有block好读,其优点就是它是用weak关键字修饰的,不会引起循环引用。weak指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制delegate偏重于与用户交互的回调,有那些方法可以供我使用,例如UITableviewDelegate;dataSource偏重于数据的回调,view里面有什么东西,属性都是什么,例如UITableviewDatasource;
[block和代理的区别](https://www.jianshu.com/p/6bba9b4a25d5)
**代理的好处:
**> delegate运行成本低。block成本很高的。
>> block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除;delegate只是保存了一个对象指针,直接回调,没有额外消耗。相对C的函数指针,只多做了一个查表动作**delegate:
**> 1,“一对一”,对同一个协议,一个对象只能设置一个代理delegate,单例对象就不能用代理这是不对的❎,任何人,任何对象,只要接受,只要允许,只要遵守了相关的协议,TA就可以使用代理(感谢http://www.jianshu.com/users/22fefaea871c同学指出错误);
>> 2,代理更注重过程信息的传输:比如发起一个网络请求,可能想要知道此时请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败
**block:**> 1:写法更简练,不需要写protocol、函数等等
>> 2,block注重结果的传输:比如对于一个事件,只想知道成功或者失败,并不需要知道进行了多少或者额外的一些信息
>> 3,block需要注意防止循环引用:**ARC下这样防止:
**> __weak typeof(self) weakSelf = self;>> [yourBlock:^(NSArray *repeatedArray, NSArray *incompleteArray) {>> [weakSelf doSomething];>> }];
**非ARC**> __block typeof(self) weakSelf = self;>> [yourBlock:^(NSArray *repeatedArray, NSArray *incompleteArray) {>> [weakSelf doSomething];>> }];
**什么时候用代理,什么时候用block
**> 公共接口,方法较多也选择用delegate进行解耦
>> iOS有很多例子比如最常用tableViewDelegate,textViewDelegate
**异步和简单的回调用block更好
**> iOS有很多例子比如常用的网络库AFNetwork,ASIHTTP库,UIAlertView类。
#####4、属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?A:属性的本质是@property = ivar+getter+setter,也就是说@property系统会自动生成getter和setter方法。
属性默认的关键字包括atomic,nonatomic,@synthesize,@dynamic,getter=getterName,setter=setterName,readwrite,readonly,assign,retain,copy。@dynamic:表示变量对应的属性访问器方法,是动态实现的,你需要在 NSObject 中继承而来的
+(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 动态实现的方法或者函数。@synthesize:如果没有实现setter和getter,编译器能够自动实现getter和setter方法。synthesize是实现一个setter的,synthesize是一个编译器指令, 它可以简化我们getter/setter方法的实现@dynamic 是相对于 @synthesize 的,它们用样用于修饰 @property,用于生成对应的的 getter 和 setter 方法。但是 @ dynamic 表示这个成员变量的 getter 和 setter 方法并不是直接由编译器生成,而是手工生成或者运行时生成。
1. 由于是手工创建 setter 和 getter,没有带下划线成员变量需要手工去创建.不像@synthesize发现没有会自动去创建
2. @dynamic告诉编译器我自己手工实现 setter 和 getter, 至于实不实现,就是自己的问题了
.#####5、属性的默认关键字是什么?
#####6、NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
A:针对于当把NSMutableString赋值给NSString的时候,才会有不同,用copy的话NSString的值不会发生变化,用strong则会发生变化,随着NSMutableString的值变化。如果是赋值是NSString对象,那么使用copy还是strong,结果都是一样的,因为NSString对象根本就不能改变自身的值,他是不可变的。
#####7、如何令自己所写的对象具有拷贝功能?若想让自己写的对象具有拷贝功能,则需要实现NSCopying协议。如果自定义的对象分为可变版本和非可变版本,那么就要同时实现NSCopying和NSMutableCopying协议,不过一般没什么必要,实现NSCopying协议就够了。#####8、可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?对于不可变对象,copy操作是浅复制,mutableCopy是深复制。对于不可变对象,mutableCopy不仅仅是深复制,返回的对象类型还是不可变对象类型相应的可变对象的类型。内容复制也就是深拷贝,集合的深复制有两个方法,可以用initWithArray:copyItems:将第二个参数设置为YES即可进行深复制,如:NSDictionary *shallowCopyDict = [NSDictionary alloc]initWithDictionary:someDictionary copyItems:YES];如果用这个方法深复制,集合里的每个元素都会收到copyWithZone:消息。如果集合里的对象遵循NSCopying协议,那么对象就会深复制到新的集合。如果对象没有遵循NSCopying协议,而尝试用这种方法进行深复制则会出错。copyWithZone:这种拷贝方式只能提供一层内存拷贝,而非真正的深拷贝。第二种方法是将集合进行归档解档,如:NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
#####9、为什么IBOutlet修饰的UIView也适用weak关键字?因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。
#####10、nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?nonatomic非原子属性,atomic原子属性,相对来说,原子属性比非原子属性较安全,但是在多个对象同时访问该属性的时候并不能保证其绝对安全。同时,原子属性更加损耗性能。所以在iOS编程中一般都使用非原子属性。使用线程锁来保证其线程安全。nonatomic和atomic的区别在于两者自动生成getter和setter的方法不一样,如果你自己写getter和setter方法,那么(getter,setter,retain,copy,assign)只起提示作用,写不写都一样。对于atomic的属性,系统生成的getter和setter会保证get,set的操作完整性,不受其他线程影响。比如线程A的getter方法运行到一半,线程B调用了setter,那么线程A的getter还是能得到一个完整的对象。而nonatomic就没有这个保证了,所以速度要比atomic快。不过atomic可不能保证线程安全,如果线程A调用了getter,与此同时线程B和线程C都调了setter,那最后线程Aget到的值,三种都有可能:可能是B,C set之前原始的值,也可能是B set的值,也可能是C set的值。同时这个最终的值,也可能是B set的值,也可能是C set的值。要保证安全,可以使用线程锁。
#####11、UICollectionView自定义layout如何实现?UICollectionViewLayoutAttributes,UICollectionViewFlowLayout。
#####12、用StoryBoard开发界面有什么弊端?如何避免?难以维护,如果需要改动全局的一个字体,如果是代码的话就很好办,pch或头文件中改动就好了。如果是storyboard中就需要一个一个改动很麻烦。如果storyboard中scene太多,打开storyboard会比较慢。错误定位比较困难,好多错误提示模棱两可。storyboard开发界面性能要求更高,因为编译器会将storyboard转化为机器可识别的代码。然后在编译运行。消耗性能。使用纯代码相对来说更好。#####13、进程和线程的区别?同步异步的区别?并行和并发的区别?进程是一个内存中运行的应用程序,比如在Windows系统中,一个运行的exe就是一个进程。线程是指进程中的一个执行流程。同步是顺序执行,执行完一个再执行下一个。需要等待,协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这些事件完成后再工作。并行和并发 是前者相当于三个人同时吃一个馒头,后者相当于一个人同时吃三个馒头。并发性(Concurrence):指两个或两个以上的事件或活动在同一时间间隔内发生。并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。并行性(parallelism)指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。区别:(并发)一个处理器同时处理多个任务和(并行)多个处理器或者是多核的处理器同时处理多个不同的任务。作者:feedback1991链接:https://www.jianshu.com/p/5e5377b244e2來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
#####14、线程间通信?NSThread、GCD、NSOperation。
#####15、GCD的一些常用的函数?(group,barrier,信号量,线程同步)
1. 延迟执行任务函数:dispatch_after(.....)。
2. 一次性执行dispatch_once(...)。
3. 栅栏函数dispatch_barrier_async/dispatch_barrier_sync。
4. 队列组的使用dispatch_group_t。
5. GCD定时器。
#####16、如何使用队列来避免资源抢夺?dispatch_barrior_async 作用是在并行队列中,等待前面两个操作并行操作完成。
#####17、数据持久化的几个方案(fmdb用没用过)Coredata,realm,fmdb。
#####18、说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?
1.当程序第一次运行并且将要显示窗口的时候执行,在该方法中我们完成的操作```objective-c- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions`
``2.程序进入后台的时候首先执行程序将要取消活跃该方法
```objective-c- (void)applicationWillResignActive:(UIApplication *)application
```3.该方法当应用程序进入后台的时候调用```objective-c- (void)applicationDidEnterBackground:(UIApplication *)application```
4.当程序进入将要前台的时候调用
```objective-c- (void)applicationWillEnterForeground:(UIApplication *)application```
5.应用程序已经变得活跃(应用程序的运行状态)
```objective-c - (void)applicationDidBecomeActive:(UIApplication *)application```
6.当程序将要退出的时候调用,如果应用程序支持后台运行,该方法被applicationDidEnterBackground:替换
```objective-c- (void)applicationWillTerminate:(UIApplication *)application```
#####19、NSCache优于NSDictionary的几点?NSCache 是一个容器类,类似于NSDIctionary,通过key-value 形式存储和查询值,用于临时存储对象。注意一点它和NSDictionary区别就是,NSCache 中的key不必实现copy,NSDictionary中的key必须实现copy。NSCache中存储的对象也不必实现NSCoding协议,因为毕竟是临时存储,类似于内存缓存,程序退出后就被释放了。
#####20、知不知道Designated Initializer?使用它的时候有什么需要注意的问题?#####21、实现description方法能取到什么效果?
1.NSLog(@"%@", objectA);这会自动调用objectA的description方法来输出ObjectA的描述信息.
2.description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)3.description方法是基类NSObject 所带的方法,因为其默认实现是返回类名和对象的内存地址, 这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法的默认实现。
#####22、objc使用什么机制管理对象内存?通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。
####**中级Block**#####
1、block的实质是什么?一共有几种block?都是什么情况下生成的?block对象就是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,__forwarding指向自己或堆上自己的地址,如果block对象截获变量,这些变量也会出现在block结构体中。最重要的block结构体有一个函数指针,指向block代码块。block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)block代码块在编译的时候会生成一个函数,函数第一个参数是前面说到的block对象结构体指针。执行block,相当于执行block里面__forwarding里面的函数指针。
#####2、为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?在block中访问的外部变量是复制过去的,写操作不对原变量生效。#####3、模拟一下循环引用的一个情况?block实现界面反向传值如何实现?两个.h文件互相import了对方造成循环引用。block先声明(在要传值的controller里声明typedef void(^MyBlock)(NSString *name);//block的重命名@property (nonatomic,copy) MyBlock block;//block的声明),在准备接收值的页面里实现block,secondVC.block = ^void(NSString *name){_label.text = name;};,谁要传值就在谁那里调用self.block(@"lalala");。
####**Runtime**#####
1、objc在向一个对象发送消息时,发生了什么?objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
#####2、什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
1. 对象未实现该方法。
2. 对象已经被释放。使用[id respondsToSelector:]进行判断。
#####3、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?- 不能向编译后得到的类中增加实例变量;- 能向运行时创建的类中添加实例变量;解释如下:因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
#####4、runtime如何实现weak变量的自动置nil?runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)
#####5、给类添加一个属性后,在类结构体里哪些元素会发生变化?
####**类结构**#####1、isa指针?(对象的isa,类对象的isa,元类的isa都要说)
#####2、类方法和实例方法有什么区别?
#####3、介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
#####4、运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
#####5、objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)那么,回到本题,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
####**高级**#####
1、UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染###1.tableview性能优化
####1.1cell重用 #####
1.1.1 数据源方法优化
```objective-c(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath```
在可见的页面会重复绘制页面,每次刷新显示都会去创建新的Cell,非常耗费性能。 解决方案:首先创建一个静态变量reuseID(代理方法返回Cell会调用很多次,防止重复创建,static保证只会被创建一次,提高性能),然后,从缓存池中取相应identifier的Cell并更新数据,如果没有,才开始alloc新的Cell,并用identifier标识Cell。每个Cell都会注册一个identifier(重用标识符)放入缓存池,当需要调用的时候就直接从缓存池里找对应的id,当不需要时就放入缓存池等待调用。(移出屏幕的Cell才会放入缓存池中,并不会被release)所以在数据源方法中做出如下优化:
```objective-c// 调用次数太多,static 保证只创建一次reuseID,提高性能static NSString *reuseID = “reuseCellID”;``````objective-c// 缓存池中取已经创建的cellUITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];
```#####1.1.2 缓存池的实现当Cell要alloc时,UITableView会在堆中开辟一段内存以供Cell缓存之用。Cell的重用通过identifier标识不同类型的Cell,由此可以推断出,缓存池外层可能是一个可变字典,通过key来取出内部的Cell,而缓存池为存储不同高度、不同类型(包含图片、Label等)的Cell,可以推断出缓存池的字典内部可能是一个可变数组,用来存放不同类型的Cell,缓存池中只会保存已经被移出屏幕的不同类型的Cell。
```objective-c-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; ```
这个方法会查询可重用Cell,如果注册了原型Cell,能够查询到,否则,返回nil;而且需要判断if(cell == nil),才会创建Cell,不推荐```objective-c-(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);``` 使用这个方法之前,必须通过xib(storyboard)或是Class(纯代码)注册可重用Cell,而且这个方法一定会返回一个Cell注册Cell```objective-c- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
```####1.2 定义一种(尽量少)类型的Cell及善用hidden隐藏(显示)subviews
#####1.2.1 定义一种类型的 cell分析Cell结构,尽可能的将 相同内容的抽取到一种样式Cell中,前面已经提到了Cell的重用机制,这样就能保证UITbaleView要显示多少内容,真正创建出的Cell可能只比屏幕显示的Cell多一点。虽然Cell的’体积’可能会大点,但是因为Cell的数量不会很多,完全可以接受的。好处:- 减少代码量,减少Nib文件的数量,统一一个Nib文件定义Cell,容易修改、维护- 基于Cell的重用,真正运行时铺满屏幕所需的Cell数量大致是固定的,设为N个。所以如果如果只有一种Cell,那就是只有N个Cell的实例;但是如果有M种Cell,那么运行时最多可能会是“M x N = MN”个Cell的实例,虽然可能并不会占用太多内存,但是能少点不是更好吗。
#####1.2.2 善用hidden隐藏(显示)subviews只定义一种Cell,那该如何显示不同类型的内容呢?答案就是,把所有不同类型的view都定义好,放在cell里面,通过hidden显示、隐藏,来显示不同类型的内容。毕竟,在用户快速滑动中,只是单纯的显示、隐藏subview比实时创建要快得多。
#### 1.3 提前计算并缓存Cell的高度 在[iOS](http://lib.csdn.net/base/1)中,不设UITableViewCell的预估行高的情况下,会优先调用”tableView:heightForRowAtIndexPath:”方法,获取每个Cell的即将显示的高度,从而确定UITableView的布局,实际就是要获取contentSize(UITableView继承自UIScrollView,只有获取滚动区域,才能实现滚动),然后才调用”tableView:cellForRowAtIndexPath”,获取每个Cell,进行赋值。如果项目中模块有10000个Cell需要显示,可想而知…
解决方案:我个人认为,可以创建一个frame模型,提前计算每个Cell的高度。参考其中一篇博客的时候,在解决这个问题的时候,可以将计算Cell的高度放入数据模型,但这与MVC设计模式可能稍微有点冲突,这个时候我就想到MVVM这种设计模式,这个时候才能稍微有点MVVM这种设计模式的优点(其实还是很不理解的),可以讲计算Cell高度放入ViewModel(视图模型)中,让Model(数据模型)只负责处理数据。 在上面的基础上,还可以继续进行优化,提前创建真正显示的、需要加工的数据并缓存。不过这方面优化我好像之前没有接触。大家可以去看看这篇博客,其实本篇性能优化也借鉴了好多这篇文章,这是土土大神的博客地址,大家可以进去看看,好多iOS开发的知识,当然也有我参照的这篇博客。[http://tutuge.me](http://tutuge.me/)
#### 1.4 异步绘制(自定义cell绘制) 遇到比较复杂的界面的时候,如复杂点的图文混排,上面的那种优化行高的方式可能就不能满足要求了,当然了,由于我的开发经验尚短,说实话,还没遇到要将自定义的Cell重新绘制。至于这方面,大家可以参考这篇博客,绝对是开发经验十足的大神,分享足够多的UITableView方面的性能优化,好多借鉴自这里,我都不好意思了。
####1.5.滑动时,按需加载 开发的过程中,自定义Cell的种类千奇百怪,但Cell本来就是用来显示数据的,不说100%带有图片,也差不多,这个时候就要考虑,下滑的过程中可能会有点卡顿,尤其网络不好的时候,异步加载图片是个程序员都会想到,但是如果给每个循环对象都加上异步加载,开启的线程太多,一样会卡顿,我记得好像线程条数一般3-5条,最多也就6条吧。这个时候利用UIScrollViewDelegate两个代理方法就能很好地解决这个问题。```objective-c- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView```思想就是识别UITableView禁止或者减速滑动结束的时候,进行异步加载图片,快滑动过程中,只加载目标范围内的Cell,这样按需加载,极大的提高流畅度。而SDWebImage可以实现异步加载,与这条性能配合就完美了,尤其是大量图片展示的时候。而且也不用担心图片缓存会造成内存警告的问题。```objective-c//获取可见部分的CellNSArray *visiblePaths = [self.tableView indexPathsForVisibleRows]; for (NSIndexPath *indexPath in visiblePaths) { //获取的dataSource里面的对象,并且判断加载完成的不需要再次异步加载}```记得在记得在“tableView:cellForRowAtIndexPath:”方法中加入判断:```objective-c// tableView 停止滑动的时候异步加载图片- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ if (self.tableView.dragging == NO && self.tableView.decelerating == NO) { //开始异步加载图片}}```
####1.6 缓存View 当Cell中的部分View是非常独立的,并且不便于重用的,而且“体积”非常小,在内存可控的前提下,我们完全可以将这些view缓存起来。当然也是缓存在模型中。
#### 1.7 避免大量的图片缩放、颜色渐变等,尽量显示“大小刚好合适的图片资源”
####1.8 避免同步的从网络、文件获取数据,Cell内实现的内容来自web,使用异步加载,缓存请求结果
####1.9 渲染 #####
1.9.1 减少subviews的个数和层级 子控件的层级越深,渲染到屏幕上所需要的计算量就越大;如多用drawRect绘制元素,替代用view显示
##### 1. 9.2 少用subviews的透明图层 对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容(尽量设置Cell的view为opaque,避免GPU对Cell下面的内容也进行绘制)
##### 1.9.3 避免CALayer特效(shadowPath) 给Cell中View加阴影会引起性能问题,如下面代码会导致滚动时有明显的卡顿:```objective-cview.layer.shadowColor = color.CGColor;view.layer.shadowOffset = offset;view.layer.shadowOpacity = 1;view.layer.shadowRadius = radius;```
#### 总结:UITableView的优化主要从三个方面入手:- 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;(这个是开发中肯定会要优化的,不可能一个app就几个Cell吧)- 滑动时按需加载,防止卡顿,这个我也认为是很有必要做的性能优化,配合SDWebImage- 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口(如题,遇到复杂的界面,可以从这入手)- 缓存一切可以缓存的,这个在开发的时候,往往是性能优化最多的方向[优化UITableViewCell高度计算的那些事](http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/)
##### 2、有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)
##### 3、看过哪些第三方框架的源码?都是如何实现的?(如果没有,问一下多图下载的设计)
##### 4、SDWebImage的缓存策略?**基本结构**闲言少叙,咱们这就开始。 首先咱们来看看 SDWebImage 的整体结构:[![img](http://swiftcafe.io/articleimg/sdimage/1.png)](http://swiftcafe.io/articleimg/sdimage/1.png)有一个专门的 Cache 分类用来处理图片的缓存。 这里面也有两个类 SDImageCache 和 SDImageCacheConfig。 大部分的缓存处理都在 SDImageCache 这个类中实现。其他几个文件夹咱们分别有个字的功能,因为咱们这次专门讨论缓存策略,所以其他内容暂时略过。**Memory 和 Disk 双缓存**首先,SDWebImage 的图片缓存采用的是 Memory 和 Disk 双重 Cache 机制, 听起来挺高大上吧。其实也不复杂。 我们先来看看 Memory Cache,贴一段 SDImageCache 的代码:```objective-c@interface SDImageCache ()#pragma mark - Properties@property (strong, nonatomic, nonnull) NSCache *memCache;...```这里我们发现, 有一个叫做 memCache 的属性,它是一个 NSCache 对象,用于实现我们对图片的 Memory Cache。 SDWebImage 还专门实现了一个叫做 AutoPurgeCache 的类, 相比于普通的 NSCache, 它提供了一个在内存紧张时候释放缓存的能力:```objective-c@interface AutoPurgeCache : NSCache@end@implementation AutoPurgeCache- (nonnull instancetype)init { self = [super init]; if (self) {#if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];#endif } return self;}```其实就是接受系统的内存警告通知,然后清除掉自身的图片缓存。 这里大家比较少见的一个类应该是 NSCache 了。 简单来说,它是一个类似于 NSDictionary 的集合类,用于在内存中存储我们要缓存的数据。详细信息大家可以参考官方文档:。说完 Memory Cache, 我们再来说说 Disk Cache,也就是文件缓存。 SDWebImage 会将图片存放到 NSCachesDirectory 目录中:```objective-c- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace { NSArray*paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); return [paths[0] stringByAppendingPathComponent:fullNamespace];}```然后为每一个缓存文件生成一个 md5 文件名, 存放到文件中。**整体机制**为了节约篇幅,提升大家的阅读体验,这里尽量少贴大段代码。 我们前面介绍了 SDWebImage 同时使用内存和硬盘两种缓存。 那么我们来看看当使用 SDWebImage 读取图片时候的完整流程。 我们一般会使用 SDWebImage 对 UIKit 的扩展,直接加载图片:```objective-c[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcode.jpg"]];```首先这个 Category 方法 sd_setImageWithURL 内部会调用 SDWebImageManager 的 downloadImageWithURL 方法来处理这个图片 URL:```objective-cidoperation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { ...}];```SDWebImageManager 内部的 downloadImageWithURL 方法会先使用我们前面提到的 SDImageCache 类的 queryDiskCacheForKey 方法,查询图片缓存:```objective-coperation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {...}];```再来看 queryDiskCacheForKey 方法内部, 先会查询 Memory Cache :```objective-cUIImage *image = [self imageFromMemoryCacheForKey:key];if (image) { doneBlock(image, SDImageCacheTypeMemory); return nil;}```如果 Memory Cache 查找不到, 就会查询 Disk Cache: ```objective-cdispatch_async(self.ioQueue, ^{ if (operation.isCancelled) { return; } @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; if (diskImage && self.shouldCacheImagesInMemory) { NSUInteger cost = SDCacheCostForImage(diskImage); [self.memCache setObject:diskImage forKey:key cost:cost]; } dispatch_async(dispatch_get_main_queue(), ^{ doneBlock(diskImage, SDImageCacheTypeDisk); }); }});```查询 Disk Cache 的时候有一个小插曲,就是如果 Disk Cache 查询成功,还会把得到的图片再次设置到 Memory Cache 中。 这样做可以最大化那些高频率展现图片的效率。如果缓存查询成功, 那么就会直接返回缓存数据。 如果不成功,接下来就开始请求网络:```objective-cid subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
}
```
请求网络使用的是 imageDownloader 属性,这个示例专门负责下载图片数据。 如果下载失败, 会把失败的图片地址写入 failedURLs 集合:
```objective-c
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
```
为什么要有这个 failedURLs 呢, 因为 SDWebImage 默认会有一个对上次加载失败的图片拒绝再次加载的机制。 也就是说,一张图片在本次会话加载失败了,如果再次加载就会直接拒绝。SDWebImage 这样做可能是为了提高性能吧。这个机制可能会容易被大家忽略,所以这里特意提一下,说不定哪天遇到一些奇怪问题时候,这个知识点会帮你快速定位问题~
如果下载图片成功了,接下来就会使用 [self.imageCache storeImage] 方法将它写入缓存,并且调用 completedBlock 告诉前端显示图片:
```objective-c
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
```
好了,到此为止 SDWebImage 的整体图片加载流程就都走完了。 由于要控制篇幅,我这里只挑了最重点的几个节点写出来,SDWebImage 的完整机制肯定会更全面,先帮大家疏通思路。
**是否要重试失败的 URL**
SDWebImage 的整体图片处理流程咱们体验了一遍。 那么有哪些重要的点对咱们使用它会有帮助呢? 我帮大家整理了一些。
你可以在加载图片的时候设置 SDWebImageRetryFailed 标记,这样 SDWebImage 就会加载之前失败过的图片了。 记得我们前面提到的 failedURLs 属性了吧,这个属性是在内存中存储的,如果图片加载失败, SDWebImage 会在本次 APP 会话中都不再重试这张图片了。当然这个加载失败是有条件的,如果是超时失败,不记在内。
总之,如果你更需要图片的可用性,而不是这一点点的性能优化,那么你就可以带上 SDWebImageRetryFailed 标记:
```objective-c
[_image sd_setImageWithURL:[NSURL URLWithString:@"http://swiftcafe.io/images/qrcodexx.jpg"] placeholder:]
```
**Disk 缓存清理策略**
SDWebImage 会在每次 APP 结束的时候执行清理任务。 清理缓存的规则分两步进行。 第一步先清除掉过期的缓存文件。 如果清除掉过期的缓存之后,空间还不够。 那么就继续按文件时间从早到晚排序,先清除最早的缓存文件,直到剩余空间达到要求。
具体点,SDWebImage 是怎么控制哪些缓存过期,以及剩余空间多少才够呢? 通过两个属性:
```objective-c
@interface SDImageCache : NSObject
@property (assign, nonatomic) NSInteger maxCacheAge;
@property (assign, nonatomic) NSUInteger maxCacheSize;
```
maxCacheAge 是文件缓存的时长, SDWebImage 会注册两个通知:
```objective-c
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
```
分别在应用进入后台和结束的时候,遍历所有的缓存文件,如果缓存文件超过 maxCacheAge 中指定的时长,就会被删除掉。
同样的, maxCacheSize 控制 SDImageCache 所允许的最大缓存空间。 如果清理完过期文件后缓存空间依然没达到 maxCacheSize 的要求, 那么就会继续清理旧文件,直到缓存空间达到要求为止。
了解了这个机制对我们有什么帮助呢? 我们来继续讲解,我们平时在使用 SDWebImage 的时候是没接触过它们的。那么以此推理,它们一定有默认值,也确实有:
```objective-c
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
```
上面是 maxCacheAge 的默认值,注释上写的很清楚,缓存一周。 再来看看 maxCacheSize。 翻了一遍 SDWebImage 的代码,并没有对 maxCacheSize 设置默认值。 这就意味着 SDWebImage 在默认情况下不会对缓存空间设限制。
这一点可以在 SDWebImage 清理缓存的代码中求证:
```objective-c
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
//清理缓存代码
}
```
说明一下, 上面代码中的 currentCacheSize 变量代表当前图片缓存占用的空间。 从这里可以看出, 只有在 maxCacheSize 大于 0 并且当前缓存空间大于 maxCacheSize 的时候才进行第二步的缓存清理。
这意味着什么呢? 其实就是 SDWebImage 在默认情况下是不对我们的缓存大小设限制的,理论上,APP 中的图片缓存可以占满整个设备。
SDWebImage 的这个特性还是比较容易被大家忽略的,如果你开发的类似信息流的 APP,应该会加载大量的图片,如果这时候按照默认机制,缓存尺寸是没有限制的,并且默认的缓存周期是一周。 就很容易造成应用存储空间占用偏大的问题。
那么可能有人会说了,现在 iPhone 的存储空间都很大,多缓存一点也不是问题吧? 但你是否知道 iOS 上还有一个用量查询的功能呢。在设置项中用户可以查看每个 APP 的空间使用情况, 如果你的 APP 占用空间比较大的话,就很容易成为用户的卸载目标,这应该是需要关注的一个细节。
另外,过多的占用缓存空间其实并不一定有用。大部分情况是一些图片被缓存下来后,很少再被重复展现。所以合理的规划缓存空间尺寸还是很有必要的。可以这样设置:
```objective-c
[SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50M
```
maxCacheSize 是以字节来表示的,我们上面的计算代表 50M 的最大缓存空间。 把这行代码写在你的 APP 启动的时候,这样 SDWebImage 在清理缓存的时候,就会清理多余的缓存文件了。
##### 5、AFN为什么添加一条常驻线程?
##### 6、KVO的使用?实现原理?(为什么要创建子类来实现)
##### 7、KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
### 2 写一个斐波那契函数
使用python实现:
```python
def fibs(n):
result = [0,1]
for i in range(n-2):
result.append(result[-2]+result[-1])
return result
def fibs1(n):
a,b = 0 ,1
while a < n:
print(a)
a ,b = b ,a+b
```
### 3. HTTP协议和HTTPS websocket
#### 3.1 HTTP协议简介
HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写,现在使用的协议版本是1.1(http0.9已经成为过去,http2.0尚在开发中)。
#### 3.2 在TCP/IP协议栈中的位置
HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示![4042E0FF-40CA-48EE-9632-C154A4CA8BA1](/Users/bene/Desktop/4042E0FF-40CA-48EE-9632-C154A4CA8BA1.png)默认HTTP的端口号为80,HTTPS的端口号为443。
#### 3.3 TCP/IP通信传输流
| | 客户端 | 服务端 |
| :---: | :--: | :--: |
| 应用层 | HTTP | HTTP |
| 传输层 | TCP | TCP |
| 网络层 | IP | IP |
| 数据链路层 | 网络 | 网络 |
在进行网络通信时,会通过分层顺序与对方进行通信,发送端从应用层往下走,接收端则从数据链路层往上走。发送端每经过一层传输数据时,就会被打上对应层所属的首部信息进行封装,接收端在层与层之间传输数据时,每经过一层就会把对应的首部去掉。
#####在传输数据时,http主要承载的协议是IP、TCP、DNS
IP层主要用于网络传输,确保数据能够正确传输给对方,在这里就会用到IP地址和Mac地址,IP和mac地址是相互绑定和映射的关系,传输过程中,使用ARP协议就可以通过IP地址反查出Mac地址。为了将数据准确可靠的传给对方,我们使用传输层的TCP协议(UDP协议不可靠),TCP协议通过三次握手策略进行传输确认,(发送端)SYN—(接收端)SYN/ACK—(发送端)ACK,代表握手成功。DNS服务和http协议一样位于应用层 ,提供域名到IP地址之间的解析服务。
#### 3.4 HTTP是不保存状态的协议
即无状态协议,对请求和响应之间的通信状态不尽兴保存。每当有新的请求发送,就会有新的响应产生。
####3.5 HTTP请求方式
| 请求方式 | 作用 |
| :----: | :----: |
| GET | 获取资源 |
| POST | 传输实体主题 |
| PUT | 传输文件 |
| DELETE | 删除文件 |
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上,比如登陆界面有可能被浏览器缓存。
####3.6 HTTP状态码
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。
HTTP状态码的英文为HTTP Status Code。
下面是常见的HTTP状态码:
- 200 - 请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
#####HTTP状态码分类
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
| 分类 | 分类描述 |
| ---- | ----------------------- |
| 1** | 信息,服务器收到请求,需要请求者继续执行操作 |
| 2** | 成功,操作被成功接收并处理 |
| 3** | 重定向,需要进一步的操作以完成请求 |
| 4** | 客户端错误,请求包含语法错误或无法完成请求 |
| 5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
#### 3.7 确保Web安全的HTTPS
##### 3.7.1 HTTP 的缺点
1. 通信使用明文(不加密),内容可能会被窃听
2. 不验证通信方的身份,因此有可能遭遇伪装
3. 无法证明报文的完整性,所以有可能已遭篡改
##### 3.7.2 HTTP+加密+认证+完整性保护=HTTPS
HTTPS并非是一种新的协议,只是HTTP通信接口部分用SSL和TLS协议代替而已。通常,HTTP直接和TCP通信。当使用SSL时,则演变成先和SSL通信,再由SSL和TCP通信,所谓HTTPS,实际上就是身披SSL协议的外壳的HTTP。
| 应用(HTTP) | 应用(HTTPS) |
| :------: | :-------: |
| | SSL |
| TCP | TCP |
| IP | IP |
| HTTP | HTTPS |
采用SSL后,HTTP就拥有了HTTPS的加密、证书和完整性保护这些功能。HTTPS采用共享密钥加密(对称密钥加密)和公开密钥加密(非对称加密)两者并用的混合加密机制。在交换密钥环节使用公开密钥加密方式,之后建立通信交换报文阶段则使用共享密钥加密方式。公开密钥加密处理起来比共享密钥方式更为复杂,因此若在通信时使用公开密钥加密方式,效率很低。
> 共享密钥加密方式加密和解密公用一个密钥的方式,公开密钥非对称,一把叫做私用密钥,另一把叫做公开密钥。发送密钥方使用公开密钥,接收方收到加密信息后使用私钥对数据进行解密。
##### 3.7.3 HTTPS的安全通信机制
HTTPS通过十次握手确立SSL连接成功,连接建立后,通信受到SSL的保护,从此开始进行应用层协议的通信,发送HTTP请求。
当然HTTPS也存在一些问题
当使用SSL时,它的处理速度会变慢。由于HTTPS还需要做服务器、客户端双方加密及解密处理,因此会消耗CPU和内存等硬件资源。
#### 3.8 使用浏览器进行全双工通信的WebSocket
WebSocket,即Web浏览器与Web服务器之间全双工通信标准。一旦Web服务器与客户端之间建立起WebSocket协议的通信连接,之后所有的通信都依靠这个专用协议进行。通信过程中可以互发XML,JSON,HTML,图片等任一格式的数据。任何一方都可以向对方发送报文。
##### 3.8.1 推送功能
支持服务器向客户端推送数据的推送功能,服务器可直接发送数据,不用等客户端的请求。
##### 3.8.2 减少通信量
只要建立起WebSocket连接,就会一直保持连接状态,和HTTP相比,不但每次连接的总开销少了,而且WebSocket的首部信息很小,通信量也相应减少了。
```http
GET /chat HTTP/1.1
Host:Server.example.com
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Key:Dhgersaferasftgsdsgswe==
Origin:http://example.com
Sec-WebSocket-Protocol:chat,superchat
Sec-WebSocket-Version:13
```
为了实现WebSocket通信,在HTTP连接建立之后,需要完成一次握手;要用到HTTP的Upgrade首部字段,告知服务器通信协议发生改变,以达到握手目的。成功握手确立WebSocket连接之后,通信不再使用HTTP的数据,而采用WebSocket独立的数据帧。