iOS内存管理10 -- RunLoop运行循环

RunLoop的基本概念

  • RunLoop运行循环,其本质是一个 do-while循环,与普通的while循环是有区别的,普通的while循环会导致CPU进入忙等待状态,即一直消耗CPU,而RunLoop则不会,RunLoop是一种闲等待,无事件处理时会进入休眠状态,不会消耗CPU资源

RunLoop的基本作用

  • 保证应用程序能持续稳定的运行而不退出,例如我们的在app启动之后,默认会开启一个主运行循环RunLoop,其能让app持续稳定运行,不会中途退出,除非用户手动杀掉app进程;
  • 处理App中的各种事件(触摸、定时器、performSelector),,也就是说RunLoop是一个事件处理循环;
  • 节省CPU资源,提升应用程序的性能,即当没有事件处理时会进入休眠状态,不会消耗CPU资源;

RunLoop的源码分析

  • OSX/iOS 系统中,提供了两个这样的对象:NSRunLoopCFRunLoopRef
    CFRunLoopRef是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的,NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的;
  • CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看
  • RunLoop不能手动创建,只能获取,被动的创建,下面提供了获取主线程RunLoop与获取当前正在运行RunLoop函数的源码:
//获取主线程RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_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();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
  • 最终都会进入CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)函数,参数是当前线程;
//全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //pthread_t为空 默认设置成main线程
    if (pthread_equal(t, kNilPthreadT)) {
       t = pthread_main_thread_np();
    }
    //保证线程安全
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        //第一次进入时,初始化全局Dic,并先为主线程创建一个RunLoop。
        __CFUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //创建主运行循环
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //将线程和RunLoop 以键值对的形式 存储在全局字典中
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    //获取其他线程的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
        //RunLoop不存在,创建一个新的RunLoop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //线程与RunLoop 键值对存在在全局字典中
            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;
}
  • 易知线程与RunLoop是以键值对的形式存储在一个全局字典(__CFRunLoops)中的,所以线程与RunLoop之间是一一对应的关系;
  • 全局字典(__CFRunLoops)为空,即第一次初始化时,会创建主线程循环,并将主线程和主RunLoop 以键值对的形式 存储在__CFRunLoops中;
  • 接下来,进入获取其他线程的RunLoop,若RunLoop不存在,会创建RunLoop,执行__CFRunLoopCreate(t)函数;
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    if (NULL == loop) {
       return NULL;
    }
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    //RunLoop的属性设置
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}
  • 创建RunLoop对象,并进行相关属性的设置;
RunLoop的底层结构
  • RunLoop的结构体定义如下:
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
  • _currentMode:表示RunLoop当前的模式,是CFRunLoopModeRef结构体类型;
  • _modes:表示RunLoop的模式集合,说明RunLoop可以有多种不同的模式;
  • _commonModeItems:是组的Items集合;
RunLoop的模式CFRunLoopModeRef
  • 其底层结构体如下:
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0; //事件源0
    CFMutableSetRef _sources1; //事件源1
    CFMutableArrayRef _observers; //观察者
    CFMutableArrayRef _timers; //定时器
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
  • 可以看到__CFRunLoopMode中包含以下主要部分:
  • 事件源:_sources0_sources1
  • 观察者源:_observers
  • 定时器源:_timers
  • RunLoop拥有多种不同的Mode,每个Mode中包含Source/Timer/Observer; Source/Timer/Observer又称为一组Item,子线程的RunLoop中必须要有Item的存在,也就是说必须要有源,有任务事件处理,否则RunLoop会无事可做,就会自动退出
RunLoop_Mode.png
  • RunLoopMode的类型有如下:
    • RunLoopMode在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有 NSDefaultRunLoopModeNSRunLoopCommonModes,NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopModeNSEventTrackingRunLoopMode
    • NSDefaultRunLoopMode:App的默认 Mode,通常主线程是在这个 Mode 下运行的;
    • NSConnectionReplyMode
    • NSModalPanelRunLoopMode
    • NSEventTrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响;
    • NSRunLoopCommonModes:伪模式,灵活性更好;
