OC中Runloop的本质

我们在探究Runloop的本质前首先要知道什么是Runloop?

runloop定义:iOS程序中的运行循环机制,它能够保证程序一直处于运行中状态而不是执行完任务后就立即退出

image

那么在项目的实际开发过程中,我们又有哪些开发场景中使用到了runloop的循环机制尼?,这里列举runloop的常用场景如下:

  • 定时器
  • PerformSelector()
  • GCD Async
  • 所有的事件响应,手势,列表滚动
  • 多线程
  • Autoreleasepool
  • ...

当我们新建一个iOS程序时,系统就会默认在主线程给我们创建了一个runloop对象,代码如下:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    
    // 在UIApplicationMain函数内部,系统会自动创建一个runloop对象,并添加到主线程中
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
image

当我们创建一个MacOs的命令行项目,系统没有默认为我们创建runloop对象,我们发现程序执行完语句后,就会立即退出,代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        NSLog(@"111");
    }
    
    NSLog(@"2222");
    return 0;
}

上面的代码当执行完NSLog(@"2222");打印后,程序就直接退出了。也就是说当前主线程没有runloop时程序执行完任务就直接退出,不能够一直保持运行状态。

image

我们如何才能获取到当前的runloop对象尼?

苹果为开发者提供了2套框架来访问和使用runloop对象

  • NSRunloop:Foundation框架API
  • CFRunLoopRef:Core Foundation框架API

其中NSRunloop是对CFRunLoopRef进行的一层更加面向对象的OC语法封装

    // 获取当前runloop
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
    CFRunLoopRef currentRunLoop2 = CFRunLoopGetCurrent();
        
    // 获取主线程runloop
    NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
    CFRunLoopRef mainRunLoop2 = CFRunLoopGetMain();
image
image

接下来我们来探究下runloop相关API的底层源码,源码查看路径:CF框架 -> CFRunLoop.c文件 -> _CFRunLoopGet0

我们跟踪下runloop的核心函数代码流程如下:

// __CFRunLoops变量是CFMutableDictionaryRef类型的,它就是一个全局的字典类型对象,用来存储runloop和对应线程的集合
static CFMutableDictionaryRef __CFRunLoops = NULL;

static CFLock_t loopsLock = CFLockInit;

// 核心函数`_CFRunLoopGet0`,这个函数就是用来获取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) {
    
    // 判断线程是否为空
    if (pthread_equal(t, kNilPthreadT)) {
        // 如果线程为空,则将主线程作为传递进来的线程
        t = pthread_main_thread_np();
    }
    
    // 执行加锁操作
    __CFLock(&loopsLock);
    
    // 判断__CFRunLoops集合是否有值
    if (!__CFRunLoops) {
        
        // __CFRunLoops集合没有值,解锁,往集合中添加值
        __CFUnlock(&loopsLock);
        
        // 初始化__CFRunLoops字典对象
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        
        // 创建一个主线程的runloop:mainLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
        // 设置CFMutableDictionaryRef字典的值,主线程作为key,根据主线程创建出来的mainLoop作为value
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
        
    // 释放mainLoop
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    // 根据参数线程`t`作为key,去CFMutableDictionaryRef字典中查找对应的runloop对象
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    
    __CFUnlock(&loopsLock);
    
    if (!loop) {
        
        // 没有找到对应的runloop,根据传递进来的线程`t`,创建一个新的runloop对象
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        
        // 加锁操作
        __CFLock(&loopsLock);
        
        // 再次根据参数线程`t`去全局字典中获取对应的runloop对象
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        
    if (!loop) {
        // 还是没有找到对应的runloop
        // 就将刚刚新建的newLoop作为value,参数线程`t`作为key,添加到全局字典中
        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);
    }
    
    // 判断传递过来的线程是否为当前线程(pthread_self())
    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);
        }
    }
    
    // 返回runloop
    return loop;
}

通过上面函数_CFRunLoopGet0中的底层源码实现,我们可以知道runloop和线程的关系,得出如下结论:

  • 每一个runloop对象中必定有一个与之对应的线程,因为程序中的所有runloop对象都保存在CFMutableDictionaryRef这个全局的字典集合中,并且是以线程作为key,runloop对象作为value存储在这个全局的集合中
  • 手动新创建的子线程,默认是没有runloop的,从上面的底层源码可以知道,runloop是在调用CFRunLoopRefAPI获取runloop对象的时候创建的,通过判断使用线程作为key在全局字典中取出对应的runloop,如果没有取到对应的runloop就用传递的线程新创建一个runloop
