iOS之RunLoop详解与实践

目录

-RunLoop的概念

-RunLoop逻辑与实现

-RunLoop在iOS中运用

-RunLoop实践

-RunLoop的概念

苹果在文档里,是这样定义RunLoop的 :

Run loops are part of the fundamental infrastructure associated with threads.
A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

RunLoop是与线程相关的基础功能,RunLoop是用于调度工作,并协调接收传入事件的事件处理循环。RunLoop的目标是让线程有任务时工作,没有任务处理时休眠。

RunLoop与线程的关系
线程在处理完自己的任务后一般会退出,为了实现线程不退出能够随时处理任务的机制被称为EventLoopnode.js 的事件处理,windows程序的消息循环,iOS、OSX的RunLoop都是这种机制。
线程和RunLoop是一一对应的,关系保存在全局的字典里。
在主线程中,程序启动时,系统默认添加了有kCFRunLoopDefaultModeUITrackingRunLoopMode两个预置ModeRunLoop,保证程序处于等待状态,如果接收到来自触摸事件等,就会执行任务,否则处于休眠中。
线程创建时并没有RunLoop,(主线程除外),RunLoop不能创建,只能主动获取才会有。RunLoop的创建是在第一次获取时,RunLoop的销毁是发生在线程结束时。只能在一个线程中获取自己和主线程的RunLoop
RunLoop的挂起与唤醒堆栈调用

FD7A79D6-A7C8-40D7-B59E-10F36213E8E4.jpg

指定用于唤醒的 mach_port 端口调用 mach_msg 监听唤醒端口,被唤醒前系统内核将这个线程挂起,停留在mach_msg_trap状态。
由另一个线程向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续工作。

一个运行环接收来自两个不同类型的源事件。输入源传递异步事件,通常消息从另一个线程或从不同的应用程序。定时源提供的同步事件,在预定的时间发生的或重复间隔。这两种类型的源的使用应用特定处理例程,当它到达处理该事件。

EE37B277-0C9E-4C31-B8E1-719F7397D0D9.jpg

上图显示了一个运行循环和各种来源的概念结构:
输入源传递异步事件到相应的处理程序,并调用runUntilDate:方法(称为线程的相关NSRunLoop对象)退出。Timer 定时源提供的事件他们的处理程序例程,但不会导致运行循环退出。

-RunLoop逻辑与实现

3026BBF4-3C5A-4501-B6A0-29413F9DF308.png

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0Source1
Source0只包含了一个回调(函数指针),并不能主动触发事件,需要手动触发,
需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef 是基于时间的触发器,可以和NSTimer 混用,包含一个时间长度和回调,加入
RunLoop时,RunLoop会注册对应的时间点,当时间点时,RunLoop会被唤醒以执行那个回调

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

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