RunLoop的事件源_sources0与_sources1 -- CFRunLoopSourceRef
  • _sources0_sources1属于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;
};
  • CFRunLoopSourceRef 是事件产生的地方,可以看到Source源有两种类型:Source0Source1
  • Source0 属于CFRunLoopSourceContext类型:其只包含了一个回调(函数指针),它并不能主动触发事件,使用时你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件,主要包括触摸事件PerformSelectors事件
  • 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);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;
  • 代码验证如下:
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
    CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    NSLog(@"点击");
}
@end
  • 点击屏幕,控制台输入bt,打印函数调用堆栈如下:
Snip20210528_45.png
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
    CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
    
    [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
}

- (void)test{
    NSLog(@"test");
}

@end
  • 控制台输入bt,打印函数调用堆栈如下:
Snip20210528_47.png
  • Source1 属于CFRunLoopSourceContext1 类型:其包含了一个 mach_port 和一个回调(函数指针),被用于基于Port的线程间通信
  • CFRunLoopSourceContext1结构体如下:
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_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;
RunLoop的定时器源timers -- CFRunLoopTimerRef
  • RunLoop的定时器timers的类型为CFRunLoopTimerRef,其底层结构体如下所示:
struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;       /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */ 
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
  • 其是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用,其包含一个时间长度一个回调(函数指针),当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当到指定时间点时,RunLoop会被唤醒以执行那个回调(函数指针);

  • 代码测试:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
        NSLog(@"NSTimer ---- timer调用了");
    }];
}
@end
  • 控制台输入bt,打印函数调用堆栈如下:
Snip20210528_48.png
RunLoop的观察者源Observers -- CFRunLoopObserverRef
  • RunLoop的观察者Observers属于CFRunLoopObserverRef类型,其底层结构体如下:
struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;      /* immutable */
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable */
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};
  • 每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接收到这个变化,可以观测的时间点状态有以下几个:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),       //即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),//即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2),//即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),         //即将退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • 测试代码:
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
     //创建监听者
     /*
     第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
     第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
     第三个参数 Boolean repeats:YES:持续监听 NO:不持续
     第四个参数 CFIndex order:优先级,一般填0即可
     第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要处理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要处理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒来了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
            default:
                break;
        }
    });
    
    // 给RunLoop添加监听者
    /*
     第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
     第二个参数 CFRunLoopObserverRef observer 监听者
     第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     /*
     CF的内存管理(Core Foundation)
     凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
     GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
     */
    CFRelease(observer);
}
@end
  • 点击屏幕,控制台打印结果如下:
Snip20210528_51.png
  • 可以看到Observer确实能监听到RunLoop的运行状态,包括唤醒,休息,以及处理各种事件;
Items源所触发的事件回调类型
  • 上面已经验证了Source0,定时器Timer与observer源的事件回调;
Snip20210528_49.png
  • block调用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • 调用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • 响应source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • 响应source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
RunLoop的运行
  • RunLoop执行,底层调用如下:
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  • 内部调用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
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    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);
    
    ////进入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    ////通知 Observers: RunLoop 即将退出loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}
  • __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry):通知Observers,RunLoop即将进入循环,此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush()
  • __CFRunLoopRun:核心函数,进入RunLoop运行循环;
  • __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit):通知Observers,RunLoop即将退出循环,此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
  • 进入__CFRunLoopRun源码,由于这部分代码较多,于是这里用伪代码代替。其主要逻辑是根据不同的事件源进行不同的处理,当RunLoop休眠时,可以通过相应的事件唤醒RunLoop;
//核心函数
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
    
    //通过GCD开启一个定时器,然后开始跑圈
    dispatch_source_t timeout_timer = NULL;
    ...
    dispatch_resume(timeout_timer);
    
    int32_t retVal = 0;
    
    //处理事务,即处理items
    do {
        
        // 通知 Observers: 即将处理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // 通知 Observers: 即将处理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        // 处理sources0返回为YES
        if (sourceHandledThisLoop) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 判断有无端口消息(Source1)
        if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
            // 处理消息
            goto handle_msg;
        }
        
        // 通知 Observers: 即将进入休眠 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        // 等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知 Observers: 被唤醒,结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        
    handle_msg:
        if (被timer唤醒) {
            // 处理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }else if (被GCD唤醒){
            // 处理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        }else if (被source1唤醒){
            // 被Source1唤醒,处理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        // 处理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;//处理源
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;//超时
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;//停止
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;//停止
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;//结束
        }
    }while (0 == retVal);
    return retVal;
}
  • 可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环,当调用 CFRunLoopRun()函数时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会返回;
