1. RunLoop对应用程序性能的影响
- 卡顿
- 原理:RunLoop负责管理事件循环,当一个任务在RunLoop的某一次循环中执行时间过长,就会导致RunLoop无法及时处理其他事件,如触摸事件、定时器事件等,从而造成界面卡顿。例如,在主线程的RunLoop中进行大量的计算或者网络请求同步操作,这些操作会阻塞RunLoop的运行,使得UI更新等任务得不到及时处理。
- 示例场景:在
- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法中进行复杂数据的初始化计算,而没有将其放到子线程处理,就可能导致应用启动时卡顿。
- 内存占用
- 原理:RunLoop会维护一些数据结构,如
CFRunLoopTimer
(定时器)、CFRunLoopSource
(输入源)等。如果这些对象没有正确释放,就会导致内存泄漏。例如,一个添加到RunLoop中的定时器,在不需要使用时没有从RunLoop中移除,定时器对象及其相关引用就会一直存在于内存中,造成内存占用增加。
- 示例场景:在一个视图控制器中创建了一个定时器并添加到当前RunLoop,当视图控制器销毁时,没有停止并移除定时器,定时器会持续占用内存。
2. 优化思路
- 优化卡顿
- 将耗时任务放到子线程:对于一些计算密集型或者I/O操作等耗时任务,应该使用
NSThread
、GCD
(Grand Central Dispatch)或NSOperationQueue
将其放到子线程执行,避免阻塞主线程的RunLoop。例如,使用GCD的dispatch_async
函数:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时任务,如复杂计算或网络请求
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://example.com"]];
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程处理UI更新等任务
UIImage *image = [UIImage imageWithData:data];
self.imageView.image = image;
});
});
- **优化RunLoop的Mode**:根据不同场景使用合适的RunLoop Mode。例如,在滚动`UITableView`时,可以将一些非关键的任务(如图片加载)放到`UITrackingRunLoopMode`中执行,这样在滚动时,这些任务不会影响到主线程处理滚动事件的流畅性。
// 创建一个定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 将定时器添加到指定Mode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
- 优化内存占用
- 及时释放无用对象:对于添加到RunLoop中的对象,在不需要使用时要及时从RunLoop中移除并释放。例如,对于定时器:
// 停止定时器
[self.timer invalidate];
self.timer = nil;
- **避免循环引用**:在使用`CFRunLoopSource`等对象时,要注意避免循环引用,确保对象在不再需要时能够被正确释放。例如,在一个自定义的`CFRunLoopSource`的回调函数中,如果存在对持有该`CFRunLoopSource`对象的类的强引用,就可能导致循环引用,此时可以使用`__weak`修饰符来打破循环。
__weak typeof(self) weakSelf = self;
// 创建自定义的CFRunLoopSource
CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, NULL,
^(void *info) {
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
// 执行回调任务
}
},
NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);