Runloop

  • 使程序一直运行并接受用户输入
  • 决定程序在何时应该处理哪些Event
  • 调用解耦(Message Queue)
  • 节省CPU时间

RunLoop对象

  • iOS中有2套API来访问和使用RunLoop
    • Foundation NSRunLoop
    • Core Foundation CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
  • NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

主线程几乎所有函数都从以下六个之一的函数调起

    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象

  • 主线程的RunLoop已经自动创建好了 子线程的RunLoop需要主动创建

  • RunLoop在第一次获取时创建,在线程结束时销毁

RunLoop相关类

Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef

  • CFRunLoopModeRef

  • CFRunLoopSourceRef

  • CFRunLoopTimerRef

  • CFRunLoopObserverRef

RunLoop相关类

构成元素

构成元素

CFRunLoopSourceRef

CFRunLoopSourceRef是事件源(输入源)

  • 以前的分法

    • Port-Based Sources

    • Custom Input Sources

    • Cocoa Perform Selector Sources

  • 现在的分法

    • Source0:非基于Port的

    • Source1:基于Port的

CFRunLoopSource(事件源/输入源)

  • Source是RunLoop的数据源抽象类(protocol)
  • RunLoop定义了两个Version的Source:
    • Source0:处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket
    • Source1:由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort

CFRunLoopTimer(定时源)

  • RunLoopTimer的封装
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

CFRunLoopObserver(观察源)

向外部报告RunLoop当前状态的更改 框架中很多机制都由RunLoopObserver触发

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoopObserver 与 Autorelease Pool

UIKit通过RunLoopObserver在RunLoop两次Sleep间
对AutoreleasePool进行Pop和Push
将这次Loop中产生的Autorelease对象释放

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式

  • 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer

  • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode

  • 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入

  • 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响

系统默认注册了5个Mode:

  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行

  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受 其他 Mode 影响

  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用

  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunLoopMode

  • RunLoop在同一段时间只能且必须在一种特定Mode下Run
  • 更换Mode时,需要停止当前Loop,然后重启新Loop
  • Mode是iOS App滑动顺畅的关键
  • 可以定制自己的Mode

NSDefaultRunLoopMode 默认状态、空闲状态
UITrackingRunLoopMode 滑动ScrollView时
UIInitializationRunLoopMode 私有,App启动时
NSRunLoopCommonModes Mode集合

RunLoop的挂起与唤醒

  • 指定用于唤醒的mach_port端口
  • 调用mach_msg监听唤醒端口,被唤醒前,系统内核将这个线程挂起,停留在mach_msg_trap状态
  • 由另一个线程(或另一个进程中的某个线程)向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续开始干活

RunLoop迭代执行顺序

   //设定过期时间  
SetupThisRunLoopRunTimeOutTimer();  //by GCD timer  
do{  
    //通知Observer要跑timer跟source  
    __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);  
    __CFRunLoopDoObservers(kCFRunLoopBeforeSources);  

    __CFRunLoopDoBlocks();  
    //运行到此刻,去检测当前加到消息队列source0的消息,此方法遍历source0去执行  
    __CFRunLoopDoSource0();  

    //询问GCD有没有分到主线程的东西需要调用  
    CheckIfExistMessageInMainDispatchQueue();   //GCD  

    //通知Observer要进入睡眠  
    __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);  
    //此刻获取到是哪个端口把我叫醒  
    var wakeUpPort = SleepAndWaitForWakingUpPorts();  
    //  mach_msg_trap  
    //  Zzz...  
    //  Received mach_msg,  wake up!  

    //通知Observer我要醒了~  
    __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);  

    //Handler msgs  
    if(wakeUpPort == timerPort){  
        //如果是timer唤醒就去执行timer  
        __CFRunLoopDoTimer();  
    }else if(wakeUpPort == mainDispatchQueuePort){  
        //GCD需要我,就去调GCD的事件  
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE();  
    }else{  
        //比如说网络来数据了就会用这个端口唤醒,然后做数据处理  
        __CFRunloopDoSource1();  
    }  
    __CFRunLoopDoBlocks(); 

}while (!stop && !timeOut);//如果没被外部干掉或者时间没到,继续循环

AFNetworking中RunLoop的创建

这是创建一个常驻服务线程的方法

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [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;
}

一个TableView延迟加载图片的新思路

    UIImage *downloadedImage = ...;
    [self.avatarImageView performSelector:@selector(setImage:)
                               withObject:downloadedImage
                               afterDelay:0
                                  inModes:@[NSDefaultRunLoopMode]];

让Crash的App回光返照

接到Crash的Signal后手动重启RunLoop

    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
    while (1) {
        for (NSString *mode in allModes) {
            CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
        }
    }

Async Test Case

RunLoop sleep前验证

- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
    __block Boolean fulfilled = NO;
    void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
    ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        fulfilled = block();
        if (fulfilled) {
            CFRunLoopStop(CFRunLoopGetCurrent());
        }
    };

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    // Run!
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);

    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);

    return fulfilled;
}

results matching ""

    No results matching ""