美文网首页RunLoop
# iOS开发之RunLoop

# iOS开发之RunLoop

作者: 纳萨立克 | 来源:发表于2016-04-11 17:57 被阅读401次

    iOS开发之RunLoop

    什么是RunLoop

    • 运行循环,跑圈
    • 其实内部就是do-while循环,在这个循环n内部不断的处理各种任务(比如Source/Timer/Observer)
    • 一个线程对应一个RunLoop,主线程的RunLoop默认启动了,子线程需要手动调用(run方法)
    • RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source/Timer/Observer,那就直接退出RunLoop

    RunLoop的作用

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

    RunLoop对象

    • Foundation : NSRunLoop(OC对C的RunLoop的简单的封装)
    [NSRunLoop currentRunLoop]; //获取当前线程的Runloop
    [NSRunLoop mainRunLoop]; //获取主线程的RunLoop
    
    • Core Foundation CFRunLoop(C语言 开源 跨平台的)
    CFRunLoopGetCurrent();//获取当前线程的Runloop
    CFRunLoopGetMain(); //获取主线程的RunLoop
    

    RunLoop的机制

    • 每条线程都有唯一一个与之对应的Runloop对象
    • 主线程的RunLoop已经创建好了,子线程的Runloop需要自动创建
    • RunLoop在第一次获取时创建,在线程结束时销毁

    CFRunLoopModelRef

    代表的是Runloop的运行模式

    • 一个Runloop包含若干个Mode,每个Model又包含了多个Source/Timer/Observer
    • Runloop在同一段时间只能且必须在一种特定的Mode下Run
    • 更换Mode时,需要停止当前的Loop,然后重启新的Loop
    • Mode是iOS App滑动顺利的关键
    • 可以自己定制Mode(基本不会发生的)

    NSDefaultRunLoopMode 默认状态,空闲状态,通常主线程是在这个Mode下运行
    UITrackingRunLoopMode 界面跟踪 Mode, 滑动ScrollView时
    UIInitializationRunLoopMode 私有,App启动时进入的第一个Mode 启动完后不在使用
    NSRunLoopCommonModes   Mode集合 默认包括上面第一和第二
    GSEventReceiveRunLoopMode 接受系统内部时间的Mode

    CFRunLoopSource

    • Source是Runloop的数据源的抽象类(类似IOS中的protocol)
    • 定义了2个版本的Source
      1. Source0 :处理app的内部时间,App自己负责管理 如:UIEvent CFSocket
      2. Source1 :用Runloop和内核管理,Mach port驱动,如 CFMachPort CFMessagePort (Port可以用于进程间的端口通讯)

    CFRunLoopTimerRef

    • CFRunLoopTimerRef是基于时间的触发器
    • 基本上说的就是NSTimer

    CFRunLoopObserver

    向外部报告Runloop当前状态的更改,能够监听Runloop的状态的改变

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0), //即将进入RunLoop
        kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
        kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
        kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
        kCFRunLoopExit = (1UL << 7), //退出
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    
    
        // 创建observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
        });
    
        // 添加观察者:监听RunLoop的状态
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        
        // 释放Observer
        CFRelease(observer);
        
    

    注意这里面的observer是需要释放的: 在ARC中自动内存管理的是OC的对象

    CF的内存管理(Core Foundation)

    • 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release 比如CFRunLoopObserverCreate.
    • release函数:CFRelease(对象);

    RunLoop的处理逻辑

    RunLoopObserver与Autorelease Pool

    在RunLoop睡觉之前释放(kCFRunLoopBeforeWaiting)

    UITrackingRunLoopMode 与 Timer

    Timer默认是被添加在NSDefaultRunLoopMode中的,当ScrollerView滑动的时候就会影响到
    Timer,若不希望Timer被影响,需要添加到NSRunLoopCommonModes

        
        [[NSRunLoop currentRunLoop] addTimer:[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeaction) userInfo:nil repeats:NO] forMode:NSRunLoopCommonModes];
        
    
    

    Runloop与dispath_get_main_queue()

    GCD到dispath到main queque的block被分发到main Runloop执行

    GCD中的定时器和Runloop没有关系的,GCD的定时器是不受RunLoop的Mode的影响的

        // 获得队列
    //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        // 创建一个定时器(dispatch_source_t本质还是个OC对象)
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        
        // 设置定时器的各种属性(什么时候开始任务,每隔多长时间执行一次)
        // GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
        // 何时开始执行第一个任务
        // dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比当前时间晚3秒
        dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
        uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
        dispatch_source_set_timer(self.timer, start, interval, 0);
        
        // 设置回调
        dispatch_source_set_event_handler(self.timer, ^{
            NSLog(@"------------%@", [NSThread currentThread]);
            count++;
            
    //        if (count == 4) {
    //            // 取消定时器
    //            dispatch_cancel(self.timer);
    //            self.timer = nil;
    //        }
        });
        
        // 启动定时器
        dispatch_resume(self.timer);
    

    RunLoop的挂起与唤醒

    • 指定用于唤醒的mach_port端口
    • 调用mach_msg监听唤醒端口,被唤醒前,系统内核将这个线程挂起,停留在mach_msg_trap状态
    • 由另一个线程(或者另一个进程中的线程) 向内核发送这个端口的msg后,trap状态被唤醒.
         //在子线程中默认是没有RunLoop的 
        //获取Runloop,当当前线程没有runloop的时候,该方法就会启动runloop
        NSRunLoop *loop = [NSRunLoop currentRunLoop];
        
        //给Runloop一个端口,这样就可以保持Runloop处于唤醒的状态
        [loop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    
        //run
        [loop run];
    

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

     [[NSThread currentThread] setName:@"thread1"];
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefalutRunLoopMode]//一直活着
    [runLoop run];
    

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

        UIImageView * iconImageView = [UIImageView new];
    
        UIImage *image = nil;
        
        [iconImageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
        
        //NSDefaultRunLoopMode 设置为这个模式的时候 当TableView在滑动到时候Runloop在UITrackingRunLoopMode模式,这样setimage方法就不会被调用.只有不滑动的时候,runloop切换到NSDefaultRunLoopMode模式,这时候设置图片
        
    

    应用场景

    • 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发送消息,处理其他时间)
      • 在子线程中开启一个定时器
      • 在子线程中长期监控行为
    • 可以控制定时器在哪种模式下运行
    • 可以让某些任务在特定模式下执行
    • 可以添加Observer监听Runloop的状态,比如监听点击时间前做一些事情

    相关文章

      网友评论

        本文标题:# iOS开发之RunLoop

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