iOS开发进阶:RunLoop

什么是Runloop?
Runloop是通过内部维护的事件循环来对事件和消息进行管理的一种机制。当没有消息需要处理的时候,线程进入休眠以避免占用资源,有消息需要处理时,立即被唤醒。

runloop循环不是单独的do-while循环,而是发生一个用户态到内核态切换,以及内核态到用户态切换。它维护的事件循环可以用来不断的处理消息和事件,当没有消息和事件需要处理时会从用户态切换到内核态,由此可以用来休眠线程,避免资源占用。当有消息需要处理时会从内核态切换到用户态,当前线程会被唤醒,所以状态切换才是runloop的关键。

main函数为何能保持不退出?
main函数中,会调用UIApplicationMain函数,在内部会启动主线程的Runloop,可以不断的接收消息,比如点击屏幕事件,滑动列表以及处理网络请求的返回等接收消息后对事件进行处理,处理完之后,就会继续等待。

iOS中提供了两套Runloop接口,一个是NSRunLoop基于Objective-C,在Foundation框架中,另一个是CFRunLoopRef基于C,在CoreFoundation中。而NSRunLoop是对CFRunLoopRef的封装,两者接口基本都是对应的。CFRunLoopRef runloop = [nsrunloop getCFRunLoop]可以获取对应的CFRunLoopRef。通过一个表格来对比一下:

特征 NSRunLoop CFRunLoopRef
所属框架 Objective-C/Foundation C/CoreFoundation
获取Runloop [NSRunLoop currentRunLoop]
[NSRunLoop mainRunLoop]
CFRunLoopGetCurrent()
CFRunLoopGetMain()
Source事件 addPort:forMode:
removePort:forMode:
CFRunLoopAddSource(...)
CFRunLoopRemoveSource(...)
Timer事件 addTimer:forMode: CFRunLoopAddTimer(...)
Observer事件 CFRunLoopAddObserver(...)
CFRunLoopRemoveObserver(...)

1. __CFRunLoop相关基本数据结构

struct __CFRunLoop {
    ...
    pthread_t _pthread;//对应的线程
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    ...
};

__CFRunLooprunloop本身:typedef struct __CFRunLoop *CFRunLoopRef__CFRunLoop对应多个__CFRunLoopMode

struct __CFRunLoopMode {
    ...
    CFStringRef _name;
    CFMutableSetRef _sources0;//
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    ...
};

__CFRunLoopMode是runloop的运行模式:typedef struct __CFRunLoopMode *CFRunLoopModeRef。每一个__CFRunLoopMode又包含多个_sources0、_sources1、_observers、_timers事件。_sources0:非基于Port的,也就是用户主动发出的事件。_sources1:基于Port的,也就是系统内部的消息事件。_observers:观察者。
_timers:定时器事件。
相关的类的成员变量与关系:

2.NSRunLoop的创建

[NSRunLoop mainRunLoop]对应底层的CFRunLoopGetMain(),[NSRunLoop currentRunLoop]对应底层的CFRunLoopGetCurrent(),内部都是通过CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)获取的runloop,分别传入pthread_main_thread_np()pthread_self()也就是主线程和当前线程的id。

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        //如果外部传入无效的0,则将主线程ID赋值给t。
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        //如果__CFRunLoops为空,则创建主线程对应的runloop
        __CFUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //__CFRunLoopCreate做线程的初始化
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); 
        // 将mainLoop保存到dict中,以线程id为key,mainLoop为value
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        //将dict中的内容复制到__CFRunLoops地址上
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    //通过线程id获取runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
        //如果获取到的为空,则直接创建
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //如果创建后还不能获取到则使用刚才创建的,并将newLoop保存到__CFRunLoops中
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        //如果获取的是当前线程的
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

__CFRunLoopCreate中通过_CFRuntimeCreateInstance实例华再进行其他变量的一些初始化。其中loop->_pthread = t;将线程id绑定到了runloop上。runloop通过线程id去查找,如果没有则进行创建并将线程id绑定到runloop上,通过这个规则我们知道:线程与runloop一一对应;线程不一定都有runloop,首个runloop创建时会检查__CFRunLoops是否为空,为空则先创建主线程的runloop,再创建指定线程的runloop

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章