程序员iOS 开发每天分享优质文章

iOS探索:RunLoop本质、数据结构以及常驻线程实现

2018-12-21  本文已影响1人  熊猫超人biubiubiu

RunLoop的本质

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象

这里有一个问题,我们应用程序中的main函数为什么可以保持无退出呢

实际上呢,在我们的main函数中会调用UIApplicationMain函数,在这个函数中会启动一个运行循环(也就是我们所说的RunLoop),在这个运行循环中可以处理很多事件,例如屏幕的点击,滑动列表,或者网络请求的返回等等,在处理完事件之后,会进入等待,在这个循环中,并不是一个单纯的for循环或者while循环,而是从用户态到内核态的切换,以及再从内核态到用户态的切换,这里面的等待也不等于死循环,这里面最重要的是状态的切换

RunLoop的数据结构

在OC中,系统为我们提供了两个RunLoop,一个是CFRunLoop,另一个是NSRunLoop,而NSRunLoop是对CFRunLoop的一个封装,提供了面向对象的API,并且它们也分别属于不同的框架,NSRunLoop是属于Foundation框架,而CFRunLoop是属于Core Foundation框架

关于RunLoop的数据结构主要有三种:

WX20181221-145251@2x.png WX20181221-150257@2x.png

CFRunLoopSource

CFRunLoopTimer

和NSTimer是toll-free bridge的(免费桥转换)

CFRunLoopObserver

我们可以通过注册一些Observer来实现对RunLoop相关时间点的观测

可以观测的时间点包括:

RunLoop的mode

WX20181221-153513@2x.png

在RunLoop中,假如在mode1中运行,那么在mode2中事件的回调就会接收不到,RunLoop只接受在当前mode中的回调,那么这里有一个经典问题,当我们在滑动列表时,为什么会出现cell上的定时器停止的情况以及如何解决

因为在列表滑动的时候当前RunLoop的mode从Default切换到了Tracking,所以导致原来mode中的事件回调接收不到,想要解决便可将其加入commonModes中,下面我们来看一下commonMode

CommonMode的特殊性

事件循环的实现机制

WX20181221-161307@2x.png

这里有一个这样的问题:当我们点击一个app,从我们点击到程序启动、程序运行再到程序杀死这个过程,系统都发生了什么呢

实际上当我们调用了main函数之后,会调用UIApplicationMain函数,在这个函数内部会启动主线程的RunLoop,然后经过一系列的处理,最终主线程的RunLoop会处于一个休眠状态,然后我们此时如果点击一下屏幕,会转化成一个Source1来讲我们的主线程唤醒,然后当我们杀死程序时,会调用RunLoop的退出,同时发送通知告诉观察者

RunLoop与多线程

实现一个常驻线程

请看下面的一个代码逻辑

#import "WXObject.h"

static NSThread *thread = nil;
/** 是否继续事件循环*/
static BOOL runAlways = YES;

@implementation WXObject

+ (NSThread *)threadForDispatch {
    
    if (thread == nil) {
        @synchronized (self) {
            if (thread == nil) {
                thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
                [thread setName:@"alwaysThread"];
                //启动线程
                [thread start];
            }
        }
    }
    
    return thread;
}

+ (void)runRequest {
    
    //创建一个Source
    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    
    //创建RunLoop,同时向RunLoop的defaultMode下面添加Source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    
    //如果可以运行
    while (runAlways) {
        @autoreleasepool {
            //令当前RunLoop运行在defaultMode下
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
        }
    }
    
    //某一时机,静态变量runAlways变为NO时,保证跳出RunLoop,线程推出
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

@end

以上就是实现一个常驻线程的代码逻辑

GitHub

Demo

上一篇 下一篇

猜你喜欢

热点阅读