- 讲一讲UITableview的优化方法
- 有没有用过运行时,用它都能做什么?
- SDWebImage的缓存策略?
- AFN为什么添加一条常驻线程?
- KVO的使用?实现原理?
- KVC的使用?实现原理?
讲一讲UITableview的优化方法
-
缓存高度
提前计算好 cell 的高度和布局,做好缓存。可以尝试用key-value方式存储
// 关于UITableView有两个重要的方法 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
-
异步绘制
在Cell上添加系统控件的时候,实质上系统都需要调用底层的接口进行绘制,当我们大量添加控件时,对资源的开销也会很大,所以我们索性
直接绘制
,提高效率。dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ CGRect rect = CGRectMake(0, 0, 100, 100); UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0); CGContextRef context = UIGraphicsGetCurrentContext(); [[UIColor lightGrayColor] set]; CGContextFillRect(context, rect); //将绘制的内容以图片的形式返回,并调主线程显示 UIImage *temp = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程 }); });
-
减少层级
减少SubViews的数量, 在滑动的列表上,多层次的view会导致帧数的下降。 例如: 绘制cell 不建议使用UIView,建议使用
CALayer(Core Animation)
。 -
Hide(显示隐藏)
尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过
hide
来控制是否显示 -
避免离屏渲染
-
离屏渲染。
在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。俗称 GPU根据
画家算法
没办法通过一次
绘制成功的。以下功能会触发离屏渲染:
- 允许边界剪切 masksToBounds 、clipsToBounds = YES
- 允许不透明 layer.allowsGroupOpacity =YES
- 不透明度 layer.opacity < 1.0
- 组不透明度 group opacity
- 阴影 layer.shadow
- 遮罩 layer.mask
- 模糊效果 UIBlurEffect,同样无法通过一次遍历完成
- layer.edgeAntialiasingMask 边缘抗锯齿,layer.allowsEdgeAntialiasing的图层文本(任何种类,包括UILabel,CATextLayer,Core Text等)
- 使用CGContext在drawRect :方法中绘制大部分情况下会导致离屏渲染,甚至仅仅是一个空的实现。
监测工具:
Xcode->Open Develeper Tools->Instruments中的
Core Animation
优化:
- 使用
CAShapeLayer
,它属于CoreAnimation - 封装
贝塞尔曲线
去绘制 - 阴影shadow 用
shadowPath
- 对于view的圆形边框,如果没有backgroundColor,可以放心使用
cornerRadius
来做 - 对layer进行
异步渲染
(Facebook开源的异步绘制框架AsyncDisplayKit) - 设置layer的opaque不透明值=YES,减少复杂图层合成
- 尽量使用
不包含透明alpha通道
的图片资源 - 尽量设置layer的大小值为
整形值
- 对于模糊效果,不采用系统提供的UIVisualEffect,而是另外实现模糊效果
CIGaussianBlur
,并手动管理渲染结果
延伸:
-
CALayer 为离屏渲染 提供了对应的解法:
shouldRasterize
。一旦被设置为true,Render Server就会强制把layer的渲染结果(包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。
-
-
其他的优化方法:
- 正确地使用UITableViewCell,和collectionView的dequeueReusableCellWithReuseIdentifier重用机制
- 避免阻塞主线程,尽量开辟子线程操作耗时功能
- 懒加载
- 尽可能重用开销比较大的对象,比如复用缓存
- 尽量减少计算的复杂度,比如开辟@autorelease来计算区间算法做好回收
有没有用过运行时,用它都能做什么?
-
通过
addMethod
添加方法 -
通过
关联
给category 模拟添加属性 -
通过
changeMethod
交换方法 -
访问私有属性
-
改变isa指针等等等
场景:button防止暴力点击、数组防nil越界、view绑定方法等等等等
SDWebImage的缓存策略?
SDWebImage 的图片缓存采用的是
Memory
和Disk
双重缓存机制。
-
图片读取
调用 sd_setImageWithURL
-
查询缓存
SDImageCache 类的 queryDiskCacheForKey 方法内部先查询
Memory Cache(内存缓存)
,查不到再查询Disk Cache(内存缓存)
。当然硬盘缓存里查到了还是会存入内存缓存里。
-
请求网络
请求网络使用的是 imageDownloader属性,这个示例专门负责下载图片数据。
如果下载失败, 会把失败的图片地址写入
failedURLs 集合
:if ( error.code != NSURLErrorNotConnectedToInternet && error.code != NSURLErrorCancelled && error.code != NSURLErrorInternationalRoamingOff && error.code != NSURLErrorDataNotAllowed && error.code != NSURLErrorCannotFindHost && error.code != NSURLErrorCannotConnectToHost) { @synchronized (self.failedURLs) { [self.failedURLs addObject:url]; } // 为什么要有这个 failedURLs 呢 }// 因为 SDWebImage 默认会有一个对上次加载失败的图片拒绝再次加载的机制。一张图片在本次会话加载失败了,如果再次加载就会直接拒绝。 // 请求的时候设置 SDWebImageRetryFailed 标记可以解决这个加载失败导致不会再次加载的问题
-
写入缓存
使用 [self.imageCache storeImage] 方法将它写入缓存,并且调用
completedBlock
告诉前端显示图片。 -
缓存清理
在每次
APP结束
的时候执行清理任务。-
第一步 先清除掉
过期的
缓存文件。 如果清除掉过期的缓存之后,空间还不够。 -
第二步 继续按
文件时间
从早到晚排序,先清除最早的缓存文件,直到剩余空间达到要求。maxCacheAge
属性是文件缓存的时长。默认值 = 7天。maxCacheSize
控制 SDImageCache 所允许的最大缓存空间,单位字节
。 没有默认值。
-
AFN为什么添加一条常驻线程?
AFN3.x 已经解决这个问题。替代方案就是
NSURLSession
。
在2.x版本的AFN中,使用的是NSURLCollection进行封装。NSURLConnection的网络请求是异步
发起的,事件和结果的回调在原来线程的RunLoop中进行。
- 在请求完成后我们需要对数据进行一些序列化处理,或者错误处理。如果我们在主线中处理这些事情很明显是不合理的。不仅会导致UI的卡顿,甚至受到默认的RunLoopModel的影响,我们在滑动tableview的时候,会导致时间的处理停止。
- 这里时候我们就需要一个子线程来处理事件和网络请求的回调了。但是,子线程在处理完事件后就会
自动结束生命周期
,这个时候后面的一些网络请求得回调我们就无法接收了。所以我们就需要开启子线程的RunLoop来保存线程的常驻
。
KVO的使用?实现原理?
KVO(key-value-observing) 即
键值观察
,利用一个key来找到某个属性并监听其值的改变。
使用步骤:
- 添加观察者
- 在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:(通过查阅文档可以知道,绝大多数对象都有这个方法,因为这个方法属于NSObject)
- 移除观察者
原理:
-
创建派生类
当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
-
重写Setter方法
重写的 setter 方法,触发设置属性 会调用 setter 方法,获得 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
-
改变isa指针指向
重写setter方法,导致系统将这个对象的 isa 指针指向这个新的派生类,因此这个对象就成为该派生类的对象了。
从而激活了键值通知机制。
此外,派生类还重写了 dealloc 方法来释放资源。
KVC的使用?实现原理?
KVC(key-Value coding)即
键值编码
。指iOS开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。不需要调用明确的存取方法,这样就可以在运行时动态访问和修改对象的属性,而不是在编译时确定。
使用:
-
动态地取值和设值
-
用KVC来访问和修改私有变量
[obj setValue:@"xxx" forKeyPath:@"_height"];
-
Model和字典转换
NSDictionary *dic = @{@"name":@"book", @"num" : @"66", @"id":@"123"}; Model *model = [[Model alloc] init]; [model setValuesForKeysWithDictionary:dic]; // 字典转model NSDictionary *modelDic = [model dictionaryWithValuesForKeys:@[@"name",@"num",@"goodId"]];// model转字典
-
修改一些控件的内部属性
-
操作集合
原理:
当一个对象调用setValue方法时,方法内部会做以下操作:
- 检查是否存在相应的key的setter方法。
- 查找与key相同名称并且带下划线的成员变量。
- 查找相同名称的属性key。
- 最终还没找到,就调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
当一个对象调用getValue方法时,方法内部会做以下操作:
- 检查是否存在相应的key的getter方法。
- 查找
countOfKey
,objectInKeyAtIndex
或keyAtIndexes
格式的方法。返回一个可以响应NSArray
所有方法的代理集合。 - 查找
countOfKey
,enumeratorOfKey
,memberOfKey
格式的方法。返回一个可以响应NSSet
所有方法的代理集合。 - 最终还没找到,就调用valueForUndefinedKey:方法。