NSRunLoop

作者: 杨大虾 | 来源:发表于2017-07-29 13:55 被阅读10次

    【iOS程序启动与运转】- RunLoop个人小结

    RunLoop总结:RunLoop的应用场景(三)

    走进Run Loop的世界 (一):什么是Run Loop?这篇比较好

    RunLoop机制理解这篇比较好

    iOS之runLoop详解这篇比较好

    深入理解RunLoop

    一、浅识RunLoop

    RunLoop其实在开发中一直在用,。(不知道大家有没有想过这个问题,一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生。给我们的感觉就像应用一直处于随时待命的状态,在没人操作的时候它一直在休息,在让它干活的时候,它就能立刻响应。其实,这就是run loop的功劳。)要想理解RunLoop,首先我们需要先了解一下程序运行机制。

    程序运行机制:我们都知道OC是运行时语言,也就是说对象的类型是在程序运行的时候确定的。并调用类与对象相应的方法。但是最终代码的执行始终是面向过程的。线程也是一样:一个线程从开始代码执行,到结束代码销毁。app如何实现这样的机制:app从运行开始一直处于待命状态,接收到事件之后执行操作,操作完成后继续等待相应,直到程序终止运行。这样的管理线程执行任务的机制就是RunLoop机制。线程在执行中的休眠和激活就是由RunLoop对象进行管理的

    也就是说,runLoop其实是自动的?

    二、RunLoop与线程的关系

    RunLoop是用来管理线程的----------->
    每一个线程都有一个RunLoop对象。
    可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。
    Run loops是线程的基础架构部分,Cocoa和CoreFundation都提供了run loop对象方便配置和管理线程的run loop(以下都已Cocoa为例)。每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。可以通过具体的方法去获得。但是需要注意:虽然每一个线程都可以获取RUnLoop对象,但是并不是每一个线程中都有实例对象,我们可以这样理解:如果我们不获取RunLoop,这个RunLoop就不存在,我们获取时,如果不存在,就会去创建。在主线程中,这个MainRunLoop是默认创建并运行激活的。

    也就是说,runLoop是用来管理线程的,我们可以通过获取runLoop来控制该线程?第二,mainRunLoop是肯定有的,其他线程不一定有runLoop,那么我可以理解为,没runLoop对象就是该线程没人管 ?

    三. NSRunLoop是Cocoa框架中的类,与之对应的是在Core Fundation中有一个CFRunLoopRef类。这两者的区别是前者不是线程安全的,而CFRunLoopRef是线程安全的。
    线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
    比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
    在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
    而如果是在多线程情况下,比如有两个线程,线程 A 先将元素1存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B向此 ArrayList 添加元素2,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值,结果Size等于2。
    那好,我们来看看 ArrayList 的情况,期望的元素应该有2个,而实际只有一个元素,造成丢失元素,而且Size 等于 2。这就是“线程不安全”了。

    简而言之,线程不安全即是某线程可能还没走完,就被暂停了,cpu去执行其它的线程,效果上可能会造成同一个内存上的存值出现叠加,进而出现貌似值丢失等现象。

    线程安全问题都是由全局变量及静态变量引起的。
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    所以这也是

    static NSString *const TEST_STRING = @"";
    

    TEST_STRING写成不可变字符串的意义,因为其是全局的,要确保其只读不写。

    四,定时器的执行,其实并不是按照时间段额间隔进行调用方法,而是在定时器注册到RunLoop中后,RunLoop会设置一个一个的时间点进行调用,例如,5,10,15,20等等。如果错过了某个时间点,定时器并不会延迟调用,而是直接等待下一个时间点调用,所以定时器并不是准确的。

    在runLoop中加入定时器,其实是runLoop设置好了时间点,然控制线程在某个时间点上去执行某个方法,至于“如果错过了某个时间点,定时器并不会延迟调用,而是直接等待下一个时间点调用”貌似可以理解为,线程不会“回头”(单向性)。

    五。 RunLoop的调用

    一般情况下我们很少去显式调用或者启动RunLoop(自动),但是下边的情况需要手动设置。

    总得来说:
    我们应该只在创建辅助线程的时候,才显示的运行一个Run Loop。对于辅助线程,我们仍然需要判断是否需要启动Run Loop。比如我们使用一个线程去处理一个预先定义的长时间的任务,我们应当避免启动Run Loop。下面是官方Document提供的使用Run Loop的几个场景:
    需要使用Port-Based Input Source或者Custom Input Source和其他线程通讯时
    需要在线程中使用Timer
    需要在线程中使用上文中提到的selector相关方法
    需要让线程执行周期性的工作

    举例:
    1、在分线程中使用定时器
    定时器的实现是基于RunLoop的,平时我们使用定时器或许并没有对RunLoop做什么操作,那是因为主线程的RunLoop默认是开启运行的,如果我们进行如下操作:

    -(void)viewDidLoad{
      [super viewDidLoad];
      dispatch_queue_t queue = dispatch_queue_create("myQueue",DISPATCH_QUEUE_CONCURRENT);
     dispatch_async(queue,^{
        NSTimer *timer = [NSTimer scheduledTimerWithTimerInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES];
    });     
    }
    -(void)time{
    NSLog(@"runTimer");
    }
    

    此时运行,控制台不会输出runTimer。我们必须在线程中手动的执行如下代码:
    [[NSRunLoop currentRunLoop] run];
    这样定时器才可以正常工作。

    感觉上有几种想法,一,要么上述的分线程没有runLoop,所以上面的方法类似于创建runLoop。二,如果说打脸,其实上面是有线程的,那么上面的run方法,就颇有歧义了,不像调用runloop,貌似是在添加,添加定时器。

    2、当线程中使用如下的方法时

    某些延迟函数和选择器在分线程中的使用,我们必须手动开始RunLoop

    @interface NSObject (NSDelayedPerforming)
    
    - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
    
    - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
    
    + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
    
    + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
    
    
    - (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
    
    - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)arg;
    - (void)cancelPerformSelectorsWithTarget:(id)target;
    

    总感觉runLoop这货就跟分线程有关

    3.其他

    输入源被注册到RunLoop中时会有方法进行remove。但是定时器没有remove,但是它的invalidate方法可以将其从RunLoop中移除。invalidate是重要的也是唯一的将定时器从RunLoop中注销的方法,所以如果我们创建了定时器,就一定要在不使用的时候调用invalidate方法。

    invalidate仅仅是移除

    六。 对其它线程来说,run loop默认是没有启动的
    对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

    线程跟runLoop没有绝对的联系,子线程可以有,但是子runLoop不一定有,(主的一定有)假如,我想控制子线程了,那么就要启动子runLoop进行控制?

    七。 Run loop的优点

    一个run loop就是一个运行着 的事件处理循环,用来不停的监听和处理输入事件并将其分配到对应的目标上进行处理。如果仅仅是想实现这个功能,你可能会想一个简单的while循环不就可以实现了吗,用得着费老大劲来做个那么复杂的机制?显然,苹果的架构设计师不是吃干饭的,你想到的他们早就想过了。
    首先,NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,即RunLoop有两种接收事件的方式:1.是input sources 2.是Timer sources。
    其次,也是很重要的一点,使用run loop可以使你的线程在有工作的时候工作,没有工作的时候休眠,如果有事件发生,Run Loop就处理事件并通知相关的Observer。这可以大大节省系统资源。

    说白了,runloop就是 while循环的升级和封装?

    相关文章

      网友评论

          本文标题:NSRunLoop

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