iOSiOS DeveloperObjective-C

RunLoop原理

2018-07-04  本文已影响12人  NeroXie

源代码

CoreFoundation源代码

RunLoop的获取

主线程的runloop

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();//判断是否需要fork 进程
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

当前线程的runloop

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    //先从TSD中查找有没有相关的runloop信息,有则返回。
    //我们可以理解为runloop不光存在与全局字典中,也存在中TSD中。
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

RunLoop与线程之间的关系

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //t为nil的时候,默认是使用主线程
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    //将线程和runloop之间关系通过一个全局字典保存起来
    //第一次进入的时候,会创建一个全局状态的,首先会为主线程创建一个runloop对象,并塞进该字典中,key是线程ID,value是runloop
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    //获取当前线程对应的runloop,如果没有就创建一个新的runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    //这里又使用了CFDictionaryGetValue方法获取loop,目的是为了防止
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        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);
    }
    //这边代码的作用:当线程销毁时,顺便也销毁其对应的runLoop
    //_CFSetTSD的代码位于CFPlatform.c文件中,传入PTHREAD_DESTRUCTOR_ITERATIONS-1的时候,会清除TSD中的储存着的runloop信息。(void (*)(void *))__CFFinalizeRunLoop 则会清空掉字典中,当前线程与runloop之间的关系
    //TSD: Thread-Specific Data 它是线程的一个数据存储单元,和全局变量很像,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的
    if (pthread_equal(t, pthread_self())) {//声明的线程ID和线程自身ID相等
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

通过源代码我们可以知道:

  1. runloop和线程之间是一一对应的,它们之间的关系保存在一个全局字典以及TSD中。
  2. 在线程创建的时候,是没有对应的runloop,runloop的创建是在第一次获取的时候,runloop的销毁则发生在线程销毁的时候。

RunLoop的类和相关属性介绍

在CFRunLoop中关于RunLoop的类一共有五个,它们分别是CFRunLoopRefCFRunLoopSourceRefCFRunLoopObserverRefCFRunLoopTimerRefCFRunLoopModeRef

struct __CFRunLoop {
    CFMutableSetRef _commonModes;//common mode的集合
    CFMutableSetRef _commonModeItems;//每个common mode都有的item(source,timer and observer)集合
    CFRunLoopModeRef _currentMode;//当前runloop的mode
    CFMutableSetRef _modes;//所有的mode的集合
    ...
};

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;//source0的集合
    CFMutableSetRef _sources1;//source1的集合
    CFMutableArrayRef _observers;//observer的数组
    CFMutableArrayRef _timers;//timer的数组
    ...
   };
   
//CFRunLoopSourceRef
struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
};

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    //当source被添加到RunLoop中后,会调用这个回调
    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    //当调CFRunLoopSourceInvalidate 函数移除该source的时候,会执 此回调。
    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    //RunLoop处 该Source的时候会执 的回调
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    //mach_port:通过内核对线程发送消息
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *  (*getPort)(void *info);
    void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

总结5个类之间的关系:
CFRunLoopRef对应的结构体中包含了若干Mode,而每个CFRunLoopModeRef中有包含了若干CFRunLoopSourceRefCFRunLoopObserverRefCFRunLoopTimerRef

Current Mode

runloop必须指定一个currentMode(_currentMode的赋值在CFRunLoopRunSpecific函数中,CFRunLoopRunSpecific函数在CFRunLoopRunCFRunLoopRunInMode中调用),如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入

Source

Source有两个版本:Source0和Source1,其中Source0只包含一个指针回调(perform),Source1除了这个指针回调以为还有一个mach_port。match_port是用于内核向线程发送消息的,所以Source1能够主动唤醒RunLoop线程,Source0需要先调CFRunLoopSourceSignal(source)将其标记为待处 ,然后再调用CFRunLoopWakeUp(runloop)来唤醒RunLoop以处理该事件。

Common Mode

