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

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

一、RunLoop 本质

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

RunLoop底层就是一个while循环;

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

iOS:RunLoop底层结构与线程保活_第1张图片

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

二、RunLoop底层结构

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

iOS:RunLoop底层结构与线程保活_第2张图片

具有以下特征:

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

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,并且保证添加的RunLoopsource/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可以监测到RunLoopmode的切换,
比如页面滑动时先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 //没事可做了,处理完当前任务后休眠

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

你可能感兴趣的:(iOS进阶)