// 用DefaultMode启动
void CFRunLoopRun(void) {                   
CFRunLoopRunSpecific(CFRunLoopGetCurrent(),        
kCFRunLoopDefaultMode, 1.0e10, false);
}
// 用指定的Mode启动,允许设置RunLoop超时时间int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { 
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
// RunLoop的实现int    CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { 
// 首先根据modeName找到对应mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); 
// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode))
return; 
// 1. 通知 Observers: RunLoop 即将进入 loop。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
// 内部函数,进入loop __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { 
Boolean sourceHandledThisLoop = NO; int retVal = 0; do { 
// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 
// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); 
// 执行被加入的block __CFRunLoopDoBlocks(runloop, currentMode);
// 4. RunLoop 触发 Source0 (非port) 回调。 sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); 
// 执行被加入的block __CFRunLoopDoBlocks(runloop, currentMode);
// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) { 
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) 
if (hasMsg) goto handle_msg;
} 
// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) { 
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。 
//  一个基于 port 的Source 的事件。
//  一个 Timer 到时间了 
//  RunLoop 自身的超时时间到了 
//  被其他什么调用者手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); 
// thread wait for receive msg } 
// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
// 收到消息,处理消息。 handle_msg: 
// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。 
if (msg_is_timer) { 
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) 
} 
// 9.2 如果有dispatch到main_queue的block,执行block。 
else if (msg_is_dispatch)   { 
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 
}
// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件 else { 
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); 
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); 
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply); 
}
}
// 执行加入到Loop的block __CFRunLoopDoBlocks(runloop, currentMode); 
if (sourceHandledThisLoop && stopAfterHandle) { 
// 进入loop时参数说处理完事件就返回。 
retVal = kCFRunLoopRunHandledSource; 
} else if (timeout) { 
// 超出传入参数标记的超时时间了 
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) { 
// 被外部调用者强制停止了 retVal = kCFRunLoopRunStopped; 
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { 
// source/timer/observer一个都没有了 
retVal = kCFRunLoopRunFinished; 
} 
// 如果没超时,mode里没空,loop也没被停止,那继续loop。 
}
while (retVal == 0); 
}
// 10. 通知 Observers: RunLoop 即将退出。          __CFRunLoopDoObservers(url, currentMode, kCFRunLoopExit);
}
void CFRunLoopRun(void) { 
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
0A1E5A26-9719-459F-A07E-8B1ED0D28160.jpg

-RunLoop在iOS中运用

Run Loop的作用
1.使程序一直运行并接受用户输入
2.决定程序在何时应该处理那些Event
3.调用解耦(Message Queue)
4.节省CPU时间

何时调用
1.使用端口port或自定义输入源input sources和其他线程通信
2.使用线程的定时器timer
3.Cocoa中使用任何performSelector的方法
4.线程执行周期性任务

RunLoop应用场景
AutoreleasePool

  • 主线程App运行时堆栈调用


    E513FD63-F9BF-4101-A618-336239CA0194.jpg

程序启动时,苹果在主线程的RunLoop中添加了两个Observer用于监视RunLoopkCFRunLoopEntry(唤起)和BeforeWating(准备休眠)

  • RunLoopObserver与Autorelease Pool的关系
    A13CAFDB-D73A-47D0-A229-22F88219630C.jpg

    UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。
    实际开发中,在线程中添加RunLoop时一般也会加上 @autoreleasepool

PerformSelecter
调用PerformSelecter:afterDelay方法时,系统会默认创建一个timer添加到当前线程中去,如果在线程中调用该方法,而没有获取currentRunLoop,则会失效

GCD
GCD与RunLoop的部分实现相互用到了对方,RunLooptimer是用dispatch_source_t实现的,而GCD的dispatch_async()也用到了RunLoop
当调用dispatch_async(dispatch_get_main_queue(), block)时,dispatch会向主线程RunLoop发送消息,RunLoop会被唤醒,并从消息中获取block后回调

定时器 NStimer
NSTimer实际上就是CFRunLoopTimerRefRunLoop不会非常准确的时间回调,延迟到程度跟当前线程是否繁忙有关,当线程空闲时,timer比较准时,线程繁忙时延迟会比较明显。Timer有个属性Tolerance宽容度,到达了某个时间点后容许有多少误差。
當創建一個Timer並添加到DefaultMode 時,Timer就會重復回調,此時滑動TableView時,RunLoop會將mode切換成TrackingRunLoopMode這時Timer就會被回調,並且也不會影響到滑動操作。如果需要Timer在多個mode下得到回調,有一個辦法就是將Timer分別加入這兩個Mode中,或者加入到頂層RunLoop的commonModeItems中去,commonModeItems被RunLoop自動更新到具有Common屬性的Mode里去。

网络请求
在基于CFNetworkNSURLConnection发起的NSURLConnectionLoader线程中,runloop通过基于mach portSource0接收来自底层CFSocket的通知,同时唤醒delegateRunLoop来处理这些通知。
事件响应 用到了 __IOHIDEventSystemClientQueueCallback()
手势识别 用到了_UIGestureRecognizerUpdateObserver()
界面更新 用到了_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
等等

RunLoop实践

// RunLoop - 创建生命周期跟App相同的常驻线程

#pragma mark - 常驻线程
- (void)viewWillAppear:(BOOL)animated
{
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longRunLoop) object:nil];
    [thread start];
}
- (void)longRunLoop
{

    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];

}