image.png
RunLoop与AutoreleasePool之间的关系
  • App启动后,苹果在主线程 RunLoop 里注册了两个Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
  • 第一个Observer源 监听RunLoop的进入状态,其回调会调用 _objc_autoreleasePoolPush() 创建自动释放池,此回调用执行的优先级是最高的,保证自动释放池的创建 在其他所有回调执行之前;
  • 第二个Observer源监听RunLoop进入休眠状态RunLoop的退出状态,RunLoop进入休眠状态时 回调执行 释放旧的自动释放池并创建新的自动释放池,RunLoop的退出状态时 回调执行释放自动释放池,此时回调执行的优先级是最低的,保证自动释放池的释放,在其他所有回调执行之后;
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的,这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了;
  • 调试,在工程中加入符号断点_wrapRunLoopWithAutoreleasePoolHandler,然后运行工程,我运行的Xcode版本为11.3.1;
Snip20210528_55.png
Snip20210528_56.png
面试题一:ScrollView发生滚动的时候,NSTimer为什么会停止工作?
  • 当ScrollView发生滚动的时候,主线程的RunLoop会切换到UITrackingRunLoopModel模式下,而NSTimer默认是添加到UIDefaultRunLoopModel下的,所以NSTimer不会执行回调,当ScrollView停止滚动是主线程的RunLoop又会切换到UIDefaultRunLoopModel模式下,此时NSTimer会恢复工作,执行自己的回调方法,为了保证NSTimer在UITrackingRunLoopModel模式下也能工作,可将主线程RunLoop的model模式设置成组合模式NSRunLoopCommonModes;
面试题二:RunLoop的实际应用
  • 应用一:线程保活,一般情况下,当线程执行完任务之后就会自动退出销毁,如下所示:
#import "ViewController.h"
#import "YYThread.h"

@interface ViewController ()

@property(nonatomic,strong)YYThread *thread;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[YYThread alloc]initWithTarget:self selector:@selector(callBack) object:nil];
    [self.thread start];
}

- (void)callBack {
    NSLog(@"%s %@ -- 开始",__func__,[NSThread currentThread]);
    NSLog(@"%s -- 结束",__func__);
}
@end
  • 自定义线程YYThread在执行完callBack方法之后,就会退出销毁,下面使用RunLoop保活YYThread线程,并能执行其他任务,实现如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[YYThread alloc]initWithTarget:self selector:@selector(callBack) object:nil];
    [self.thread start];
}

- (void)callBack {
    NSLog(@"%s %@ -- 开始",__func__,[NSThread currentThread]);
    //实现线程保活
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%s -- 结束",__func__);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    //点击让子线程 继续执行其他任务
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)test {
    NSLog(@"%s %@ -- 开始",__func__,[NSThread currentThread]);
}
  • 点击屏幕,让YYThread线程继续执行test函数,说明RunLoop确实能让YYThread线程存活,继续执行其他任务;

  • 应用二:监听App的卡顿,原理是子线程向主线程发送消息,若主线程未在指定的时间内作出应答,说明主线程在执行耗时任务,存在App的卡顿,代码实现见iOS性能优化01 -- 卡顿优化

  • 应用三:优化tableView的滚动,利用RunLoop的Model特性,可将更新UI的操作放到
    UIDefaultRunLoopModel模式中,这样tableView在滚动时不会因为更新UI而影响到平滑的滚动效果,网络图片加载完成,更新UI到ImageView时,使用UIDefaultRunLoopModel模式,代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIImage *networkImage = nil;
    [self.icon performSelector:@selector(setImage:) withObject:networkImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
}

你可能感兴趣的:(iOS内存管理10 -- RunLoop运行循环)