美文网首页
RunLoop第一讲

RunLoop第一讲

作者: 奋斗的郅博 | 来源:发表于2017-01-03 14:51 被阅读30次
    cmd-markdown-logo

    引言

    RunLoop在大家的印象中就是很神秘的感觉,很多人都不愿去触碰它。其实对于其在项目中的应用还是非常实际的和方便的。在我们项目中也有关于其的应用实例。今天,我们一起探索关于RunLoop的那些事吧!

    基本作用

    • 保持程序的持续运行(比如主运行循环)。
    • 处理App中的各种事件响应(比如触摸事件、定时器事件等)。
    • 节省CPU资源,提高程序性能(也就是说该做事时做事,该休息时休息)。

    在程序中存在价值和意义

    就拿程序内部的main函数为例:

    图一没有RunLoop.png 图二有RunLoop.png

    main函数的作用和其中实现的RunLoop(主循环)

    • main函数是程序的入口
    • 保持程序的持续运行
    • 图一中Return返回的为具体数值,有返回值,程序运行到Return就结束了,这种情况表现为我们的程序启动不起来
    • 图二中Return一直没有返回值。内部实现是通过UIApplicationMain函数内部就启动了一个RunLoop。所以UIApplicationMain函数一直没有返回,保持了程序的持续运行
    • 图二这个默认启动的RunLoop是跟主线程相关联的

    RunLoop 的对象

    对于RunLoop其有两套API:

    • 基于C语言的CoreFoundation框架的CFRunLoopRef对象
    • 基于CFRunLoopRef的一层OC包装的Foundation的NSRunLoop对象

    RunLoop与线程

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

    RunLoop对象的获取

    基于Foundation框架的:

    • [NSRunLoop currentRunLoop] //获取当前线程的RunLoop的对象
    • [NSRunLoop mainRunLoop] //获取主线程RunLoop的对象

    基于CoreFoundation框架的:

    • CFRunLoopGetCurrent() //获取当前线程的RunLoop的对象
    • CFRunLoopGetMain() //获取主线程RunLoop的对象

    Core Foundation中关于RunLoop相关的类

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

    CFRunLoopModeRef

    • 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer,即是一个RunLoop至少有一个向对应的Mode
    • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
    相关类

    系统默认注册了5个Mode:(前三个比较常用)

    • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
    • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
    • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

    CFRunLoopSourceRef

    CFRunLoopSourceRef是事件源(输入源)
    按照官方文档的分类

    • Port-Based Sources (基于端口,跟其他线程交互,通过内核发布的消息)
    • Custom Input Sources (自定义)
    • Cocoa Perform Selector Sources (performSelector...方法)

    按照函数调用栈的分类Source有两个版本:Source0 和 Source1。

    • Source0:非基于Port的
    • Source1:基于Port的

    注:Source0: event事件,只含有回调,需要先调用CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop。
    Source1: 包含了一个 mach_port 和一个回调,被用于通过内核和其他线程相互发送消息,能主动唤醒 RunLoop 的线程。

    CFRunLoopTimerRef

    • CFRunLoopTimerRef是基于时间的触发器
    • 基本上说的就是NSTimer(CADisplayLink也是加到RunLoop),它受RunLoop的Mode影响

    注释:是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

    CFRunLoopObserverRef

    CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:


    观察者可以监听的状态

    使用方法调用:

    - (void)observer
    {
        // 创建observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
        });
        // 添加观察者:监听RunLoop的状态
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        // 释放Observer
        CFRelease(observer);
    }
    
    

    *注:CF的内存管理(Core Foundation)
    1.凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release 。比如CFRunLoopObserverCreate
    2.release函数:CFRelease(对象);

    RunLoop的应用实例

    • 场景还原
      1.NSTimer(最常见RunLoop使用)
    - (void)timer
    {
        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        // 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
        //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        // 定时器只运行在UITrackingRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
        //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
        // 定时器会跑在标记为common modes的模式下
        // 标记为common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode兼容
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    - (void)timer2
    {
        // 调用了scheduledTimer返回的定时器,已经自动被添加到当前runLoop中,而且是NSDefaultRunLoopMode
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        // 修改模式
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    

    2.关于轮播图的拖拽Mode的变换导致的问题
    由于拖拽时模式由NSDefaultRunLoopMode 进入 UITrackingRunLoopMode模式发生了变化。所以我们设置模式的时候设为NSRunLoopCommonModes 模式下两种模式都可运行
    3.PerformSelector

    • -(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

    创建一个线程在子线程执行,aSelector代表了新创建的线程,arg是传入的参数

    • -(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

    该方法的作用是在主线程中,执行制定的方法(代码块)。
    参数:
    @selector就是,要定义我们要执行的方法。
    withObject:arg定义了,我们执行方法时,传入的参数对象。类型是id。(我们可以传入任何参数)waitUntilDone:YES指定,当前线程是否要被阻塞,直到主线程将我们制定的代码块执行完。
    注意:

    • 当前线程为主线程的时候,waitUntilDone:YES参数无效。
    • 该方法,没有返回值
    • 该方法主要用来用主线程来修改页面UI的状态。

    3.常驻线程
    应用场景:经常在后台进行耗时操作,如:监控联网状态,扫描沙盒等 不希望线程处理完事件就销毁,保持常驻状态

    - (void)run
    {
      //addPort:添加端口(就是source)  forMode:设置模式
       [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
      //启动RunLoop
        [[NSRunLoop currentRunLoop] run];
     /*
      //另外两种启动方式
        [NSDate distantFuture]:遥远的未来  这种写法跟上面的run是一个意思
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        不设置模式
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
      */
    }
    

    退出-退出当前线程:

    [NSThread exit];
    
    

    结论:虽然RunLoop很难接触到,但是项目中也是经常出现的,关于NSTimer定时器的问题,我们每次还都是与其打交道的。

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

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

    相关文章

      网友评论

          本文标题:RunLoop第一讲

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