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 : 释放自动释放池

    将来的你会感谢今天如此努力的你! 版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    python之openpyxl模块
    jquery实现轮播图
    Css进阶练习(实现抽屉网样式布局)
    python之UnittTest模块
    zkClient的使用
    Watcher、ZK状态、事件类型 ,权限
    java 操作zookeeper
    Zookeeper简介
    RocketMq顺序消费
    RocketMq --consumer自动实现负载均衡
  • 原文地址:https://www.cnblogs.com/chglog/p/4748954.html
Copyright © 2011-2022 走看看