在iOS下,主线程会预设两个Mode: kCFRunLoopDefaultMode 和 UITrackingRunLoopMode ,并且都被标记为“CommonModes”。

什么是CommonModes?它其实是一个标识符,并不是一个具体的Mode。以addSource为例:

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rls)) return;
    Boolean doVer0Callout = false;
    __CFRunLoopLock(rl);
    //当Mode为kCFRunLoopCommonModes
    if (modeName == kCFRunLoopCommonModes) {
    //如果_commonModes存在则获取一份拷贝数据
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    //_commonModeItems如果不存在则创建一个新的集合
    if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
    //将source添加到_commonModeItems
    CFSetAddValue(rl->_commonModeItems, rls);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, rls};
        /* add new item to all common-modes */
        //调用__CFRunLoopAddItemToCommonModes函数向_commonModes中所有的Mode添加这个source
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }
    } else {
        //此时该Mode不是CommonMode,从runloop中获取当前对应的Mode
    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        //当该Mode存在于RunLoop中,并且该Mode的_sources0集合为空
    if (NULL != rlm && NULL == rlm->_sources0) {
        //创建_sources0集合和_sources1集合
        rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
    }
        //当该Mode存在于RunLoop中,并且_sources0集合和_sources1集合中都不包含该Source
    if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
        //说明是Source0
        if (0 == rls->_context.version0.version) {
            CFSetAddValue(rlm->_sources0, rls);
        } else if (1 == rls->_context.version0.version) {//说明是Source1
            CFSetAddValue(rlm->_sources1, rls);
        __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
        if (CFPORT_NULL != src_port) {
            CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
            __CFPortSetInsert(src_port, rlm->_portSet);
            }
        }
        __CFRunLoopSourceLock(rls);
        if (NULL == rls->_runLoops) {
            rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
        }
        CFBagAddValue(rls->_runLoops, rl);
        __CFRunLoopSourceUnlock(rls);
        if (0 == rls->_context.version0.version) {
            if (NULL != rls->_context.version0.schedule) {
                //如果是Source0,并且schedule回调 为空,标记为需要出发回调
                doVer0Callout = true;
            }
        }
    }
        if (NULL != rlm) {
        __CFRunLoopModeUnlock(rlm);
    }
    }
    __CFRunLoopUnlock(rl);
    if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but
        // to do this after unlocking the run loop and mode locks, to avoid deadlocks
        // where the source wants to take a lock which is already held in another
        // thread which is itself waiting for a run loop/mode lock
    rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
    }
}

一个Mode通过将其ModeName可添加到RunLoop的的“commonModes”中。每当RunLoop的内容发生变化时,RunLoop都会自动将_commonModeItems里的Source/Observer/Timer同步到具有“Common”标记的所有Mode里(这句话很重要)。

这里我们做一下拓展,在面试的时候经常被问到的一个问题--为什么列表滑动的时候,NSTimer不执行回调?该如何解决?
默认NSTimer是运行在RunLoop的kCFRunLoopDefaultMode下,在列表滑动的时候,RunLoop会进入UITrackingRunLoopMode,因为RunLoop只能运行在一种模式下,所以NSTimer不会执行回调。那如何解决呢?这似乎更简单,现成的API就有添加到CommonModes就可以了,这又是为什么呢?其实不难理解,kCFRunLoopDefaultModeUITrackingRunLoopMode都已经被标为”Common”属性的。这就解释了为什么添加到CommonModes的时候就可以解决NSTimer的回调问题。

RunLoop运行

RunLoop通过CFRunLoopRunCFRunLoopRunInMode这两个函数运行。

void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    //线程就会一直停留在这个do-while的循环里直到result为kCFRunLoopRunStopped或者kCFRunLoopRunFinished
    //函数不会主动调用CFRunLoopStop函数(kCFRunLoopRunStopped)
或者将所有事件源移除(kCFRunLoopRunFinished),从这里我们也可以了解,如果runloop的_currentMode值变化,只能退出,然后重新指定一个Mode进入
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
    

