iOS底层探索28、Run Loop

  • 关于 Run Loop 的文章在之前的博客已做过分析,这里将文章转移到简书,并进行一些信息补充。

RunLoop 源码地址

RunLoop 官方文档 Threading Programming Guide

image

何时要用 run loop

  • 应用程序主线程的 run loop 是基础结构的关键部分。因此,应用程序框架提供了运行主应用程序循环的代码,并自动启动该循环。
  • 对于辅助线程,您需要决定是否需要运行循环,如果需要,自己配置并启动它。
  • 不需要在所有情况下启动线程的运行循环。例如,如果您使用一个线程来执行一些长时间运行和预定的任务,您可能可以避免启动运行循环。
  • 运行循环适用于需要与线程进行更多交互性的情况。例如,如果你计划做以下任何一件事,你需要启动一个运行循环:
    1.使用端口或自定义输入源与其他线程通信。- 例:线程间 NSPort 通讯 必须加入 runloop 才可正常通讯
    2.在线程上使用计时器
    3.在Cocoa应用程序中使用任何performSelector方法。
    4.保持线程运行以执行周期性任务

一、Runloop 介绍

1、RunLoop 是什么?

运行循环是与线程相关联的基础设施的一部分。运行循环是一个事件处理循环,用于调度工作和协调接收传入事件。

运行循环的目的是让线程在有工作要做时保持忙碌,而在没有工作要做时让线程休眠
运行循环管理不是完全自动的。您仍然必须设计线程的代码,以在适当的时间启动运行循环并响应传入的事件。Cocoa 和 Core Foundation 都提供了run loop对象来帮助你配置和管理线程的 run loop。您的应用程序不需要显式地创建这些对象;

每个线程,包括应用程序的主线程,都有一个关联的run loop对象。然而,只有辅助线程需要显式地运行它们的run循环。作为应用程序启动过程的一部分,应用程序框架会在主线程(main)上自动设置并运行run循环。

简单来说即:runloop 是一个对象,它提供了一个入口函数,内部是一个 do...while... 循环(并非通常意义上do...while...),循环内 进行事件处理。

image

2、RunLoop 作用

runloop 的结构和 source:

image

保持程序不死。--> main 函数

处理 APP 中的各类事件:触摸交互、定时器、performSelect、端口交互... ...

节省 CPU 资源 --> 有工作时唤醒,完成/无任务时休眠

3、RunLoop 应用

block / timer / 响应 source0/source1 / GCD 主队列 / observe 源

// GCD
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline)); static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
    _dispatch_main_queue_callback_4CF(msg);
    asm __volatile__(""); // thwart tail-call optimization
} // observer
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { if (func) {
        func(observer, activity, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
} // timer
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) { if (func) {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
} // block
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) { if (block) {
        block();
    }
    asm __volatile__(""); // thwart tail-call optimization
} // source0
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) { if (perform) {
        perform(info);
    }
    asm __volatile__(""); // thwart tail-call optimization
} // source1
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__( #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                                                                       void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
                                                                       mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply, #else
                                                                       void (*perform)(void *), #endif
                                                                       void *info) { if (perform) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info); #else perform(info); #endif }
    asm __volatile__(""); // thwart tail-call optimization
}

4、RunLoop 和线程的关系

4.1)线程和 runloop 一对一(key - value)关系

runloop 源码中可得出 runloop 和 thread 的 dictionary 的key - value关系;

--> 从简介“每个线程,包括应用程序的主线程,都有一个关联的run loop对象”也可得知。

// should only be called by Foundation // t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock); if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);

        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); // 创建 runloop: __CFRunLoopCreate()
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // 存储 runloop: CFDictionarySetValue()
 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
  // 获取 runloop: CFDictionaryGetValue() 
    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) {
            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;
}

4.2)示例

NSThread *thread = [[NSThread alloc] initWithBlock:^{ // 任务
      NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]); // 定时器任务
      [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
          NSLog(@"子线程 timer -- hello word");
      }];
}];

