iOS:RunLoop底层结构与线程保活

    技术2022-08-01  89

    大家好,我是OB! 今天来聊聊RunLoop!

    一、RunLoop 本质

    RunLoop就是一个运行循环,在每次循环中接受消息,处理消息,然后休眠或者进入下一次循环。

    RunLoop底层就是一个while循环;

    官网的解释是:在2-9之间循环

    休眠:该线程会释放占用的所有资源(从CPU任务队列里移除),系统会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程或者系统才能被唤醒。

    什么是source0和source1?

    source0基本就是应用层事件,source1基本就是系统事件;

    Source1 :基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(iOS里进程间通信开发过程中我们一般不主动使用)。mach_port大家就理解成进程间相互发送消息的一种机制就好。

    Source0 :非基于Port的 处理事件,什么叫非基于Port的呢?就是说你这个消息不是其他进程或者内核直接发送给你的。

    用户手指点击了一下APP界面,会发生怎么样的过程?

    我们触摸屏幕,先摸到硬件(屏幕),屏幕会把改事件会先包装成Event, Event先告诉source1(mach_port),由source1唤醒RunLoop,然后将事件Event分发给source0,然后由source0来处理。

    二、RunLoop底层结构

    RunLoop也是一个对象,底层也是一个结构体struct __CFRunLoop(如下图)。

    具有以下特征:

    1、一个RunLoop包含一个或者多个Mode,每个Mode又包含Source0/Source1/Timer/Observer
    2、开启RunLoop时,只能从Modes集合CFMutableSetRef中选择一个Mode作为当前的currentMode启动,如果遇到切换Mode(例如页面滑动,活动停止时),要退出当前Loop,再重新选择一个Mode进入
    3、如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

    runloop 部分源码

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { //通知Observer进入runloop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); //处理事件 result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //通知Observer 退出runloop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; }

    通过源码可以窥探一二

    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm...) { __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); __CFRunLoopDoBlocks(rl, rlm); __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); __CFRunLoopDoBlocks(rl, rlm); __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); do { __CFRunLoopServiceMachPort(...); if (....) { break;} } while (1); __CFRunLoopDoBlocks(rl, rlm); retVal = kCFRunLoopRunStopped; } while (0 == retVal); return retVal; }

    三、线程与RunLoop

    线程中没有RunLoop,那么该线程执行完任务就会销毁。

    如下代码,观察执行结果:

    dispatch_async(dispatch_get_global_queue(0, 0), ^{ [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull t) { NSLog(@"---------%@",[[NSRunLoop currentRunLoop] currentMode]); }]; NSLog(@"-----end----"); });

    打印如下

    Test_06_Runloop[96546:11552658] -----end----

    发现根本不会执行timer中的任务,因为线程已经销毁了。而NSTimer的使用官方也说了,必须依赖RunLoop(NSTimer底层就是通过RunLoop实现)。 所以这时必须要让线程中的RunLoop 运行run起来才能执行timer中的任务

    注意:NSTimer可以自动添加到RunLoop中,所以[loop addTimer: forMode:]可以省略,但是[loop run]或者[loop runMode:beforeDate:]必须执行才能让RunLoop运行起来

    换成如下代码

    dispatch_async(dispatch_get_global_queue(0, 0), ^{ //不能写在timer创建之前,不然RunLoop运行时,其中没有事件源,也会立马退出 //[[NSRunLoop currentRunLoop] run]; NSTimer*timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull t) { NSLog(@"---------%@",[[NSRunLoop currentRunLoop] currentMode]); }]; //可写可不写,因为timer自动添加到RunLoop,如果RunLoop存在 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; //[[NSRunLoop currentRunLoop] run]不能退出,后面会说到,不急 //可以控制RunLoop是否退出, while (!_stop) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"-----end----"); });

    打印如下:线程没有退出,timer正常执行

    Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode

    四、线程保活

    开启RunLoop的几种方法

    - (void)run; - (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

    说明一下,run方法内部其实大概就是不断调用:runMode:beforeDate:

    while (condition) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }

    线程start时,在线程内部添加一个RunLoop,并且保证添加的RunLoop有source/timer/observer其中的一种,不然RunLoop会立马退出。要想控制线程的生命周期,不能用run方法,要用runMode:beforeDate:方法,因为官方说的:run()方法一旦开启RunLoop,那么将不会被打断(If you want the run loop to terminate, you shouldn’t use this method. ),贴心的官方还给出示例:

    BOOL shouldKeepRunning = YES; // global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

    以下代码可以让线程休眠,不退出。

    self.thread =[[NSThread alloc] initWithBlock:^{ _shouldKeepRunning = YES;// global NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (_shouldKeepRunning){ [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }; }]; [self.thread start];

    需要用子线程执行任务时,可以调用

    [self performSelector:@selector(doSomethingInSubThread:) onThread:self.thread withObject:@"sdf" waitUntilDone:NO];

    想要线程停止,需要先关闭RunLoop,

    注意: CFRunLoopStop()方法是停止当前那一次循环,想要彻底停止,那么需要添加标志位_shouldKeepRunning 去判断

    - (void)stopSubThread { CFRunLoopStop(CFRunLoopGetCurrent()); } - (void)dealloc { _shouldKeepRunning = NO; [self performSelector:@selector(stopSubThread) onThread:self.thread withObject:nil waitUntilDone:NO]; }

    五、监测observer的状态

    observer可以监测到RunLoop的mode的切换, 比如页面滑动时先kCFRunLoopExit 当前的NSDefaultRunLoopMode,然后再kCFRunLoopEntry一个新的UITrackingRunLoopMode

    CFRunLoopObserverRef runloopObsever=CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()); switch (activity) { case kCFRunLoopEntry: NSLog(@"kCFRunLoopEntry - %@",mode); break; case kCFRunLoopBeforeTimers: NSLog(@"kCFRunLoopBeforeTimers - %@",mode); break; case kCFRunLoopBeforeSources: NSLog(@"kCFRunLoopBeforeSources - %@",mode); break; case kCFRunLoopBeforeWaiting: NSLog(@"kCFRunLoopBeforeWaiting - %@",mode); break; case kCFRunLoopAfterWaiting: NSLog(@"kCFRunLoopAfterWaiting - %@",mode); break; case kCFRunLoopExit: NSLog(@"kCFRunLoopExit - %@",mode); break; default: break; } CFRelease(mode); }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObsever, kCFRunLoopCommonModes); CFRelease(runloopObsever);

    其中部分打印

    Test_06_Runloop[97611:11941654] kCFRunLoopAfterWaiting - kCFRunLoopDefaultMode //唤醒 Test_06_Runloop[97611:11941654] kCFRunLoopBeforeTimers - kCFRunLoopDefaultMode //处理timer Test_06_Runloop[97611:11941654] kCFRunLoopBeforeSources - kCFRunLoopDefaultMode //处理source Test_06_Runloop[97611:11941654] kCFRunLoopBeforeWaiting - kCFRunLoopDefaultMode //没事可做了,处理完当前任务后休眠

    今天先到这里,下期见!欢迎留言!

    Processed: 0.016, SQL: 9