上述两个函数均调用了CFRunLoopRunSpecific这个函数,那接下来我们分析一下这个函数

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    //是否释放
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 首先根据modeName找到对应Mode,如果没有则创建一个新的Mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //如果mode为空或者mode中没有相关的source/timer/observer
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    //通知Observers:RunLoop即将进入loop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    //真正的RunLoop运行方法:__CFRunLoopRun
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    //通知Observers: 退出runloop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

再看一下__CFRunLoopRun的代码(这里的代码很长,只贴关键性的代码)

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm,
                              CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
    do { //通知观察者,即将处理Timer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers)
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //通知观察者,即将处理Source
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //处理加入的Block __CFRunLoopDoBlocks(rl, rlm);
        //处理Source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm,
                                                              stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        //如果被MachPort唤醒,跳转到handle_msg
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer),
                                       &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        //通知Observer,即将休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting))
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        //调 mach_msg 等待接受 mach_port 的消息,线程将进 休眠,直到被唤醒 msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer),
                                   &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        //通知Observer,即将被唤醒
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        //处理消息 handle_msg:;
        //处理Timer,处理Source...
        //一系列判断,retVal是否为0, 不为0,继续loop
        
    } while (retVal == 0);
    }

我们可以看到Runloop内部其实一直在一个循环里面,通过mach_msg()函数,在用户态和内核态中相互切换,RunLoop调用这个函数去接收消息,如果没有别人发送port消息过来,内核会将线程置于等待状态

RunLoop的面试题分析

以下代码的输出结果

- (void)viewDidLoad {
    [super viewDidLoad];
    NSInteger number = 1;
    NSLog(@"%zd", number);
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        [self performSelector:@selector(printString) withObject:nil afterDelay:0];
    });
    
    number = 3;
    NSLog(@"%zd", number);
}

- (void)printString {
    NSLog(@"sdasdas");
}

结果:1 3

NSObject的performSelecter:afterDelay:performSelector:onThread:被调用时,实际添加了一个Timer到当前线程的RunLoop中,如果该线程没有RunLoop则不会执行这个方法。

AutoReleasePool在什么么时候释放?

App启动后,主线程的RunLoop被创建了两个Observer,分别在kCFRunLoopEntry进入的时候和kCFRunLoopBeforeWaiting即将休眠的时候执行回调。
在进入时,会调用_objc_autoreleasePoolPush()创建自动释放池。
在即将休眠时,会先调用_objc_autoreleasePoolPop(),接着再调用_objc_autoreleasePoolPush() ,释放旧池,创建新池。
也就是说:通常在主线程的代码已经被RunLoop自动创建的AutoReleasePool包裹着,因此不会出现内存泄漏。当然,我们也可以创建自己的自动释放池干预某些对象的释放时机,以解决一些内存峰值的问题。

两个线程如何实现交替打印

思路:在A线程的RunLoop即将休眠kCFRunLoopBeforeWaiting的时候,主动去唤醒B线程的RunLoop,在B线程的RunLoop即将进入休眠的时候,主动唤醒A。代码大致如下:

@interface ViewController ()

@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Run) object:nil];
    self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Run) object:nil];
   
    [self.thread1 start];
    [self.thread2 start];
}

- (void)thread1Run {
    //添加Observer用来唤醒其他线程的RunLoop
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        if (activity == kCFRunLoopBeforeWaiting) {
            NSLog(@"1");
            [self performSelector:@selector(weakUpThread) onThread:self.thread2 withObject:nil waitUntilDone:YES];
        }
    });
    
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);
    
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}

- (void)thread2Run {
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        if (activity == kCFRunLoopBeforeWaiting) {
            NSLog(@"2");
            [self performSelector:@selector(weakUpThread) onThread:self.thread1 withObject:nil waitUntilDone:NO];
        }
    });
    
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);
    
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
}

- (void)weakUpThread {
    CFRunLoopWakeUp(CFRunLoopGetCurrent());
}
上一篇下一篇

猜你喜欢

热点阅读