thread.name = @"my_thread";
[thread start]; </pre>

执行结果:

image

并没有执行 timer,为何???

子线程中 runloop 并未开启,默认是关闭不开启的

我们在 thread block 中加入代码:[[NSRunLoop currentRunLoop] run];

再次执行:timer 执行了

image

如何停止 runloop 呢?

1. 设置 timeout;

2.直接结束对应的线程

通过某响应事件(触摸/按钮等)触发,代码如下:

NSThread *thread = [[NSThread alloc] initWithBlock:^{ // 任务
    NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]); // 定时器任务
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"子线程 timer -- hello word"); // 事件响应 isStoping=YES --> 退出线程 --> runloop 停止
        if (self.isStopping) {
            [NSThread exit];
        }
    }];
    [[NSRunLoop currentRunLoop] run];
}];

另:关于 NSTimer 定时器,是基于 RunLoop 实现的(下面会详细分析)

当我们启用 NSTimer 时,并不是按照时间间隔进行循环调用的。在定时器注册到 runloop 中后,runloop 会设置一个个的时间点进行调用,比如10、20、30...。如果错过了某个时间点,定时器是不会延时调用的,他会直接等待下一个时间点调用,so 定时器并不是精准的。

4.2.1)子线程中使用某些延时函数和选择器时,也必须手动开启 runloop,如下方法

/****************     Delayed perform     ******************/

@interface NSObject (NSDelayedPerforming) - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes; - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget; @end

@interface NSRunLoop (NSOrderedPerform) - (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes; - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg; - (void)cancelPerformSelectorsWithTarget:(id)target; @end

二、RunLoop 的5个类

A run loop object provides the main interface for adding input sources, timers, and run-loop observers to your run loop and then running it. Every thread has a single run loop object associated with it. In Cocoa, this object is an instance of the NSRunLoop class. In a low-level application, it is a pointer to a CFRunLoopRef opaque type.

-- runloop 对象提供了一个主接口(入口),用于向运行循环添加输入源、计时器和运行循环观察器,然后运行它。每个线程都有一个与之关联的运行循环对象。在Cocoa中,这个对象是NSRunLoop 类的一个实例。在低级应用程序中,它是一个指向 CFRunLoopRef 不透明类型的指针。

RunLoop 的5个类:

image

RunLoop 关系图:

image

1、CFRunLoopRef

<-- typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;

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;
    CFTypeRef _counterpart;
};

2、CFRunLoopModeRef <-- __CFRunLoopMode 结构体

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct **__CFRunLoopMode** {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */ CFStringRef _name;     // mode name 例如:KCFRunLoopDefaultMode
    Boolean _stopped;
    char _padding[3];
    **CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    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 */ };

RunLoop 的 mode

A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified. Each time you run your run loop, you specify (either explicitly or implicitly) a particular “mode” in which to run. During that pass of the run loop, only sources associated with that mode are monitored and allowed to deliver their events. (Similarly, only observers associated with that mode are notified of the run loop’s progress.) Sources associated with other modes hold on to any new events until subsequent passes through the loop in the appropriate mode.

==》 runloop mode 是要监视的输入源和计时器的集合,以及要通知的运行循环观察者的集合。每次运行 runloop 时,都(显式或隐式)指定要在其中运行的特定“mode”。在 runloop 的传递过程中,只监视与该模式关联的源,并允许交付它们的事件。(类似地,只有与该模式关联的观察者才会被告知运行循环的进度。)与其他模式相关联的源 会保留任何新事件,直到后续事件以适当的模式通过循环。

mode 类型:

image

runloop mode 关系图:

image

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

mode item(CFRunLoopTimerRef / CFRunLoopSourceRef / CFRunLoopObserverRef)

2.1)CFRunLoopTimerRef <-- __CFRunLoopTimer

基于时间的触发器,当其加入到 RunLoop 时,RunLoop 会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

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 */ };
image

