美文网首页
iOS-RunLoop01-基本认识

iOS-RunLoop01-基本认识

作者: IBigLiang | 来源:发表于2019-08-29 17:46 被阅读0次

RunLoop,顾名思义就是一种iOS框架中的运行循环,而程序在运行中,这个循环一直在为程序做一些事情。
首先大家都知道,我们创建一个项目,在main.m文件的main函数中包含以下内容:

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

其实,这个return的内容就是一个RunLoop,其主要目的就是要程序一直进行,不被退出,简单点说就是一个死循环,二在这个循环中,程序可以做自己想做的事,一旦退出循环,程序就结束应用。
除了上述所说,RunLoop可以保持程序的持续运行,它还可以帮我们处理APP运行中的各种事件,比如触摸事件啊,定时器事件等等。而且它还有一个很大的特点,就是在没有事件需要处理的时候,它就会进入休眠,等待下一次被唤醒,在休眠过程中,基本不消耗CPU资源。
在iOS中有Foundation框架中的NSRunLoop和CoreFoundation框架中的CFRunLoop两套关于RunLoop的API可供我们使用,这里我们先简单的来运用下吧:

//当获取到一个RunLoop时,首先它自动会去帮我们创建一个fRunLoop
    // OC
    NSRunLoop *runLoop =  [NSRunLoop currentRunLoop]; // 创建一个RunLoop
    [runLoop run]; // 运行RunLoop
    
    [NSRunLoop mainRunLoop]; // 获取主线程RunLoop

    // C
    CFRunLoopGetCurrent(); // 创建一个RunLoop
    CFRunLoopRun(); // 运行RunLoop
    
    CFRunLoopGetMain(); // 获取主线程RunLoop

以上是在两个框架下,最简单的RunLoop运用。在上面代码中,我们可以看到,我们可以直接获取一个RunLoop,比方说mainRunLoop,这个方法获取到的是主线程的RunLoop,其实没一个RunLoop都会对应这一条线程,mainRunLoop就对应着我们的主线程,而子线程中也有着与自己对应的RunLoop,就比如如下的代码中:

- (void)viewDidLoad {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFun) object:nil];
    [thread start];   
}

- (void)threadFun {
    NSRunLoop *runLoop =  [NSRunLoop currentRunLoop]; // 创建一个RunLoop
    NSLog(@"%p, %p", runLoop, [NSRunLoop mainRunLoop]);
}

打印出来的结果是:

2019-08-29 15:35:10.753941+0800 001-RunLoop使用[68546:25248318] 0x600000244ba0, 0x600000249080

很明显,子线程中创建的RunLoop和主线程的RunLoop不是系统一个,所以每一个线程都对应着一个RunLoop,而且,需要知道的是,RunLoop在线程结束的时候,就会销毁,除非用特定的方法不让线程结束,这个我们后面再讨论。
接下来,我们再来看看和runloop相关的类,这里我用CoreFoundation框架来展示,有以下这几个类:

    CFRunLoopMode // RunLoop模式
    CFRunLoopRef //RunLoop类
    CFRunLoopSourceRef // source(各种事件处理)
    CFRunLoopObserverRef //观察者(用于监听RunLoop状态)
    CFRunLoopTimerRef //定时器

这里我们看不到底层的源码,所以按照之前的方法,我们去下载源码来看对应的知识点,源码地址:https://opensource.apple.com/tarballs/CF/,下载完之后,打开找到CFRunLoop.c文件,我们先来看CFRunLoopRef这个类型,它对应的是struct __CFRunLoop 这个结构体,这里我们先把结构体内主要的部分抽出来:

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};

从抽出来的代码中,我们也可以了解到,每一个RunLoop都对应着一个线程,然后我们再主要来看看CFRunLoopModeRef这个类型,可以找到:

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

同样也是一个结构体,抽取之后如下:

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
}

到这里,我们基本上就能看清一个RunLoop的内部结构了,用一张图来总结就是如下:


image.png

所以,总结得到,RunLoop中包含若干个_commonModes,但是每次只能选择其中一个_currentMode,而每一个_currentMode中又包含若干个Source0/Source1/Timer/Observer
,而且不同组的Source0/Source1/Timer/Observer都是分隔开的,互不影响,当然当mode中没有Source0/Source1/Timer/Observer,这时候RunLoop会退出。
然后我们来看看常见的几种model,一种是kCFRunLoopDefaultMode(NSDefaultRunLoopMode),一种是UITrackingRunLoopMode。
NSDefaultRunLoopMode是默认模式,通常主线程就是在这个模式下。UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
然后我们再看CFRunLoopObserverRef这个监听着RunLoop状态的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
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

这里,我们可以利用在主线程的RunLoop中添加Observer来检测RunLoop在不同模式下的切换,代码如下:

- (void)viewDidLoad {
    
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
        
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
}

在项目中的Main.storyboard中根视图上添加一个textView,然后在程序启动之后进行滚动,你会发现控制台的打印:

2019-08-29 17:41:10.888940+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - kCFRunLoopDefaultMode
2019-08-29 17:41:10.889159+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - UITrackingRunLoopMode
2019-08-29 17:41:12.873182+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - UITrackingRunLoopMode
2019-08-29 17:41:12.873288+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - kCFRunLoopDefaultMode

很明显主线程的RunLoop进行的切换。
由于时间关系,今天暂时分享到这个,之后我会继续分享RunLoop的运行逻辑,以及RunLoop在实际开发中的应用。

相关文章

  • iOS-RunLoop01-基本认识

    RunLoop,顾名思义就是一种iOS框架中的运行循环,而程序在运行中,这个循环一直在为程序做一些事情。首先大家都...

  • python基本认识

    Python 版本 分为: Python 2 为过去的版本。 解释器 为 Ipython 或者 Ipython2 ...

  • python基本认识

    注释用于提高代码可读性的辅助性文字,不被执行 单行''' 三个连续的单引号 ,多行注释 缩进一行代码开始前的空白区...

  • 联盟基本认识

    接手联盟两个多月了,也对联盟有一定了解了,今天把所认识的联盟梳理下。 联盟主要的形式是CPS,广告主与网站主之间通...

  • binlog 基本认识

    binlog 基本认识 MySQL的二进制日志可以说是MySQL最重要的日志了,它记录了所有的DDL和DML(...

  • swift 基本认识

    1,格式化创建字符串 2,数组 array(跟 OC 里面的数组是可以互相转换的) 3,字典 dictionary...

  • OC 基本认识

    1 #import int main(int argc, constchar * argv[]) {...

  • HTTP基本认识

    HTTP(HyperText Transfer Protocol,超文本传输协议又叫超文本转移协议)协议是用来完...

  • SPDY基本认识

    关于HTTP安全性的问题,我们可以使用HTTPS来解决。但是HTTP都发布十几年了,有些规则已经过时了,HTTP/...

  • HTTPS基本认识

    HTTP协议中有可能存在信息窃听或者身份伪装等安全问题,使用HTTPS通信机制可以有效地防止这些问题。接下来简单的...

网友评论

      本文标题:iOS-RunLoop01-基本认识

      本文链接:https://www.haomeiwen.com/subject/pduiectx.html