IOS-RunLoop附面试题,应用场景

简述

  • 运行循环、跑圈 内部是一个do-while循环 在这个循环内部不断处理各种比如(source timer observer) 有事情就跑一圈看看有没有东西要处理 提高程序性能

作用

  • 可以实现 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。

RunLoop 对外的接口

  • 在 CoreFoundation 里面关于 RunLoop 有5个类:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

其中CFRunLoopModeRef类并没有对外暴露,只是通过CFRunLoopRef的接口进行了封装。他们的关系如下:

CFRunLoopRef

  • 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
  • 上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
系统默认注册了5个Mode
CFRunLoopTimerRef是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
CFRunLoopObserverRef是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 1
kCFRunLoopBeforeTimers = (1UL << 1), 2
kCFRunLoopBeforeSources = (1UL << 2), 4
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopAfterWaiting = (1UL << 6), 64
kCFRunLoopExit = (1UL << 7), 12867
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoop的Mode

Mode是一种状态

  • 苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。

    应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

  • 同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操作 Common Items,或标记一个 Mode 为 “Common”。使用时注意区分这个字符串和其他 mode name。

    应用场景举例:有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。

RunLoop 的内部逻辑

RunLoop 内部的逻辑
  • 实际上就是一个do-while循环 当你调用RunLoop并且让他开始Run的时候线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

下面是例子

autoreleasepool

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

熟悉吗?是的。每个程序被创建我们都能在main文件中看到这一行,在这里UIApplicationMain表示主线程已经创建了一个RunLoop, 线程结束销毁。注意哦RunLoop在子线程中不是自动创建的。在NSRunLoop进入休眠状态前会把该进程中的东西释放(autoreleasepool),第二次唤醒的时候再把数据添加进池子。

Timer

#pragma mark 定时器
-(void)timer{
    //调用了scheduledTimer返回的定时器,已经自动被添加到当前的runLoop中,而且是NSDefaultRunLoopMode 但是仍然可以通过addtimer的方式修改他的runloop
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    //选择在一个模式(mode)下运行,要想切换就必须先退出当前模式,然后再去重开另外一个模式。一个模式下只能执行当面模式下的状态

    //添加定时器只在NSDefaultRunLoopMode(空闲)下可以使用 进入其他模式后timer就不能工作了
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
   
    //添加定时器只在UITrackingRunLoopMode(滑动)下可以使用 进入其他模式后timer就不能工作了
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    //定时器会表跑在标记为NSRunLoopCommonModes的模式下 标记有UITrackingRunLoopMode(scrollerview滚动)和NSDefaultRunLoopMode(空闲状态下)
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
}

Source

Observers

#pragma mark Observer
-(void)observers{
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"---------监听状态改变----%zd",activity);
    });

    //添加观察者:监听runloop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    //释放observ
    CFRelease(observer);
    //Core foundati内Create Copy Retain 都会让计数器+1 都要释放
}

PerformSelecter

  • 让某个对象在执行performSelector的时候一定要处于某种情况下 这句话在@selector的方法执行完的时候出于消亡状态。
  • 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
[object performSelector:@selector(method) withObject:nil afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
  • 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
[self performSelector:@selector(method) onThread:Somethread withObject:nil waitUntilDone:NO];

GCD

  • 当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。

常驻线程

  • 概念:让一个子线程不进入消亡状态,让他一直处于runloop状态 等待其他线程发来消息 处理时间。
  • 做法:在该线程中添加一个mode并且run起来该NSRunLoop
- (void)viewDidLoad {
    [super viewDidLoad];
    //创建线程
    self.thread = [[WXZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
   //开启线程
    [self.thread start];
}
- (void)run
{
    NSLog(@"----------run----%@", [NSThread currentThread]);
    
    //保证线程永远都在运行状态,并且开始在NSDefaultRunLoopMode下等待处理[NSPort port]
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    //开始启动循环处理self.thread线程内的事件
    [[NSRunLoop currentRunLoop] run];
    
    //后面的都不是self.thread线程的事情 所以没有被打印 因为不会走到这一步 上面的一直在跑圈监听self.thread的线程
    NSLog(@"----------我没有被打印---------");
}

//当外界给这个正在RunLoo的线程一个外力时,也能够发送消息给test。因为self.thread线程没有消亡(RunLoop即使休眠了也会因为self.thread里有mode而不会消亡)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //selector也是一个source
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)test
{
    NSLog(@"----------test----%@", [NSThread currentThread]);
}

在什么情况下可以使用runloop

  1. 常驻线程 概念:让一个子线程不进入消亡状态,让他一直处于runloop状态 等待其他线程发来消息 处理时间。做法:在该线程中添加一个mode并且run起来该NSRunLoop
  2. 在子线程中开启定时器NSTimer
  3. 在子线程中进行一些长期监控 在什么情况下使用:长时间的耗时操作 比如socket
  4. 让某些事情在特定的mode下实行
  5. 可以添加observers监听runloop的状态 比如在点击事件前先做一些事情

一些面试题

runloop 和线程的关系:

  1. 主线程的run loop默认是启动的。

iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {
@autoreleasepool {   
 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}}

重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

  1. 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

  2. 在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

runloop的mode作用是什么?

1.model 主要是用来指定事件在运行循环中的优先级的,分为:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态

  • UITrackingRunLoopMode:ScrollView滑动时

  • UIInitializationRunLoopMode:启动时

  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

2.苹果公开提供的 Mode 有两个:

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes)

以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?

RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响scrllView的滑动。

如果我们把一个NSTimer对象NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。

同时因为mode还是可定制的,所以:Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。

猜想runloop内部是如何实现的?

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑 是这样的:

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

推荐阅读更多精彩内容

  • 原文地址:http://blog.ibireme.com/2015/05/18/runloop/ RunLoop ...
    大饼炒鸡蛋阅读 1,142评论 0 6
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技术 RunLoop 是 iOS 和 ...
    橙娃阅读 847评论 1 2
  • 转载:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling阅读 1,432评论 0 13
  • 转自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飘金阅读 972评论 0 4
  • 我是80后。小的时候,不懂钱是什么?只知道钱可以换来甜甜的棒棒糖、可以换来好看的小画书、可以换来能够吹的很大...
    林凤舞阅读 190评论 0 0