image

接下来我们再来看看runloop的底层数据结构,源码查找路径:CF框架 -> CFRunLoop.c -> struct __CFRunLoop

__CFRunLoop结构体:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

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
    uint32_t _winthread;
    
    pthread_t _pthread; // 与之对应的线程
    
    // commonMode集合,存储在_commonModes中的模式,都可以运行在kCFRunLoopCommonModes这种模式下
    CFMutableSetRef _commonModes;
    
    // 所有在commonModes这个模式下工作的`timer\source\observer等`都放到`_commonModeItems`集合中
    CFMutableSetRef _commonModeItems;
    
    CFRunLoopModeRef _currentMode; // runloop当前正在运行的Mode
    CFMutableSetRef _modes; // modes集合中存放的都是CFRunLoopModeRef对象
    
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

__CFRunLoop结构体中有一个CFMutableSetRef _modes成员,modes集合中又包含了多个CFRunLoopModeRef对象

__CFRunLoopMode结构体:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    
    Boolean _stopped;
    char _padding[3];
    
    CFStringRef _name; // Mode的名称
    
    CFMutableSetRef _sources0; // _sources0集合中存放的都是CFRunLoopSourceRef
    CFMutableSetRef _sources1; // _sources1集合中存放的都是CFRunLoopSourceRef
    CFMutableArrayRef _observers; // _observers集合中存放的都是CFRunLoopObserverRef
    CFMutableArrayRef _timers; // _observers集合中存放的都是CFRunLoopTimerRef
    
    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这四个集合对象

__CFRunLoopSource结构体

typedef struct __CFRunLoopSource * 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;
};

__CFRunLoopObserver结构体

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    
    // runloop
    CFRunLoopRef _runLoop;
    
    CFIndex _rlCount;
    CFOptionFlags _activities;      /* immutable */
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable */
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};

__CFRunLoopTimer结构体

typedef struct __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    
    // runloop
    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 */
};

上面的这些runloop相关的类的对应关系如图:

image
image

runloop的运行模式,最常用的就两种模式:

  • kCFRunLoopDefaultMode
  • UITrackingRunLoopMode

我们在平时的开发过程中也有使用过kCFRunLoopCommonModes这种模式,但是需要注意:

kCFRunLoopCommonModes并不是真正意义是上的mode,它只是一个标记符,也就是说kCFRunLoopDefaultModeUITrackingRunLoopMode这两种mode都被标记为common,存储在CFMutableSetRef _commonModes集合中,当我们设置runloop的模式为kCFRunLoopCommonModes时,系统就会在_commonModes这个集合中查找所有可以运行的模式来使用

image

我们从上面的CFRunLoopModeRef结构体成员中知道,runloop的modes集合中含有_sources0_sources1_observers_timers这四个,那么这些_sources0_sources1_observers_timers到底有什么作用?

runloop在运行循环中不停的处理的任务就是这些_sources0_sources1_observers_timers

image

runloop中循环处理_observers,也可以理解为runloop在运行循环中一直监听着_observers的以下这几种状态的变化

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

    kCFRunLoopEntry = (1UL << 0),          // 准备进入runloop
    kCFRunLoopBeforeTimers = (1UL << 1),   // 即将处理Timer事件
    kCFRunLoopBeforeSources = (1UL << 2),  // 即将处理Sources事件
    kCFRunLoopBeforeWaiting = (1UL << 5),  // 准备进入休眠状态
    kCFRunLoopAfterWaiting = (1UL << 6),   // 即将从休眠状态唤醒
    kCFRunLoopExit = (1UL << 7),               // 退出runloop状态
    kCFRunLoopAllActivities = 0x0FFFFFFFU  // 所有的状态
};
image

下面我们通过代码来验证下在runloop中手动添加observer,来观察observer的状态变化,代码如下:

    // 创建一个Observe
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerHandler, NULL);
    
    // 创建一个runloop
    CFRunLoopRef loop = CFRunLoopGetMain();
    
    // 将observere添加到runloop
    CFRunLoopAddObserver(loop, observer, kCFRunLoopDefaultMode);
    
    // 释放observer
    CFRelease(observer);

observerHandler监听函数

void observerHandler(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
        break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
        break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
        break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
        break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
        break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
        break;
        default:
            break;
    }
}

我们通过打印可以看出,runloop确实是在各种observer的状态间不停的切换

image