以定时器(其底部是一个 CFRunLoopTimerRef)为例,通过源码探索 item mode 关系流程。

创建个 timer 并将其添加到 runloop 的 mode 中:

- (void)timerTest {
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"timer in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

流程:

创建定时器 --> timer 加入到 runloop 中的 mode 中(timer 加到 items 中) --> 即:CFSetAddValue(rl->_commonModeItems, rlt) --> runloop run --> 函数 CFRunLoopRun() 执行 --> CFRunLoopRunSpecific() 中 __CFRunLoopRun --> __CFRunLoopDoBlocks(rl, rlm) --> while 循环所有items --> mode 判断(doit CFEqual/CFSetContainsValue) --> 执行 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block) --> block() --> function

(item 依赖于 mode mode 依赖于 runloop)

CFRunLoop 历程部分源码(runloop完整源码下载见文章顶部)

1. CFRunLoopAddTimer

void **CFRunLoopAddTimer**(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return; if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl); if (modeName == kCFRunLoopCommonModes) {
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        } // 将 timer 加入 commonModeItems 里面
        **CFSetAddValue(rl->_commonModeItems, rlt);** if (NULL != set) {// 循环,一直加,知道set为null
            CFTypeRef context[2] = {rl, rlt}; /* add new item to all common-modes */
            // __CFRunLoopAddItemToCommonModes  执行 CFRunLoopAddTimer
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
        ... ...
    }
    __CFRunLoopUnlock(rl);
}

2. CFRunLoopRunSpecific()

result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

3. __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)

__CFRunLoopDoBlocks(rl, rlm);

4. doBlock

static Boolean **__CFRunLoopDoBlocks**(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
    if (!rl->_blocks_head) return false; if (!rlm || !rlm->_name) return false;
    Boolean did = false; struct _block_item *head = rl->_blocks_head; struct _block_item *tail = rl->_blocks_tail;
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl); struct _block_item *prev = NULL; struct _block_item *item = head; // 循环所有 item
    while (item) { struct _block_item *curr = item;
        item = item->_next;
        Boolean doit = false; if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) { // CFEqual(curr->_mode, curMode): 当前的_mode 和 传过来的curMode 是否相同 // 当前_mode是kCFRunLoopCommonModes && 传来的curMode是rlmodes里的一员 // doit
            doit = **CFEqual**(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && **CFSetContainsValue**(commonModes, curMode));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } if (!doit) prev = curr; if (doit) { if (prev) prev->_next = item; if (curr == head) head = item; if (curr == tail) tail = prev; void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr); if (doit) { /* block 回调
                **static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
                if (block) {
                block();
                }
                asm __volatile__(""); // thwart tail-call optimization
                }** */ **__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block)**;
                did = true;
            }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
 }
    }

    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm); if (head) {
        tail->_next = rl->_blocks_head;
        rl->_blocks_head = head; if (!rl->_blocks_tail) rl->_blocks_tail = tail;
    } return did;
}

tip:通过上述流程,我们亦可得出定时器不准的具体原因:

定时器所属 mode 是 kCFRunLoopDefaultMode,当页面进行滑动or其他操作时,mode 是 UITrackingRunLoopMode, mode 来回切换 --> 在“流程4”中:当页面滑动时 mode 不同,doit 为false 后续 block 不执行,回调便不走了 --> 定时器停了,直到下次 loop 点继续。

2.2)CFRunLoopSourceRef <-- __CFRunLoopSource

事件产生的地方,包含 source0 source1。

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;            /* immutable */ CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;    /* source0 immutable, except invalidation */ CFRunLoopSourceContext1 version1;    /* source1 immutable, except invalidation */ } _context;
};

image

2.3)CFRunLoopObserverRef <-- __CFRunLoopObserver

观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

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 */ };
image

可观测的 时间点 们:

