iOS -- RunLoop认识以及常用的场景

一、runLoop是什么

字面意思:运行循环,程序运行过程中循环的处理事件
它的实际:实际是一个对象,这个对象提供一个入口函数,执行这个入口函数后,程序会进入一个do..while循环,循环的处理一些事情。

二、runLoop有什么用?

2.1 如果没有runLoop

int main(int argc, char * argv[]){
    
    @autoreleasepool {
        
        NSLog(@“%s”, __func__);
    }
    return 0;
}

结果:程序执行完就会退出。
2.2 如果有runLoop?

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

结果:程序一直执行没有退出。

三、runLoop在程序中的例子

3.1 触摸

触摸

3.2 定时器
定时器

3.3 PerformSelector
performSelector

3.4 GCD异步操作回到主线程中
异步操作回到主线程中

总结

runLoop基本作用:

1、保持程序的持续运行
2、处理App中的各种事件(触摸、定时器、PerformSelector
3、节省CPU资源、提高程序性能:该做事的时候做事,该休息的时候休息。

四、拓展关于PerformSelector

- (void)viewDidLoad {
    [super viewDidLoad];

    BOOL isNoFire = [self respondsToSelector:@selector(fire)];
    if (isNoFire) {
        
        [self performSelector:@selector(fire)];
    }
}

- (void)fire {
    NSLog(@"%s", __func__);
}

总结:下面讲讲performSelector调用方法和直接调用方法的区别
4.1、performSelector是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用编译是会自动校验;
如果fire不存在,那么直接调用:在编译时候就能够发现(借助Xcode可以写完就发现),但是使用performSelector的话一定是在运行时候才能发现(此时程序崩溃);
Cocoa支持在运行时向某个类添加方法,即方法编译时不存在,但是运行时候存在,这时候必然需要使用performSelector去调用。所以有时候如果使用了performSelector,为了程序的健壮性,会使用检查方法respondsToSelector
4.2、直接调用方法时候,一定要在头文件中声明该方法的使用,也要将头文件import进来。而使用performSelector时候, 可以不用import头文件包含方法的对象,直接用performSelector调用即可。
4.3、performSelector是在iOS中的一种方法调用方式。他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式。
所以performSelector和直接调用方法的区别就在与runtime
直接调用编译是会自动校验。如果方法不存在,那么直接调用 在编译时候就能够发现,编译器会直接报错。
但是使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector的话他就会有个最佳伴侣respondsToSelector:来在运行时判断对象是否响应此方法。

五、runLoop与线程的关系?

总结:
5.1 线程与runLoop是一一对应的
5.2 线程创建的时候,并没有创建runLoop对象,runLoop会在第一次获取的时候自动创建。
5.3 主线程默认开启了runLoop,子线程默认没有开启。
源码如下:

// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// 访问 loopsDic 时的锁
static CFLock_t loopsLock = CFLockInit;

// 获取一个 pthread 对应的 RunLoop。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
   
    if (!__CFRunLoops) {

        __CFUnlock(&loopsLock);
        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
    }
    // 直接从 Dictionary 里获取。
    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;
        }
   
    return loop;
}

从上面的代码可以看出,线程和 runLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

六、runLoop执行过程?

//
//  RunLoop内部执行过程.c
//  TZRunloop001-初识
//
//  Created by hzg on 2018/8/25.
//  Copyright © 2018年 tz. All rights reserved.
//


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);
    
    
    /// 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 内部函数,进入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}

/// 核心函数
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    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 (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 处理消息
            goto handle_msg;
        }
        
        /// 通知 Observers: 即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // 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;
}

// main  dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

七、休眠的原理?

RunLoop实现休眠的原理, 真正的原因是:

  1. 调用了内核的API(mach_msg), 进入内核态,由内核来将线程置于休眠
  2. 有消息,就唤醒线程,回到用户态,来处理消息.


    休眠的原理

八、GCD Timer与NSTimer区别?

8.1、NSTimer不准时的原因?

1.runLoop循环处理的这个时间
2.受runLoop模式的影响

8.2、GCD Timer与NSTimer不同的原因?

  1. 都是源,一个是runLoop的源,一个Dispatch的源
  2. GCD timer不需要加入mode

代码如下

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) dispatch_source_t timer;

@end

@implementation ViewController

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

- (void) gcdTimerTest {
    
    // 队列
    dispatch_queue_t queue = dispatch_get_main_queue(); // 主线程队列,会受到runLoop影响
//    dispatch_queue_t queue = dispatch_queue_create("timer_serial_label", DISPATCH_QUEUE_SERIAL); // 子线程队列,不会受到runLoop影响
    
    // 创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 设置定时的开始时间、间隔时间
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), 1*NSEC_PER_SEC, 0);
    
    // 设置定时器回调
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"你好");
    });
    
    // 启动定时器,默认是关闭的
    dispatch_resume(timer);
    self.timer = timer;
}

总结:GCD Timer总结?

1.GCD Timer精度高
2.GCD Timer主线程执行会受runLoop影响,子线程不受影响
3.GCD Timer不受模式切换的影响

参考

iOS performSelector(参考)

推荐好文

深入理解RunLoop

你可能感兴趣的:(iOS -- RunLoop认识以及常用的场景)