iOS 性能优化
2017-03-15 本文已影响135人
iOS104
列举在项目开发中可能遇到的优化点
-
1、内存泄露检测
- 可能出现内存泄露的地方:循环引用,block强引用,NSTimer释放不当,NSNotification remove 监听,Core Foundation
-
静态检查 Product -> Analyze 使用静态检查可以检查出一些明显没有释放的内存,CF开头的
-
instruments 工具检测。
-
推荐一个比较好的工具MLeakFinder。比通过Instrument工具,不断尝试才得以定位好用的多,可以clone下源码看看基本原理。
-
基本原理:为基类 NSObject 添加一个方法 -willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。
- (BOOL)willDealloc {
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf assertNotDealloc];
});
return YES;
}
- (void)assertNotDealloc {
NSAssert(NO, @“”);
}
-
2、FPS性能监测
- 要保持流畅的UI交互,App 刷新率应该当努力保持在 60fps。监控实现原理比较简单,通过记录两次刷新时间间隔,就可以计算出当前的 FPS。利用CADisplayLink和NSRunLoop
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayAction)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)displayAction {
if (_lastTime == 0) {
_lastTime = _link.timestamp;
return;
}
_count++;
NSTimeInterval delta = _link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = _link.timestamp;
float fps = _count / delta;
_count = 0;
_fps = (int)round(fps);
}
- 3、NSDateFormatter
- 设置一个全局的NSDateFormatter单例对象,用串行队列确保线程安全
dispatch_queue_t formatterQueue = dispatch_queue_create("formatter queue", NULL);
NSDateFormatter *dateFormatter;
// ...
- (NSDate *)dateFromString:(NSString *)string
{
__block NSDate *date = nil;
dispatch_sync(formatterQueue, ^{
date = [dateFormatter dateFromString:string];
});
return date;
}
- 4、主线程卡顿监控
- 通过子线程监测主线程的 runLoop,判断两个状态区域之间的耗时是否达到一定阈值。
- NSRunLoop调用方法主要就是在kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting之间,还有kCFRunLoopAfterWaiting之后,也就是如果我们发现这两个时间内耗时太长,那么就可以判定出此时主线程卡顿.
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
MyClass *object = (__bridge MyClass*)info;
// 记录状态值
object->activity = activity;
// 发送信号
dispatch_semaphore_t semaphore = moniotr->semaphore;
dispatch_semaphore_signal(semaphore);
}
- (void)registerObserver
{
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 创建信号
semaphore = dispatch_semaphore_create(0);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES)
{
// 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
if (st != 0)
{
if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)
{
if (++timeoutCount < 5)
continue;
NSLog(@"好像有点儿卡哦");
}
}
timeoutCount = 0;
}
});
}
/*
第1个参数: 指定如何给observer分配存储空间
第2个参数: 需要监听的状态类型/ kCFRunLoopAllActivities监听所有状态
第3个参数: 是否每次都需要监听
第4个参数: 优先级
第5个参数: 监听到状态改变之后的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚从睡眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将退出");
break;
default:
break;
}
});
```
- 5、使用Autorelease Pool
- 在循环中,如果有很多autorelease对象,要放在循环内部。
- 6、UIImage缓存取舍
- imagedNamed 默认加载图片成功后会内存中缓存图片,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象.
- tableView中或者反复使用的图片可以使用imageNamed
- imageWithContentsOfFile 则仅只加载图片,不缓存。一次性大图片使用
- 7、加速启动时间
- 程序启动时,除了界面上的操作,其他尽可能放在工作线程做。一些不重要的任务可以延后执行,例如判断版本升级提醒
- 8、UIKit 优化
- 1、尽量使用不透明的View opaque为YES
- 2、ImageView 使用合适大小的图片资源
- 3、重用和延迟加载Views
- 4、尽可能在TableViewCell时避免设置圆角,如果必须大量设置圆角,UIImageView 的圆角通过直接截取图片实现。其它视图的圆角可以通过 Core Graphics 画出圆角矩形实现。少量的话使用cornerRadius
- 5、使用shadowPath来画阴影
- 6、减少subviews的数量
- 7、缓存行高
- 9、日志打点
- 项目中有些业务异常需要在一定的环境才能重新,最好就好做好日志打点,预留埋点,在出现问题的时候方便排除问题
- 出现问题的时候网速监控上报
- 10、合理的线程分配
- 开的线程越来越多,线程的开销逐渐明显。
- DB操作,日志操作,网络回调在各自固定的线程。不同业务,通过创建队列保持数据的一致性。
- 主线程尽量少处理非UI的操作,同时控制整个APP子线程数量在一个合理的范围内。
- 11、建立合适的DB索引
- 12、cache
- cache可能是所有性能优化中最常用的手段,但是cache建立的成本低,见效快,但是带来维护的成本却很高
- 并发访问 cache 时,数据一致性问题
- cache 线程安全问题
- 13、基本类的抽取
- 基本类抽取可以提高开发效率
- 工具类,比如View,Label,Button的快捷创建
- 14、Unit Tests 和 UI Testing 的使用