接下来我们再通过滚动列表示例,验证runloop的mode的切换过程,代码如下:

    // 创建一个runloop
    CFRunLoopRef loop = CFRunLoopGetMain();
    
    // 创建一个Observe
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
            {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(loop);
                NSLog(@"kCFRunLoopEntry == %@", mode);
                CFRelease(mode);
            }
            break;
            case kCFRunLoopExit:
            {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(loop);
                NSLog(@"kCFRunLoopExit == %@", mode);
                CFRelease(mode);
            }
            break;
            default:
                break;
        }
    });
    
    // 将observere添加到runloop
    CFRunLoopAddObserver(loop, observer, kCFRunLoopCommonModes);
    
    // 释放observer
    CFRelease(observer);

我们通过打印可以看到,当我们拖动列表时,runloop的modekCFRunLoopDefaultMode切换至UITrackingRunLoopMode

image

当我们停止列表拖动后,runloop的mode又从UITrackingRunLoopMode切换至kCFRunLoopDefaultMode

image

接下来我们通过底层源码来研究runloop的整个循环执行过程,底层源码查找路径:CF框架 -> CFRunLoop.c文件 -> CFRunLoopRunSpecific -> __CFRunLoopRun,底层核心源码如下:

CFRunLoopRunSpecific函数核心代码

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    CHECK_FOR_FORK();
        
    __CFRunLoopLock(rl);
    
    // 获取当前的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;

    // 判断是否进入runloop
    if (currentMode->_observerMask & kCFRunLoopEntry)
    
        // 通知observer,进入runloop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
        // __CFRunLoopRun:此函数中真正的开始处理runnloop中的任务
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    
    // 判断是否退出runloop
    if (currentMode->_observerMask & kCFRunLoopExit )
    
        // 通知observer,退出runloop
        __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) {
    
    int32_t retVal = 0;
    
    // 此do-while循环就是runloop能够保证程序一直运行而不退出的的核心
    do {
        
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
            // 通知observer,处理timers
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        }
            
        if (rlm->_observerMask & kCFRunLoopBeforeSources) {
            // 通知observer,处理Sources
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

            // 处理blocks
            __CFRunLoopDoBlocks(rl, rlm);
                        
            // 处理sources0
            __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                // 处理blocks
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            // 判断是否有source1
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                // 如果有source1,则跳转到`handle_msg`标记处,执行标记后的代码
                goto handle_msg;
            }
        }
            
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) {
            // 通知observer,即将进入休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            
            // 设置runloop开始休眠
            __CFRunLoopSetSleeping(rl);
        
            // runloop在此处就开始处于休眠状态,等待消息来唤醒runloop,使用内核机制来进行线程阻塞,而不是死循环
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            // runloop 取消休眠设置
            // user callouts now OK again
            __CFRunLoopUnsetSleeping(rl);
            
           // 通知observer,即将唤醒runloop
           __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        }
            
    // `handle_msg`标记
    handle_msg:;
        
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // 1、runloop被timer唤醒
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            
            // 处理timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        } else if (livePort == dispatchPort) {
            // 2、runloop被dispatch唤醒
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            
            // 处理gcd相关事情
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            // 3、runloop被source唤醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            // 处理source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }

        // 处理blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 判断retVal的值
        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;
}

我们在对上面的核心流程进行一个简要的梳理总结:

  • 通知observer,进入runloop
  • 执行__CFRunLoopRun函数
    1. 通知observer,处理timers
    2. 通知observer,处理sources
    3. 处理blocks
    4. 处理sources0
    5. 如果sourceHandledThisLoop条件满足,处理blocks
    6. 判断是否有sources1,有则跳转到handle_msg
    7. 通知observer,runloop即将进入休眠
    8. 通知observer,runloop即将结束休眠
      • 如果runloop被timer唤醒,处理timers
      • 如果runloop被dispatch唤醒,处理gcd(dispatch_async(dispatch_get_main_queue(), ^{})
      • 如果runloop被source唤醒,处理source1
    9. 处理blocks
    10. 判断retVal的值,决定是跳到循环第一步还是退出runloop
  • 通知observer,退出runloop

上面runloop的循环执行流程图如下图:

image

讲解示例Demo地址:https://github.com/guangqiang-liu/08-Runloop

更多文章

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

推荐阅读更多精彩内容

  • RunLoop 是 iOS 和 OS X 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,...
    iOS_Alex阅读 883评论 0 10
  • RunLoop 的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线...
    Mirsiter_魏阅读 592评论 0 2
  • 转自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_阅读 1,209评论 0 5
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技术 RunLoop 是 iOS 和 ...
    橙娃阅读 821评论 1 2
  • RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介...
    iOS大熊猫阅读 190评论 0 0