iOS RunLoop的使用及底层原理

”大师,近日我研读线程操作之法,发现了一个问题。我的子线程做完了任务之后就销毁了,后续再来任务,我需要重新开一个子线程,但为什么我手中的这部iPhone18点开一个APP却能一直运转呢?“

”哦,少侠是说这个问题啊,简单。为什么能一直运转呢?因为电池电量没用完。“

”......“

“哈哈哈,少侠如此好学,老夫就授予你RunLoop使用大法和RunLoop内功心法,融汇贯通这两种秘籍,iPhone18的运行机理会在你眼中变的越来越清晰。”

RunLoop使用大法

日常开发中主要使用在一下几个方面:

  1. NSTimer相关使用
  2. 子线程保活
  3. Perform Selector的使用
  4. 更深层次操作
  1. NSTimer在主线程时,列表滑动时也可正常使用。
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

创建NSTimer时,默认是运行在主线程的NSRunLoopDefaultMode中,当UIScrollView滚动时,会切换到UITrackingRunLoopMode,此时定时器会停止调用。
为了让定时器正确调用,可以手动将RunLoop加入到NSRunLoopCommonModes中,此时定时器可以正确调用。

  1. 在子线程中使用NSTimer。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
     NSRunLoop *runloop = [NSRunLoop currentRunLoop];
     [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
     [runloop run];
 });

子线程中的RunLoop在第一次获取时创建,把timer加入到对应的Mode之后,子线程中的timer正常工作。注意:如果想要取消子线程中的timer,也要在对应的子线程中取消。

  1. 子线程保活。AFNetworking2.x中有一个示范性的应用。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
    [[NSThread currentThread] setName:@"AFNetworking"];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort 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;
}

主线程的RunLoop是默认开启的,子线程的RunLoop默认是不开启的,所以子线程执行完一个任务之后,就会被销毁。如果想让子线程一直存活,就需要创建RunLoop。NSRunLoop *runLoop = [NSRunLoop currentRunLoop];,如果只是这样,会发现子线程依然是做完任务就退出了。为什么出现这样的情况呢?因为RunLoop想要运行在一个mode下,需要mode中有Source、Timer、Observer,当这些都没有时RunLoop会退出。所以添加了[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];,相当于添加了一个Source,这样子线程的RunLoop就能一直运行了,后台线程保活成功。

  1. Perform Selector的使用
- (void)viewDidLoad {
   [super viewDidLoad];    
   [self performSelector:@selector(onMainThread) withObject:nil afterDelay:3];
 
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [[NSThread currentThread] setName:@"gaga"];
       [self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
   });

}

- (void)onMainThread {
   NSLog(@"%@", [NSThread currentThread]);
   NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)onOtherThread {
   NSLog(@"%@", [NSThread currentThread]);
   NSLog(@"%@", NSStringFromSelector(_cmd));
}

在主线程和子线程中分别调用performSelector:withObject:afterDelay:,发现主线程的正常执行,但是子线程中调用的不能执行。

performSelector:withObject:afterDelay延时操作相当于创建一个Timer添加到当前线程的RunLoop中,因为主线程的RunLoop一直存在,所以可以正常执行。而子线程的RunLoop在第一次获取时才会创建,因为我们没有手动获取子线程的RunLoop,所以不能正常执行。

将子线程修改为如下即可正常执行

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[NSThread currentThread] setName:@"gaga"];
        [self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop run];
    });

打印子线程的RunLoop可以看到Timer,证实了延时操作会加入Timer的想法。

- (void)onOtherThread {
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    NSLog(@"%@", runLoop);
}

结果:
timers = <CFArray 0x600001fa6d60 [0x10da31ae8]>{type = mutable-small, count = 0, values = ()},
    currently 589343081 (174839084728530) / soft deadline in: 1.84465692e+10 sec (@ -1) / hard deadline in: 1.84465692e+10 sec (@ -1)

“少侠,想一想如果不用延时,直接在主线程和子线程使用performSelector:withObject:,会正常调用吗?”

“额...”

"答案是会正常调用,因为直接使用performSelector:withObject:,相当于是个方法调用,不涉及Runloop。"

  1. 其他操作

当然还有其他的应用,比如在子线程中执行performSelector这个方法时,如果不创建RunLoop会发现方法无法调用。这个留给少侠,自己摸索了。

还有一种应用在iOS性能优化(中级+): 异步绘制有所体现,添加Observer,在RunLoop运行中的合适时机执行想要操作的代码。

“少侠可看好了,以上这些使用方法涵盖了RunLoop大部分的应用层使用。”

“大师,这们武功修炼起来,确实让人经络通畅,身心舒适。”

“但这些只是招式,少侠如果想要更好的了解iPhone的运行机理,还需修炼对应的内功心法方方能发挥出本门武功的最大威力。”

RunLoop内功心法

线程与runloop一一对应,其关系保存在一个全局的Dictionary里。线程刚创建时没有RunLoop,如果你不主动获取,那它就一直不会有。
RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生在线程结束时。RunLoop不能直接创建,只能获取。
提供了两个自动获取的函数:CFRunLoopGetMain()CFRunLoopGetCurrent(),且只能在一个线程的内部获取其RunLoop(主线程除外)。以上这些关系,可以通过观察CF源码中,CFRunLoop.c 文件里,
_CFRunLoopGet0这个方法查看。

RunLoop

RunLoop主要涉及这几个类
CFRunLoopRef CFRunLoopModeRef CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopObserverRef,一个RunLoop包含多个Mode,每个Mode又包含多个Source/Timer/Observer。每次调用RunLoop的主函数时,只能指定其中一个Mode,这个Mode被称作CurrentMode,如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入。这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响。如果一个mode中一个item都没有,RunLoop会直接退出,不进入循环。

附一张RunLoop运行图

RunLoop运行
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容