zoukankan      html  css  js  c++  java
  • iOS开发——多线程篇——RunLoop

    一、简介


    1、什么是RunLoop
    从字面意思看
    运行循环
    跑圈

    基本作用
    保持程序的持续运行
    处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
    节省CPU资源,提高程序性能:该做事时做事,该休息时休息
    ......

    2、如果没有RunLoop

    1 int main(int argc, char * argv[]) {
    2 NSLog(@"execute main function");
    3 return 0;
    4 }

    没有RunLoop的情况下
    第3行后程序就结束了

    3、如果有了RunLoop

    有RunLoop的情况下
    由于main函数里面启动了个RunLoop,所以程序并不会马上退出,保持持续运行状态

    4、main函数中的RunLoop

    第14行代码的UIApplicationMain函数内部就启动了一个RunLoop
    所以UIApplicationMain函数一直没有返回,保持了程序的持续运行
    这个默认启动的RunLoop是跟主线程相关联的

    验证:

    可以看到----------不会打印,一直在执行UIApplicationMain

    5、模拟RunLoop内部实现
    + 其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)

    void message(int num)
    {
        printf("执行第%i个任务", num);
    }
    int main(int argc, const char * argv[]) {
        do {
            printf("有事吗? 没事我睡了");
            int number;
            scanf("%i", &number);
            message(number);
        } while (1);
        return 0;
    }

    二、RunLoop对象


    1、RunLoop
    iOS中有2套API来访问和使用RunLoop
    Foundation
    NSRunLoop

    Core Foundation
    CFRunLoopRef

    NSRunLoop和CFRunLoopRef都代表着RunLoop对象

    NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

    2、RunLoop资料
    苹果官方文档
    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

    CFRunLoopRef是开源的
    http://opensource.apple.com/source/CF/CF-1151.16/

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

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

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

    4、如何获得RunLoop对象
    4.1 Foundation(oc语言)
    如果是在子线程中调用currentRunLoop, 那么系统首先会查看, 当前子线程是否有对应的NSRunLoop, 如果没有就创建一个
    注意: 如果想给子线程添加一个与之对应的NSRunLoop, 不能通过alloc/init  需要通过currentRunLoop
    如果是在子线程中调用了currentRunLoop, 那么拿到的就是子线程的NSRunLoop(在什么线程内调用就是这个线程的NSRunLoop对象)
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

    4.2 Core Foundation(c语言)
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象


    三、RunLoop相关类


    1、RunLoop相关类
    Core Foundation中关于RunLoop的5个类
    CFRunLoopRef
    CFRunLoopModeRef
    CFRunLoopSourceRef
    CFRunLoopTimerRef
    CFRunLoopObserverRef

    2、CFRunLoopModeRef
    2.1 CFRunLoopModeRef代表RunLoop的运行模式
    一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer

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

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

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

    2.2 系统默认注册了5个Mode:
    kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行

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

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

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

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

    3、CFRunLoopSourceRef
    CFRunLoopSourceRef是事件源(输入源)

    以前的分法
    Port-Based Sources
    Custom Input Sources
    Cocoa Perform Selector Sources

    现在的分法
    Source0:非基于Port的
    Source1:基于Port的

    4、CFRunLoopTimerRef
    CFRunLoopTimerRef是基于时间的触发器

    基本上说的就是NSTimer

    5、CFRunLoopObserverRef
    CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

    可以监听的时间点有以下几个

    // 1.创建Observer
        // 第一个参数:用于分配该observer对象的内存
        // 第二个参数:用以设置该observer所要关注的的事件
        // 第三个参数:用于标识该observer是在第一次进入run loop时执行, 还是每次进入run loop处理时均执行
        // 第四个参数:用于设置该observer的优先级
        // 第五个参数: observer监听到事件时的回调block
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch(activity)
            {
                case kCFRunLoopEntry:
                    NSLog(@"即将进入loop");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"即将处理timers");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"即将处理sources");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"即将进入休眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"刚从休眠中唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"即将退出loop");
                    break;
                default:
                    break;
            }
        });
    
        // 2.添加监听
        /*
         第一个参数: 给哪个RunLoop添加监听
         第二个参数: 需要添加的Observer对象
         第三个参数: 在哪种模式下监听
         */
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
    
        // 3,释放observer
        CFRelease(observer);
        
        

    四、RunLoop处理逻辑


    1、官方版-RunLoop处理逻辑

    五、RunLoop应用


    1、NSTimer 只能在指定的model下运行

    // 创建一个NSTimer之后, 必须将NSTimer添加到RunLoop中, 才能执行
        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];
        
        // 将NSTimer添加到当前线程NSRunLoop的默认模式下, 只有当前线程NSRunLoop当前是默认模式才会执行timer
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        
        // 将NSTimer添加到当前线程NSRunLoop的追踪模式,只有当前线程NSRunLoop当前是追踪模式才会执行timer
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
        
        // 这是一个占位用的Mode,不是一种真正的Mode
        // 其实Common是一个标识, 它是将NSDefaultRunLoopMode和UITrackingRunLoopMode标记为了Common
        // 所以, 只要将timer添加到Common占位模式下,timer就可以在NSDefaultRunLoopMode和UITrackingRunLoopMode模式下都能运行
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        
        
    // 如果通过scheduled方法创建NSTimer, 系统会默认添加到当前线程的默认模式下
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];

    2、GCD定时器

    // 1.创建tiemr
        // queue: 代表定时器将来回调的方法在哪个线程中执行
    //    dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        self.timer = timer;
        // 2.设置timer
        /*
         第一个参数: 需要设置哪个timer
         第二个参数: 指定定时器开始的时间
         第三个参数: 指定间隔时间
         第四个参数: 定时器的精准度, 如果传0代表要求非常精准(系统会让定时器执行的时间变得更加准确) 如果传入一个大于0的值, 就代表我们允许的误差
         // 例如传入60, 就代表允许误差有60秒
         */
        
        // 定时器开始时间
    //    dispatch_time_t startTime = DISPATCH_TIME_NOW;
        dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
        
        // 定时器间隔的时间
        uint64_t timerInterval = 2.0 * NSEC_PER_SEC;
        dispatch_source_set_timer(timer, startTime, timerInterval, 0 * NSEC_PER_SEC);
        
        // 3.设置timer的回调
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"我被调用了  %@", [NSThread currentThread]);
        });
        
        // 4.开始执行定时器
        dispatch_resume(timer);

    3、imageView

    - ImageView显示
    + 只能在指定的model下设置图片
    - PerformSelector
    + 只能在指定的model下调用

    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageNamed:@"lnj"] waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];

    4、常驻线程
    给子线程添加一个RunLoop
    // 注意:
    + 必须调用run才会执行死循环
    + NSRunLoop的model中必须有source/timer,死循环才不会退出
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runloop run]

    5、自动释放池
    activities = 0x1 = 1
    1: 即将进入RunLoop : 创建一个自动释放池
    activities = 0xa0 = 160 = 128 + 32
    32:即将休眠 : 释放上一次的自动释放池, 创建一个新的自动释放池
    128:即将退出RunLoop : 释放自动释放池

    将来的你会感谢今天如此努力的你! 版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    Spring Boot2 系列教程(二十)Spring Boot 整合JdbcTemplate 多数据源
    Spring Boot 如何给微信公众号返回消息
    Spring Boot2 系列教程(十九)Spring Boot 整合 JdbcTemplate
    Spring Boot2 系列教程(十八)Spring Boot 中自定义 SpringMVC 配置
    Spring Boot 开发微信公众号后台
    Spring Boot2 系列教程(十七)SpringBoot 整合 Swagger2
    Spring Boot2 系列教程(十六)定时任务的两种实现方式
    Spring Boot2 系列教程(十五)定义系统启动任务的两种方式
    Spring Boot2 系列教程(十四)CORS 解决跨域问题
    JavaScript二维数组
  • 原文地址:https://www.cnblogs.com/chglog/p/4748954.html
Copyright © 2011-2022 走看看