学习MJ的视频课程,整理总结知识点–Runloop
RunLoop的运行逻辑
RunLoop 循环等待处理sources、timers,处理完睡觉




MJ流程图介绍(熟悉)
切换模式、线程销毁会推出Loop;循环2-10的步骤
实践RunLoop的调用流程
1 2 3 4
| - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"111111"); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 * frame #0: 0x00000001040f3571 Interview01-runloop流程`__29-[ViewController viewDidLoad]_block_invoke_2(.block_descriptor=0x00000001040f50c0) at ViewController.m:32:9 frame #1: 0x000000010440ddf0 libdispatch.dylib`_dispatch_call_block_and_release + 12 frame #2: 0x000000010440ed64 libdispatch.dylib`_dispatch_client_callout + 8 frame #3: 0x000000010441ce1c libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1500 frame #4: 0x00007fff23afb699 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 frame #5: 0x00007fff23af62f9 CoreFoundation`__CFRunLoopRun + 2329 frame #6: 0x00007fff23af56b6 CoreFoundation`CFRunLoopRunSpecific + 438 frame #7: 0x00007fff3815cbb0 GraphicsServices`GSEventRunModal + 65 frame #8: 0x00007fff47162a67 UIKitCore`UIApplicationMain + 1621 frame #9: 0x00000001040f3680 Interview01-runloop流程`main(argc=1, argv=0x00007ffeebb0bc68) at main.m:14:16 frame #10: 0x00007fff5123bcf5 libdyld.dylib`start + 1 frame #11: 0x00007fff5123bcf5 libdyld.dylib`start + 1
|
打印函数调用栈 bt
RunLoop执行流程源码分析,从CFRunLoopRunSpecific
开始,在CFRunLoop.c
中搜索CFRunLoopRunSpecific
,找打对应的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { Boolean did = false; if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; } volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl); CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; }
|
调用细节
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
_CFRUNLOOP_IS_CALLING_OUT_TO
…
GCD的很多流程是不依赖RunLoop的,除了dispatch_async(dispatch_get_main_queue(), ^{});
休眠细节

线程休眠,操作系统内核层面API才能实现,实现:mach_msg
函数
用户态、内核态(操作系统的知识稍微补一下)
等待消息
没有消息就让线程休眠
有消息就唤醒线程
状态
活动名状态
模式
两种,执行时互相隔离,互不影响
应用
控制线程的生命周期(线程保活)
线程是默认执行完里面的任务后就会被自动销毁,有些场景需要经常在子线程做事情,但是一做完就销毁了,再次使用还要创建,频繁的创建销毁时比较浪费的。AFN避免线程频繁创建销毁,使用RunLoop来使线程做完事情后以休眠态阻塞线程,避免被销毁,达到保活的效果。
保活基础代码
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
1 2 3
| [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run];
|
touchesBegan
时让子线程做事情,完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; MJThread *thread = [[MJThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start]; self.thread = thread; }
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES]; }
#pragma mark - Action
- (void)run { NSLog(@"start --%s", __func__); [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; NSLog(@"end --%s", __func__); }
- (void)test { NSLog(@"%s", __func__); } @end
|
优化
上面的代码,[MJThread alloc] initWithTarget:self
thread会强引用target
1 2 3 4 5 6 7 8 9
| MJThread *thread = [[MJThread alloc] initWithBlock:^{ NSLog(@"start --%s", __func__); [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; NSLog(@"end --%s", __func__); }];
|
此时控制器可以正常销毁,但是线程还没销毁
因为线程里面的block任务还没执行完毕,NSLog(@"end --%s", __func__);
还没执行
run的注释

run是个无限循环,想停止的话只能使用如下方法
解决如下:
1 2 3 4 5 6 7 8 9 10 11 12
| MJThread *thread = [[MJThread alloc] initWithBlock:^{ NSLog(@"start --%s", __func__); NSRunLoop *theRL = [NSRunLoop currentRunLoop]; [theRL addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf.shouldKeepRunning) { [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"end --%s", __func__); }]; [thread start];
|
在需要销毁时把shouldKeepRunning
修改为NO
1 2 3 4 5 6 7 8
| - (IBAction)stop:(id)sender { [self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:NO]; }
- (void)stopRunLoop { self.shouldKeepRunning = NO; CFRunLoopStop(CFRunLoopGetCurrent()); }
|
封装
1 2 3 4 5
| CFRunLoopSourceContext context = {0};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
|
这就是封装后的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| @implementation MJPermenantThread #pragma mark - public methods - (instancetype)init { if (self = [super init]) { self.innerThread = [[MJThread alloc] initWithBlock:^{ NSLog(@"begin----"); CFRunLoopSourceContext context = {0}; CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); CFRelease(source); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
NSLog(@"end----"); }]; [self.innerThread start]; } return self; }
- (void)executeTask:(MJPermenantThreadTask)task { if (!self.innerThread || !task) return; [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO]; }
- (void)stop { if (!self.innerThread) return; [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES]; }
- (void)dealloc { NSLog(@"%s", __func__); [self stop]; }
#pragma mark - private methods - (void)__stop { CFRunLoopStop(CFRunLoopGetCurrent()); self.innerThread = nil; }
- (void)__executeTask:(MJPermenantThreadTask)task { task(); }
@end
|
解决NSTimer在滑动时停止工作的问题
RunLoop只能处于某一种模式,默认模式,拖拽时切换到另一种模式,所以NSTimer的默认模式消失
NSRunLoopCommonModes并不是一个真的模式,它只是一个标记,timer能在_commonModes数组中存放的模式下工作
RunLoop里面放的有模式,模式里面放的有timers,timer设置为commonmodes时会添加到runloop的_commonModeitems
timer工作:在runloop的执行流程中工作,timer能唤醒RunLoop
监控应用卡顿
性能优化
总结
Runloop循环处理当前模式下的source0、source1、timers、observes
参考和源码
源码:
CF源码
参考:
iOS Runloop与线程保活
深入理解RunLoop