RunLoop是iOS开发中的核心机制,它使线程能够在没有工作时进入休眠状态,并在有新任务时被唤醒。本文将深入探讨RunLoop的工作原理及其在实际开发中的应用。
1. RunLoop基础结构
1.1 RunLoop对象模型
RunLoop是一个对象,用于处理来自不同源的输入事件,并调度这些事件到对应的处理程序。在iOS中,有两套API可以访问RunLoop:
- Foundation框架中的
NSRunLoop
类 - Core Foundation框架中的
CFRunLoopRef
类型
这两者是紧密关联的,NSRunLoop
实际上是对CFRunLoopRef
的封装,提供了面向对象的接口。
每个线程都有且只有一个RunLoop对象与之关联,主线程的RunLoop在应用启动时自动创建并运行,而子线程的RunLoop需要显式创建和运行。
// 获取当前线程的RunLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
// 获取主线程的RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
// Core Foundation中的等效调用
CFRunLoopRef currentCFRunLoop = CFRunLoopGetCurrent();
CFRunLoopRef mainCFRunLoop = CFRunLoopGetMain();
1.2 事件源分类与处理
RunLoop处理的事件源分为三种类型:
- Source0:非基于端口的输入源,需要手动触发。主要包括:
- 用户触摸事件
- 界面刷新事件
- 自定义事件
- Source1:基于端口的输入源,可以自动触发。包括:
- 系统事件
- 进程间通信
- Mach端口事件
- Timer:基于时间的触发源,包括:
- NSTimer
- CADisplayLink
- performSelector:withObject:afterDelay:
1.3 运行模式与切换机制
RunLoop可以在不同的模式下运行,每种模式定义了一组事件源和定时器的集合。RunLoop在某一时刻只能运行在一种模式下,如果想要切换模式,只能退出当前RunLoop,再以新模式重新启动。
常见的RunLoop模式包括:
- kCFRunLoopDefaultMode (NSDefaultRunLoopMode):默认模式,大多数情况下使用。
- UITrackingRunLoopMode:用于跟踪UI事件(如滚动)时的模式。
- kCFRunLoopCommonModes (NSRunLoopCommonModes):一个模式集合,包含Default和Tracking模式。
// 在默认模式下添加定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 在UI跟踪模式下添加
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 在Common模式下添加(同时在Default和Tracking模式下有效)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
当界面滚动时,RunLoop会从NSDefaultRunLoopMode切换到UITrackingRunLoopMode,如果Timer只添加到了NSDefaultRunLoopMode,则在滚动时Timer不会被触发。这就是为什么我们经常使用NSRunLoopCommonModes来确保Timer在滚动时也能正常工作。
1.4 底层数据结构剖析
RunLoop的核心是其内部数据结构的设计。以下是Core Foundation中的RunLoop相关数据结构的简化版本:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock;
__CFPort _wakeUpPort;
Boolean _stopped;
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFStringRef _name;
Boolean _stopped;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order;
CFMutableBagRef _runLoops;
union {
struct {
void (*perform)(void *info);
void *info;
} version0;
struct {
void *(*retain)(void *info);
void (*release)(void *info);
void (*perform)(void *info);
void *info;
} version1;
} _context;
};
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval;
CFTimeInterval _tolerance;
uint64_t _fireTSR;
CFIndex _order;
void (*_callout)(CFRunLoopTimerRef timer, void *info);
void *_context;
};
2. RunLoop与线程
2.1 主线程RunLoop特性
主线程的RunLoop具有一些特殊的特性:
- 自动创建和启动:主线程的RunLoop在应用启动时自动创建并启动,不需要手动干预。
- 处理UI事件:主线程RunLoop负责处理所有的UI事件,包括触摸、手势等。
- 与应用生命周期绑定:主线程RunLoop会一直运行,直到应用终止。
- 集成系统框架:主线程RunLoop集成了UIKit、CoreAnimation等框架的事件处理。
// 主线程RunLoop已自动启动,不需要手动启动
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
// 可以添加额外的输入源或定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(onTimer:) userInfo:nil repeats:YES];
[mainRunLoop addTimer:timer forMode:NSRunLoopCommonModes];
2.2 RunLoop线程管理机制
RunLoop与线程之间存在着密切的关系:
- 一对一关系:每个线程都有且只有一个RunLoop对象,且不能共享。
- 懒创建:除主线程外,其他线程的RunLoop需要显式获取才会被创建。
- 自动销毁:RunLoop会随着线程的结束而销毁。
- 线程保活:通过启动RunLoop可以保持线程不退出,实现线程常驻。
// 创建一个常驻线程
+ (void)createPermanentThread {
static NSThread *permanentThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
permanentThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEntryPoint) object:nil];
[permanentThread setName:@"PermanentThread"];
[permanentThread start];
});
}
// 线程入口函数
+ (void)threadEntryPoint {
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 添加一个Port源,保证RunLoop不会因为没有源而退出
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 开启RunLoop
[runLoop run];
}
}
// 在常驻线程上执行任务
+ (void)performTaskInPermanentThread:(void(^)(void))task {
[self performSelector:@selector(executeTask:)
onThread:permanentThread
withObject:task
waitUntilDone:NO];
}
+ (void)executeTask:(void(^)(void))task {
if (task) {
task();
}
}
2.3 休眠与唤醒实现原理
RunLoop最重要的特性之一是能够在没有任务时进入休眠状态,在有新任务时被唤醒,这一机制是如何实现的呢?
RunLoop执行序列:
─── 初始化流程 ───────────────────────────────
1. 观察者通知:Entry(进入)
2. DO-WHILE循环:
a. 观察者通知:BeforeTimers(处理定时器前)
b. 观察者通知:BeforeSources(处理源前)
c. 处理Blocks
d. 处理Source0
e. [如果处理了Source0] 处理Blocks
f. 观察者通知:BeforeWaiting(进入休眠前)
g. 休眠(mach_msg等待)
h. 唤醒事件处理:
- Timer事件
- Source1事件
- 自定义端口事件
i. 观察者通知:AfterWaiting(唤醒后)
j. 处理Blocks
k. 检查退出条件
3. 观察者通知:Exit(退出)
─────────────────────────────────────────────
PS: 复习下,JavaScript事件循环:
─── 调用栈处理 ─────────────────────────────
1. 执行同步代码
2. 循环:
a. 处理所有微任务直至清空
b. 渲染更新(如需要)
c. 执行最早的宏任务
d. 再次处理所有微任务
e. 渲染更新(如需要)
───────────────────────────────────────────
RunLoop的休眠与唤醒由底层的mach_msg()机制实现:
- 休眠:当没有事件需要处理时,RunLoop调用mach_msg()函数等待消息,线程进入休眠状态。
- 唤醒:当有新事件到来时(如用户触摸、NSTimer到时、网络数据到达等),系统会向RunLoop的端口发送消息,mach_msg()函数返回,RunLoop被唤醒。
- 退出:根据事件处理情况和超时设置,RunLoop可能会退出当前循环,并根据需要重新进入下一个循环。
RunLoop的源码中,核心的运行逻辑可以简化为以下伪代码:
int CFRunLoopRun(void) {
CFRunLoopRef rl = CFRunLoopGetCurrent();
CFRunLoopRunSpecific(rl, kCFRunLoopDefaultMode, 1.0e10, false);
return 0;
}
int CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
// 通知即将进入RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// RunLoop主循环
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, &shouldStop);
// 通知即将退出RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef mode, CFTimeInterval seconds, Boolean stopAfterHandle, Boolean *stopped) {
int32_t retVal = 0;
do {
// 1. 通知观察者:即将处理Timers
__CFRunLoopDoObservers(rl, mode, kCFRunLoopBeforeTimers);
// 2. 通知观察者:即将处理Sources
__CFRunLoopDoObservers(rl, mode, kCFRunLoopBeforeSources);
// 3. 处理Blocks
__CFRunLoopDoBlocks(rl, mode);
// 4. 处理Source0(非端口)
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, mode, stopAfterHandle);
// 5. 处理Source1(端口)
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, mode);
}
poll = __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, timeout);
// 10. 处理消息
if (livePort) {
// 处理Timer
if (livePort == timerPort) {
__CFRunLoopDoTimers(rl, mode, mach_absolute_time());
}
// 处理GCD
else if (livePort == dispatchPort) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(rl);
}
// 处理Source1
else {
CFRunLoopSourceRef source = __CFRunLoopModeFindSourceForMachPort(rl, mode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, mode, source, msg, msg_size, &reply) || sourceHandledThisLoop;
}
}
// 11. 处理Blocks
__CFRunLoopDoBlocks(rl, mode);
// 判断是否需要退出
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, mode)) {
retVal = kCFRunLoopRunFinished;
}
} while (retVal == 0);
return retVal;
}
2.4 自定义RunLoop应用
RunLoop的灵活性使我们能够实现一些高级功能,以下是几个常见的自定义RunLoop应用:
(1) 创建常驻线程
@interface ThreadManager : NSObject
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign, getter=isThreadRunning) BOOL threadRunning;
@end
@implementation ThreadManager
- (instancetype)init {
self = [super init];
if (self) {
self.threadRunning = NO;
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
[self.thread setName:@"MyPermanentThread"];
[self.thread start];
}
return self;
}
- (void)threadMain {
@autoreleasepool {
[[NSThread currentThread] setName:@"MyPermanentThread"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
self.threadRunning = YES;
// 使用CFRunLoopRun()启动无限循环
// 或者使用NSRunLoop的run方法,但需要注意防止RunLoop退出
// [runLoop run];
CFRunLoopRun();
}
}
- (void)executeTask:(void(^)(void))task {
if (!self.isThreadRunning) return;
[self performSelector:@selector(_executeTask:) onThread:self.thread withObject:task waitUntilDone:NO];
}
- (void)_executeTask:(void(^)(void))task {
if (task) {
task();
}
}
- (void)stop {
if (!self.isThreadRunning) return;
[self performSelector:@selector(_stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)_stop {
CFRunLoopStop(CFRunLoopGetCurrent());
self.threadRunning = NO;
}
- (void)dealloc {
[self stop];
}
@end
(2) 实现AFNetworking中的常驻线程
AFNetworking是一个流行的网络库,它创建了一个常驻线程来处理网络回调:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
3. RunLoop与性能优化
3.1 监控卡顿实现机制
通过监控RunLoop的状态切换,我们可以检测应用是否发生卡顿。当主线程被大量计算任务阻塞时,RunLoop的状态切换会变慢,我们可以通过观察这些状态切换来发现潜在的卡顿。
@interface APMMonitor : NSObject
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) CFRunLoopObserverRef observer;
@property (nonatomic, assign) CFRunLoopActivity currentActivity;
@property (nonatomic, strong) NSThread *monitorThread;
@property (nonatomic, assign) BOOL monitoring;
@property (nonatomic, assign) NSTimeInterval threshold; // 卡顿阈值,单位秒
- (instancetype)initWithThreshold:(NSTimeInterval)threshold;
- (void)startMonitoring;
- (void)stopMonitoring;
@end
@implementation APMMonitor
- (instancetype)initWithThreshold:(NSTimeInterval)threshold {
self = [super init];
if (self) {
_threshold = threshold;
_semaphore = dispatch_semaphore_create(0);
}
return self;
}
- (void)startMonitoring {
if (_monitoring) return;
// 创建RunLoop观察者
CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
_observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallback,
&context);
// 将观察者添加到主线程RunLoop
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
// 创建监控线程
_monitorThread = [[NSThread alloc] initWithTarget:self
selector:@selector(monitorThreadEntryPoint)
object:nil];
[_monitorThread setName:@"APM.MonitorThread"];
[_monitorThread start];
_monitoring = YES;
}
static void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
APMMonitor *monitor = (__bridge APMMonitor *)info;
monitor.currentActivity = activity;
// 发送信号通知监控线程
dispatch_semaphore_signal(monitor.semaphore);
}
- (void)monitorThreadEntryPoint {
while (_monitoring) {
// 假设主线程卡顿,一直没有进入下一个RunLoop状态,那么这里会等待超时
long semaphoreWaitTime = dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_threshold * NSEC_PER_SEC)));
if (semaphoreWaitTime != 0) {
if (!_monitoring) {
// 监控已停止,退出监控循环
break;
}
// 发生卡顿,获取堆栈
[self captureMainThreadBacktrace];
}
}
}
- (void)captureMainThreadBacktrace {
// 获取主线程堆栈信息
NSString *backtrace = [self backtraceOfMainThread];
// 记录卡顿日志
NSLog(@"检测到卡顿,主线程堆栈信息:\n%@", backtrace);
// 在实际应用中,这里可以将堆栈信息上传到服务器进行分析
}
- (NSString *)backtraceOfMainThread {
// 获取主线程堆栈的实现
// 这里需要用到一些底层的C函数,如thread_get_state等
// 实际实现比较复杂
return @"堆栈信息获取实现...";
}
- (void)stopMonitoring {
if (!_monitoring) return;
_monitoring = NO;
// 移除观察者
if (_observer) {
CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
CFRelease(_observer);
_observer = NULL;
}
// 唤醒监控线程,使其能够退出
dispatch_semaphore_signal(_semaphore);
}
- (void)dealloc {
[self stopMonitoring];
}
@end
// 使用示例
APMMonitor *monitor = [[APMMonitor alloc] initWithThreshold:0.2]; // 设置200ms卡顿阈值
[monitor startMonitoring];
卡顿监控原理说明:
- 创建一个RunLoopObserver来监听主线程RunLoop的状态变化。
- 在另一个线程中等待主线程RunLoop的状态变化信号。
- 如果超过预定阈值(如200ms)没有收到状态变化信号,则认为发生了卡顿。
- 在发生卡顿时获取主线程的堆栈信息,用于分析导致卡顿的原因。
3.2 任务优先级调度技术
RunLoop提供了多种运行模式,可以用于任务的优先级调度。通过合理安排任务在不同模式下执行,可以优化应用性能。
// 高优先级任务(在所有模式下都执行)
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
// 低优先级任务(只在默认模式下执行,UI交互时会被暂停)
[timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
我们可以利用这一特性实现图片的延迟加载,以提高列表滚动的流畅度:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// 滚动停止后,加载可见图片
[self loadVisibleImages];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
// 滚动停止后,加载可见图片
[self loadVisibleImages];
}
}
- (void)loadVisibleImages {
// 获取可见cell
NSArray *visibleCells = [self.tableView visibleCells];
for (UITableViewCell *cell in visibleCells) {
if ([cell isKindOfClass:[MyImageCell class]]) {
MyImageCell *imageCell = (MyImageCell *)cell;
[imageCell loadImage]; // 加载图片
}
}
}
3.3 UI渲染与RunLoop关系
UI的渲染与RunLoop有着密切的关系:
- 屏幕刷新机制:iOS设备的屏幕刷新频率为60Hz,即每16.7ms刷新一次。系统会注册一个CADisplayLink到RunLoop中,以确保UI的渲染与屏幕刷新同步。
- UIKit的渲染流程:
- 在RunLoop开始时,系统会在beforeWaiting阶段调用
[CATransaction flush]
来提交所有UI更新。 - 这些更新会被打包发送到渲染服务(Render Server)。
- Render Server会在VSync信号到来时进行实际的渲染工作。
- 在RunLoop开始时,系统会在beforeWaiting阶段调用
- 异步绘制:为避免主线程阻塞,可以使用
CALayer.drawsAsynchronously = YES
将绘制操作放到后台线程执行。
高效UI渲染的一些建议:
- 避免主线程阻塞:耗时操作应放在子线程执行,以保持UI的响应性。
- 异步绘制:对于复杂的自定义视图,考虑使用异步绘制:
- (void)setupAsyncRendering {
self.layer.drawsAsynchronously = YES;
// 或者使用异步绘制
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
// 绘制代码...
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (__bridge id)image.CGImage;
});
});
}
- 减少离屏渲染:离屏渲染是指GPU需要在屏幕缓冲区以外创建新的缓冲区进行渲染,这会带来额外的性能开销。以下操作可能导致离屏渲染:
- cornerRadius + masksToBounds
- shadow
- mask
- allowsGroupOpacity(默认为YES)
- 预渲染:对于不常变化的复杂视图,可以考虑预渲染:
+ (UIImage *)preRenderBubbleWithSize:(CGSize)size {
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
// 绘制气泡背景
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, size.width, size.height) cornerRadius:8];
[[UIColor whiteColor] set];
[path fill];
// 绘制阴影
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2, 2, size.width - 4, size.height - 4) cornerRadius:7];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 3, [UIColor colorWithWhite:0 alpha:0.2].CGColor);
[[UIColor whiteColor] set];
[shadowPath fill];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 设置拉伸区域
return [image resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
}
3.4 网络请求优化策略
RunLoop可以用来优化网络请求的处理,特别是当应用处理大量并发请求时:
- 线程隔离:创建专门的线程处理网络回调,避免阻塞主线程:
+ (NSThread *)networkThread {
static NSThread *_networkThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_networkThread = [[NSThread alloc] initWithTarget:self
selector:@selector(networkThreadEntryPoint)
object:nil];
[_networkThread start];
});
return _networkThread;
}
+ (void)networkThreadEntryPoint {
@autoreleasepool {
[[NSThread currentThread] setName:@"NetworkThread"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
- (void)handleNetworkData:(NSData *)data {
[self performSelector:@selector(processNetworkData:)
onThread:[self.class networkThread]
withObject:data
waitUntilDone:NO];
}
- (void)processNetworkData:(NSData *)data {
// 在网络线程上处理数据
// ...
// 处理完成后,回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
}
- 请求优先级管理:利用RunLoop的不同模式来管理网络请求的优先级:
// 高优先级请求(无论何时都会处理)
[self performSelector:@selector(sendHighPriorityRequest)
onThread:networkThread
withObject:nil
waitUntilDone:NO
modes:@[NSRunLoopCommonModes]];
// 低优先级请求(只在系统空闲时处理)
[self performSelector:@selector(sendLowPriorityRequest)
onThread:networkThread
withObject:nil
waitUntilDone:NO
modes:@[NSDefaultRunLoopMode]];
- 分批处理:对于大量数据的处理,可以利用RunLoop的特性进行分批处理,避免长时间阻塞:
- (void)processBatchedData:(NSArray *)data {
NSUInteger count = data.count;
NSUInteger processedCount = 0;
NSUInteger batchSize = 100;
// 创建一个递归Block进行分批处理
__block __weak void (^weakProcessBatch)(void);
void (^processBatch)(void) = ^{
@autoreleasepool {
// 计算本批次要处理的数量
NSUInteger currentBatchSize = MIN(batchSize, count - processedCount);
NSRange range = NSMakeRange(processedCount, currentBatchSize);
// 处理当前批次
NSArray *batch = [data subarrayWithRange:range];
[self processBatch:batch];
processedCount += currentBatchSize;
if (processedCount < count) {
// 还有数据需要处理,使用performSelector:afterDelay:将下一批处理放到下一个RunLoop周期
[self performSelector:@selector(executeProcessBatch:)
withObject:[weakProcessBatch copy]
afterDelay:0.01];
} else {
// 所有数据处理完成
[self allBatchesProcessed];
}
}
};
weakProcessBatch = processBatch;
// 开始处理第一批
processBatch();
}
- (void)executeProcessBatch:(void(^)(void))block {
if (block) {
block();
}
}
- (void)processBatch:(NSArray *)batch {
// 处理单个批次的数据
// ...
}
- (void)allBatchesProcessed {
// 所有批次处理完成后的回调
// ...
}
总结
RunLoop是iOS开发中的核心机制,它不仅是线程管理的基础,也是实现高性能应用的关键。通过深入理解RunLoop的原理和应用,我们可以:
- 优化应用性能:合理安排任务在不同的RunLoop模式下执行,避免主线程阻塞。
- 提高UI流畅度:理解UI渲染与RunLoop的关系,优化渲染流程。
- 实现高级功能:利用RunLoop实现线程常驻、延迟加载、卡顿监控等功能。
在实际开发中,我们应该根据具体需求选择合适的RunLoop应用策略,既不过度优化,也不忽视性能问题。只有透彻理解RunLoop的工作原理,才能在开发中游刃有余地应对各种复杂场景。