//RunLoop - 维护线程的生命周期,让线程不自动退出,isFinished为Yes时退出。 在启动RunLoop之前,必须添加监听的Port或输入源事件sources或者定时源事件timer,否则调用[runloop run]会直接返回,而不会进入循环让线程长驻。如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。

 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
@autoreleasepool {
        [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    }
}

// RunLoop - 不断检测直到满足条件

#pragma mark - 不断检测直到满足条件
- (NSString *)userAgentString
{
NSString *string;

while (string == nil)
{
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

    return string;
}

// tableView延迟加载图片的新思路

#pragma mark - 图片延迟加载
- (void)imageViewLoad{

    //ImageView的显示 滑动时不加载 只在NSDefaultRunLoopMode模式下显示图片
    UIImageView *img;
    
    [img performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

}

// RunLoop - 在一段时间内每隔一会执行某种任务的线程

//Run Loop 有 [acceptInputForMode:beforeDate:] 和[runMode:beforeDate:]方法来指定在一时间之内运行模式。如果不指定时间,Run Loop默认会运行在Default模式下(不断重复调用runMode:NSDefaultRunLoopMode beforeDate:...) 例如需要在应用启动之后,在一定时间内持续更新某项数据。
-(void)loopEvent{
@autoreleasepool {
    //在30分钟内,每隔30s执行 run 方法
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
                                                    target:self
                                                  selector:@selector(run)                                                  userInfo:nil
                                                   repeats:YES];
    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
}

// RunLoop - NSTimer 在不同模式下工作

    NSTimer *timer = [NSTimer timerWithTimeInterval:3.0
                                             target:self
                                           selector:@selector(run)
                                           userInfo:nil
                                            repeats:YES];
    // timerWithTimeInterval: 方式创建的如果不加入RunLoop,则不会执行
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

/或者/

    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                   target:self
                                                 selector:@selector(doSomeThing)
                                                 userInfo:nil
                                                  repeats:YES];
                                                  
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

     //或 [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    [timer fire];

// RunLoop - NSTimer 在线程中创建一个定时器

- (void)viewDidLoad {

    [super viewDidLoad];
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(createTimer) object:nil];
    
    [thread start];
}

- (void)createTimer{

    [NSTimer scheduledTimerWithTimeInterval:1.0
                                 target:self
                               selector:@selector(run)
                               userInfo:nil
                                repeats:YES];

    [[NSRunLoop currentRunLoop] run];
}

// RunLoop - ReactNative 框架中RCTWebSocket中用到的RunLoop

#pragma mark - ReactNative 框架中RCTWebSocket中用到的RunLoop
NSRunLoop *_runLoop;
dispatch_group_t _waitGroup;
- (void)main
{

_waitGroup = dispatch_group_create();

dispatch_group_enter(_waitGroup);

@autoreleasepool {
    _runLoop = [NSRunLoop currentRunLoop];
    dispatch_group_leave(_waitGroup);
    
    NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:self selector:@selector(step) userInfo:nil repeats:NO];
    [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
    
    while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { }
    assert(NO);
}
}
- (void)step
{
// Does nothing
}
- (NSRunLoop *)runLoop;
{
    dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
    return _runLoop;
}

// RunLoop AFNetWorking之 AFURLRequestSerilization

#pragma mark - AFNetWorking之 AFURLRequestSerilization
//额外提供的一个方法,把request里的bodyStream写到文件
//即可以把multipart发送的内容先生成好保存到文件

- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(void (^)(NSError *error))handler
{
    NSParameterAssert(request.HTTPBodyStream);
    NSParameterAssert([fileURL isFileURL]);
    
    NSInputStream *inputStream = request.HTTPBodyStream;
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
    __block NSError *error = nil;
    
    //
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //新开一条线程做这个事,stream的read和write是阻塞的
    
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    
    /*
     *NSInputStream子类
     NSURLRequest的setHTTPBodyStream接受的是一个NSInputStream*参数,那我们要自定义inputStream的话,创建一个NSInputStream的子类传给它是不是就可以了?实际上不行,这样做后用NSURLRequest发出请求会导致crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。
     这是因为NSURLRequest实际上接受的不是NSInputStream对象,而是CoreFoundation的CFReadStreamRef对象,因为CFReadStreamRef和NSInputStream是toll-free bridged,可以自由转换,但CFReadStreamRef会用到CFStreamScheduleWithRunLoop这个方法,当它调用到这个方法时,object-c的toll-free bridging机制会调用object-c对象NSInputStream的相应函数,这里就调用到了_scheduleInCFRunLoop:forMode:,若不实现这个方法就会crash。详见这篇文章。
     */
    
    [inputStream open];
    [outputStream open];
    
    while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
        uint8_t buffer[1024];
        
        NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
        if (inputStream.streamError || bytesRead < 0) {
            error = inputStream.streamError;
            break;
        }
        
        NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
        if (outputStream.streamError || bytesWritten < 0) {
            error = outputStream.streamError;
            break;
        }
        
        if (bytesRead == 0 && bytesWritten == 0) {
            break;
        }
    }
    //NSURLRequest怎么知道数据读完了?
    //应该是这个函数返回0时外部就知道stream已经读完,调用它的close方法。
    
    [outputStream close];
    [inputStream close];
    
    if (handler) {
        dispatch_async(dispatch_get_main_queue(), ^{
            handler(error);
        });
    }
});

//

NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;

return mutableRequest;
}

//AFNetworking之前的版本中AFURLConnectionOperationRunLoop用到了常驻线程

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
   [[NSThread currentThread] setName:@"AFNetworking"];
      // 为了使接收delegate回调能够在后台线程中执行,且该线程不会提前被回收
   NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    // 在这里通过监听MachPort使线程不会回收,MachPort并没有发送消息
  [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
  [runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
   _networkRequestThread =    [[NSThread alloc] initWithTarget:self
   selector:@selector(networkRequestThreadEntryPoint:)
     object:nil];
  [_networkRequestThread start];  });
return _networkRequestThread;
}

//

1432798974517485.png

相关资料:
官方文档 NSRunLoop Class Reference , CFRunLoop Reference.
CFRunLoopRef 的代码是开源的,你可以在这里http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看。

Swift 版跨平台的 CoreFoundation :https://github.com/apple/swift-corelibs-foundation/,这个版本的源码可能和现有 iOS 系统中的实现略不一样,但更容易编译,而且已经适配了 Linux/Windows。

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

推荐阅读更多精彩内容

  • 前言 最近离职了,可以尽情熬夜写点总结,不用担心第二天上班爽并蛋疼着,这篇的主角 RunLoop 一座大山,涵盖的...
    zerocc2014阅读 12,363评论 13 67
  • RunLoop的定义与概念RunLoop的主要作用main函数中的RunLoopRunLoop与线程的关系RunL...
    __silhouette阅读 1,000评论 0 6
  • Runloop是iOS和OSX开发中非常基础的一个概念,从概念开始学习。 RunLoop的概念 -般说,一个线程一...
    小猫仔阅读 976评论 0 1
  • Run loop 剖析:Runloop 接收的输入事件来自两种不同的源:输入源(intput source)和定时...
    Mitchell阅读 12,401评论 17 111
  • runLoop,正如其名,表示一直运行着的循环。 一般来说,一个线程只能执行一个任务,执行完就会推出,如果我们需要...
    li大鹏阅读 1,758评论 3 11