/* 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 };

一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。

如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

000)示例 - 简单实现 timer 和 事件监听:

主要代码:

#pragma mark - timer -
- (void)cfTimerDemo {

    CFRunLoopTimerContext context = { 0,
        ((__bridge void *)self),
        NULL,
        NULL,
        NULL
    };
    CFRunLoopRef rlp = CFRunLoopGetCurrent(); /**
     参数一:用于分配对象的内存
     参数二:在什么是触发 (距离现在)
     参数三:每隔多少时间触发一次
     参数四:未来参数
     参数五:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
     参数六:回调,比如触发事件,我就会来到这里
     参数七:上下文记录信息 */ CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, myRunLoopTimerCallBack, &context);
    CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
} void myRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info){
    NSLog(@"%@---%@",timer,info);
} #pragma mark - observer -
- (void)cfObseverDemo {

    CFRunLoopObserverContext context = { 0,
        ((__bridge void *)self),
        NULL,
        NULL,
        NULL
    };
    CFRunLoopRef rlp = CFRunLoopGetCurrent(); /**
     参数一:用于分配对象的内存
     参数二:你关注的事件
          kCFRunLoopEntry=(1<<0),
          kCFRunLoopBeforeTimers=(1<<1),
          kCFRunLoopBeforeSources=(1<<2),
          kCFRunLoopBeforeWaiting=(1<<5),
          kCFRunLoopAfterWaiting=(1<<6),
          kCFRunLoopExit=(1<<7),
          kCFRunLoopAllActivities=0x0FFFFFFFU
     参数三:CFRunLoopObserver是否循环调用
     参数四:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
     参数五:回调,比如触发事件,我就会来到这里
     参数六:上下文记录信息 */ CFRunLoopObserverRef observerRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, myRunLoopObserverCallBack, &context);
    CFRunLoopAddObserver(rlp, observerRef, kCFRunLoopDefaultMode);
} void myRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    NSLog(@"%lu-%@",activity,info);
}

三、RunLoop 内部逻辑

苹果文档: The Run Loop Sequence of Events

image

RunLoop 核心流程源码如下,比较长可以粘下来再看.

 1 /* rl, rlm are locked on entrance and exit */
 2 /**
 3  *  运行run loop
 4  *
 5  *  @param rl                         运行的RunLoop对象
 6  *  @param rlm                       运行的mode
 7  *  @param seconds                run loop超时时间
 8  *  @param stopAfterHandle     true:run loop处理完事件就退出  false:一直运行直到超时或者被手动终止
 9  *  @param previousMode        上一次运行的mode
 10  *
 11  *  @return 返回4种状态
 12  */
 13 static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { 14     
 15     // 获取系统启动后的CPU运行时间,用于控制超时时间
 16     uint64_t startTSR = mach_absolute_time(); 17     
 18     // 判断当前runloop的状态是否关闭
 19     if (__CFRunLoopIsStopped(rl)) { 20         __CFRunLoopUnsetStopped(rl);
 21         return kCFRunLoopRunStopped; 22     } else if (rlm->_stopped) {
 23         return kCFRunLoopRunStopped; 24         rlm->_stopped = false;
 25     }
 26     
 27     // mach端口,在内核中,消息在端口之间传递。 初始为0
 28     mach_port_name_t dispatchPort = MACH_PORT_NULL; 29     // 判断是否为主线程
 30     Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ))); 31     // 如果在主线程 && runloop是主线程的runloop && 该mode是commonMode,则给mach端口赋值为主线程收发消息的端口
 32     if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF(); 33     
 34 #if USE_DISPATCH_SOURCE_FOR_TIMERS
 35     mach_port_name_t modeQueuePort = MACH_PORT_NULL; 36     if (rlm->_queue) {
 37         // mode赋值为dispatch端口_dispatch_runloop_root_queue_perform_4CF
 38         modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
 39         if (!modeQueuePort) {
 40             CRASH("Unable to get port for run loop mode queue (%d)", -1);
 41         }
 42     }
 43 #endif
 44     
 45     dispatch_source_t timeout_timer = NULL; 46     struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
 47     if (seconds <= 0.0) { // instant timeout
 48         seconds = 0.0;
 49         timeout_context->termTSR = 0ULL; 50     } else if (seconds <= TIMER_INTERVAL_LIMIT) { 51         // seconds为超时时间,超时时执行__CFRunLoopTimeout函数
 52         dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT); 53         timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
 54         dispatch_retain(timeout_timer);
 55         timeout_context->ds = timeout_timer; 56         timeout_context->rl = (CFRunLoopRef)CFRetain(rl); 57         timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds); 58         dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
 59         dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
 60         dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
 61         uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL); 62         dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
 63         dispatch_resume(timeout_timer);
 64     } else { // infinite timeout 65         // 永不超时
 66         seconds = 9999999999.0;
 67         timeout_context->termTSR = UINT64_MAX; 68     }
 69     
 70     // 标志位默认为true
 71     Boolean didDispatchPortLastTime = true;
 72     //记录最后runloop状态,用于return
 73     int32_t retVal = 0;
 74     do { 75         // 初始化一个存放内核消息的缓冲池
 76         uint8_t msg_buffer[3 * 1024];
 77 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
 78         mach_msg_header_t *msg = NULL; 79         mach_port_t livePort = MACH_PORT_NULL; 80 #elif DEPLOYMENT_TARGET_WINDOWS
 81         HANDLE livePort = NULL; 82         Boolean windowsMessageReceived = false;
 83 #endif
 84         // 取所有需要监听的port
 85         __CFPortSet waitSet = rlm->_portSet;
 86         
 87         // 设置RunLoop为可以被唤醒状态
 88         __CFRunLoopUnsetIgnoreWakeUps(rl);
 89         
 90         /// 2\. 通知 Observers: RunLoop 即将触发 Timer 回调。
 91         if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 92         if (rlm->_observerMask & kCFRunLoopBeforeSources) 93             /// 3\. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
 94             __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
 95         
 96         /// 执行被加入的block
 97         __CFRunLoopDoBlocks(rl, rlm);
 98         /// 4\. RunLoop 触发 Source0 (非port) 回调。
 99         Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); 100         if (sourceHandledThisLoop) { 101             /// 执行被加入的block
102 __CFRunLoopDoBlocks(rl, rlm); 103 } 104         
105         // 如果没有 Sources0 事件处理 并且 没有超时,poll为false 106         // 如果有 Sources0 事件处理 或者 超时,poll都为true
107         Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); 108         // 第一次do..whil循环不会走该分支,因为didDispatchPortLastTime初始化是true
109         if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { 110 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
111             // 从缓冲区读取消息
112             msg = (mach_msg_header_t *)msg_buffer; 113             /// 5\. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
114             if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) { 115                 // 如果接收到了消息的话,前往第9步开始处理msg
116                 goto handle_msg; 117 } 118 #elif DEPLOYMENT_TARGET_WINDOWS
119             if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { 120                 goto handle_msg; 121 } 122 #endif
123 } 124         
125         didDispatchPortLastTime = false; 126         /// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
127         if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); 128         // 设置RunLoop为休眠状态
129 __CFRunLoopSetSleeping(rl); 130         // do not do any user callouts after this point (after notifying of sleeping) 131         
132         // Must push the local-to-this-activation ports in on every loop 133         // iteration, as this mode could be run re-entrantly and we don't 134         // want these ports to get serviced.
135         
136 __CFPortSetInsert(dispatchPort, waitSet); 137         
138 __CFRunLoopModeUnlock(rlm); 139 __CFRunLoopUnlock(rl); 140         
141 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
142 #if USE_DISPATCH_SOURCE_FOR_TIMERS
143         
144         // 这里有个内循环,用于接收等待端口的消息 145         // 进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
146         do { 147             if (kCFUseCollectableAllocator) { 148                 objc_clear_stack(0); 149                 memset(msg_buffer, 0, sizeof(msg_buffer)); 150 } 151             
152             msg = (mach_msg_header_t *)msg_buffer; 153             /// 7.接收 waitSet 端口的消息
154             __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); 155             // 收到消息之后,livePort的值为msg->msgh_local_port,
156             if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { 157                 // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
158                 while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue)); 159                 if (rlm->_timerFired) { 160                     // Leave livePort as the queue port, and service timers below
161                     rlm->_timerFired = false; 162                     break; 163                 } else { 164                     if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); 165 } 166             } else { 167                 // Go ahead and leave the inner loop.
168                 break; 169 } 170         } while (1); 171 #else
172         if (kCFUseCollectableAllocator) { 173             objc_clear_stack(0); 174             memset(msg_buffer, 0, sizeof(msg_buffer)); 175 } 176         msg = (mach_msg_header_t *)msg_buffer; 177         /// 7\. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。 178         /// • 一个基于 port 的Source 的事件。 179         /// • 一个 Timer 到时间了 180         /// • RunLoop 自身的超时时间到了 181         /// • 被其他什么调用者手动唤醒
182         __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); 183 #endif
184         
185         
186 #elif DEPLOYMENT_TARGET_WINDOWS
187         // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
188         __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived); 189 #endif
190         
191 __CFRunLoopLock(rl); 192 __CFRunLoopModeLock(rlm); 193         
194         // Must remove the local-to-this-activation ports in on every loop 195         // iteration, as this mode could be run re-entrantly and we don't 196         // want these ports to get serviced. Also, we don't want them left 197         // in there if this function returns.
198         
199 __CFPortSetRemove(dispatchPort, waitSet); 200         
201 __CFRunLoopSetIgnoreWakeUps(rl); 202         
203         // user callouts now OK again 204         //取消runloop的休眠状态
205 __CFRunLoopUnsetSleeping(rl); 206         /// 8\. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
207         if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); 208         
209         /// 收到消息,处理消息。
210 handle_msg:; 211 __CFRunLoopSetIgnoreWakeUps(rl); 212         
213 #if DEPLOYMENT_TARGET_WINDOWS
214         if (windowsMessageReceived) { 215             // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
216 __CFRunLoopModeUnlock(rlm); 217 __CFRunLoopUnlock(rl); 218             
219             if (rlm->_msgPump) { 220                 rlm->_msgPump(); 221             } else { 222 MSG msg; 223                 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) { 224                     TranslateMessage(&msg); 225                     DispatchMessage(&msg); 226 } 227 } 228             
229 __CFRunLoopLock(rl); 230 __CFRunLoopModeLock(rlm); 231             sourceHandledThisLoop = true; 232             
233             // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced 234             // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later. 235             // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
236 __CFRunLoopSetSleeping(rl); 237 __CFRunLoopModeUnlock(rlm); 238 __CFRunLoopUnlock(rl); 239             
240             __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL); 241             
242 __CFRunLoopLock(rl); 243 __CFRunLoopModeLock(rlm); 244 __CFRunLoopUnsetSleeping(rl); 245             // If we have a new live port then it will be handled below as normal
246 } 247         
248         
249 #endif
250         if (MACH_PORT_NULL == livePort) { 251 CFRUNLOOP_WAKEUP_FOR_NOTHING(); 252             // handle nothing
253         } else if (livePort == rl->_wakeUpPort) { 254 CFRUNLOOP_WAKEUP_FOR_WAKEUP(); 255             // do nothing on Mac OS
256 #if DEPLOYMENT_TARGET_WINDOWS
257             // Always reset the wake up port, or risk spinning forever
258             ResetEvent(rl->_wakeUpPort); 259 #endif
260 } 261 #if USE_DISPATCH_SOURCE_FOR_TIMERS
262         else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { 263 CFRUNLOOP_WAKEUP_FOR_TIMER(); 264             /// 9\. 处理 唤醒时收到的消息 265             /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
266             if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { 267                 // Re-arm the next timer, because we apparently fired early
268 __CFArmNextTimerInMode(rlm, rl); 269 } 270 } 271 #endif
272 #if USE_MK_TIMER_TOO
273         else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { 274 CFRUNLOOP_WAKEUP_FOR_TIMER(); 275             // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled. 276             // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
277             if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { 278                 // Re-arm the next timer
279 __CFArmNextTimerInMode(rlm, rl); 280 } 281 } 282 #endif
283         /// 9.2 如果有dispatch到main_queue的block,执行block
284         else if (livePort == dispatchPort) { 285 CFRUNLOOP_WAKEUP_FOR_DISPATCH(); 286 __CFRunLoopModeUnlock(rlm); 287 __CFRunLoopUnlock(rl); 288             _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL); 289 #if DEPLOYMENT_TARGET_WINDOWS
290             void *msg = 0; 291 #endif
292 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 293             _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL); 294 __CFRunLoopLock(rl); 295 __CFRunLoopModeLock(rlm); 296             sourceHandledThisLoop = true; 297             didDispatchPortLastTime = true; 298         } else { 299             /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
300 CFRUNLOOP_WAKEUP_FOR_SOURCE(); 301             // Despite the name, this works for windows handles as well
302             CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); 303             if (rls) { 304 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
305                 mach_msg_header_t *reply = NULL; 306                 sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; 307                 if (NULL != reply) { 308                     (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); 309 CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); 310 } 311 #elif DEPLOYMENT_TARGET_WINDOWS
312                 sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop; 313 #endif
314 } 315 } 316 #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
317         if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); 318 #endif
319         
320         /// 执行加入到Loop的block
321 __CFRunLoopDoBlocks(rl, rlm); 322         
323         if (sourceHandledThisLoop && stopAfterHandle) { 324             /// 进入loop时参数说处理完事件就返回。
325             retVal = kCFRunLoopRunHandledSource; 326         } else if (timeout_context->termTSR < mach_absolute_time()) { 327             /// 超出传入参数标记的超时时间了
328             retVal = kCFRunLoopRunTimedOut; 329         } else if (__CFRunLoopIsStopped(rl)) { 330             /// 被外部调用者强制停止了
331 __CFRunLoopUnsetStopped(rl); 332             retVal = kCFRunLoopRunStopped; 333         } else if (rlm->_stopped) { 334             /// 自动停止了
335             rlm->_stopped = false; 336             retVal = kCFRunLoopRunStopped; 337         } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { 338             /// source/timer/observer一个都没有了
339             retVal = kCFRunLoopRunFinished; 340 } 341         /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
342     } while (0 == retVal); 343     
344     if (timeout_timer) { 345 dispatch_source_cancel(timeout_timer); 346 dispatch_release(timeout_timer); 347     } else { 348 free(timeout_context); 349 } 350     
351     return retVal; 352 }            

通过这里也可验证上述“runloop 是一个对象,它提供了一个入口函数,其内部是一个 do...while... 循环,循环内 进行事件处理”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • Run Loop是什么 RunLoop顾名思义,是运行循环。它跟线程是一一对应的,每一个线程都有一个RunLoop...
    楚槟夕阅读 2,903评论 0 2
  • 最近这几天研究了下Runloop,下面就来分享一下心得(有不好的地方请帮忙指出来,共同进步,谢谢!!!) 一:前...
    Small_Potato阅读 994评论 1 11
  • 一.NSRunLoop 在Cocoa中,每个线程(NSThread)对象中内部都有一个run loop(NSRun...
    阿拉灯神钉阅读 704评论 0 0
  • 在上一节,简单的说了一下Run Loop相关的知识,这一节将做进一步练习巩固。 每个runloop对象都提供了一些...
    大鹏鸟阅读 1,060评论 0 0
  • RUN Loop是什么? 1。runloop是事件接收和分发机制的一个实现。2。什么时候使用runloop?当需要...
    且行且珍惜_iOS阅读 819评论 1 5