iOS多线程-RunLoop简介

  • 什么是RunLoop?

    • 从字面上来看是运行循环的意思.

    • 内部就是一个do{}while循环,在这个循环里内部不断的处理各类任务(好比:source/timer/Observer)

    • RunLoop的存在其实就是为线程而存在的.线程的做用就是执行一个特定的任务,可是默认状况下线程执行完任务后就不能再次执行任务,这是由于默认状况下线程是没有开启RunLoop的.若是开启RunLoop以后,线程执行完任务以后,会一直等待,直到再次接受到任务,接续执行任务.线程销毁前,会先释放这个线程所对应的RunLoop.

  • RunLoop基本做用

    • 保持程序的持续运行,保持线程的持续运行.

    • 处理App中的各类事件(好比触摸事件,定时器事件,Selector事件)

    • 节省CPU资源,提升程序性能:该作事时作事,该休息时休息

  • RunLoop对象

    • ios中有2套API来访问和使用RunLoop

    • 一套是Fundation(纯OC的)框架中的

      • NSRunLoop
        // 得到当前线程的RunLoop对象 [NSRunLoop currentRunLoop]; // 得到主线程的RunLoop对象 [NSRunLoop mainRunLoop];
    • 一套是Core Fundation(纯C语言的)框架中的

      • CFRunLoopRef
        // 得到当前线程的RunLoop对象 CFRunLoopGetCurrent(); // 得到主线程的RunLoop对象 CFRunLoopGetMain();
    • NSRunLoo和CFRunLoopRef都表明着RunLoop对象.NSRunLoop是基于CFRunLoopRef的一层OC包装

  • RunLoop与线程

    • 每条线程都有惟一的一个与之对应的RunLoop对象

    • 主线程的Runloop系统已经自动建立好了,子线程的RunLoop须要手动建立

    • RunLoop在第一次获取时由系统自动建立,在线程结束时销毁

    • 若是想给子线程建立RunLoop,不能直接alloc&init,只要调用获取当前线程RunLoop方法便可,系统会自动放回当前线程的RunLoop,若是当前线程没有RunLoop,系统会自动建立.

  • RunLoop相关类

  • Core Fundation中关于RunLoop的5个类

    • CFRunLoopRef: RunLoop对象
    • CFRunLoopModeRef: RunLoop运行模式.
    • CFRunLoopSoruceRef: 事件源(输入源)
    • CFRunLoopTimerRef:基于时间的触发器.
    • CFRunLoopObserverRef: 观察者,可以监听RunLoop的状态改变
  • CFRunLoopModeRef

  • CFRunLoopModeRef表明RunLoop运行模式

  • 一个RunLoop对象包含若干个Mode(模式),每一个Mode又包含若干个 source/Timer/Observer

  • RunLoop运行时,只能指定一个Mode, 这个Mode又称之为CurrentMode,而后RunLoo就执行CurrentMode中的source/Timer/Observer

  • 若是须要切换Mode,只能退出RunLoop,再从新指定一个Mode进入,这样作是为了分隔开不一样组的Source/Timer/Observer,让其不受影响

  • 系统默认注册了5个Mode:

    • NSDefaultRunLoopMode: App的默认Mode,一般主线程实在这个模式下运行
    • UITrackingRunLoopMode:界面跟踪Mode,用于界面控件(ScrollView,tableView等等)追踪触摸滑动,保证界面滑动时不受其余Mode影响
    • UIInitializationRunLoopMode:在刚启动App是进入的第一个Mode,启动完成后就再也不使用
    • GSEventReceiveRunLoopMode:接收系统事件的内部Mode,一般用不到
    • NSRunLoopCommonMode:这是一个占位的Mode,不是一种真正的Mode,(能够当作模式组,默认状况下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)两种模式.
  • CFRunLoopTimerRef

    • CFRunLoopTimerRef是基于时间的触发器

    • CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop的Mode影响

      -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //建立一个NSTimer定时器,默认状况下NSTimer是不会执行的,只有把NSTimer添加到RunLoop中,由RunLoop管理执行 NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES]; // 在当前线程中RunLoop添加一个timer, 并告诉runLoop, 这个timer只能在NSDefaultRunLoopMode模式下才能触发 // runLoop会找到NSDefaultRunLoopMode,而后把timer添加NSDefaultRunLoopMode中的Timer数组中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; //在当前线程中RunLoop添加一个timer, 并告诉runLoop, 这个timer只能在NSRunLoopCommonModes模式下才能触发 //runLoop会找到NSDefaultRunLoopMode和UITrackingRunLoopMode //而后把timer添加NSDefaultRunLoopMode和UITrackingRunLoopMode中的Timer数组中 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //利用此方法建立的NSTimer, 系统会自动放入当前线程中的currentRunLoop中,而且只能在NSDefaultRunLoop模式下才能触发 NSTimer * timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES]; //虽然经过类方法scheduledTimerWithTimeInterval建立NSTimer,会自动添加到NSDefaultRunLoopMode模式中 //但咱们仍是能够修改它的模式 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }
  • CFRunLoopSoruceRef

    • 按照官方文档,source的分类:

      • Port-Based Sources:基于端口的事件源:监听程序响应的端口,基于端口事件是由系统内核自动发送的.
      • Custom Input Sources: 自定义输入源:监听自定义事件源,而自定义的输入源是须要人工从其余线程发送
      • Cocoa Perfrom Selector Source: selector事件源
    • 按照源码函数调用栈,source的分类:

      • Source0:非基于Prot(端口)的,是用户主动触发的事件
      • Source1:基于Prot(端口)的,经过内核和其余线程相互发送消息
  • CFRunLoopObserverRef

    • CFRunLoopObserverRef:观察者对象,能够监听RunLoop的状态

    • RunLoop状态:

      • kCFRunLoopEntry 即将进入runLoop
      • kCFRunLoopBeforeTimers 即将处理Timer
      • kCFRunLoopBeforeSources 即将处理source(事件源)
      • kCFRunLoopBeforeWaiting 即将进入休眠
      • kCFRunLoopAfterWaiting 即将从休眠中醒来
      • kCFRunLoopExit 即将退出runLoop
      -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //建立一个CFRunLoopObserverRef /* 第一个参数: CFRunLoopObserverRef(观察者)分配内存空间方式 第二个参数: 监听那些状态 kCFRunLoopAllActivities(监听全部状态) 第三个参数: 是否每次都要监听 第四个参数: 优先级 第五个参数: 回调函数 */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { // observer监听对象 //activity Runloop当前状态 }); /* 第一个参数: 为那个线程下的RunLoop添加CFRunLoopObserverRef(观察者) 第二个参数: 须要添加的CFRunLoopObserverRef(观察者) 第三个参数: 把监听添加到RunLoop那个模式中 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); //记得内存管理,由于Core Foundation不在ARC管理范围内 //带有Create、Copy、Retain等字眼的函数,建立出来的对象,都须要在最后作一次release //销毁对象函数:CFRelease对象 CFRelease(observer); }
  • RunLoop处理逻辑

  • 若是RunLoop中没有Timer或source,RunLoop就会马上退出

  • 每次运行RunLoop,RunLoop会自动处理以前未处理的消息,并通知相关观察者.具体顺序以下:

    • 1.通知观察者RunLoop已经启动

    • 2.通知观察者即将开始启动定时器

    • 3.通知观察者即将启动非基于端口的事件源

    • 4.启动任何准备好的非基于端口的事件源

    • 5.若是基于端口的事件源准备好并处于等待得状态,当即启动.并进入步骤9

    • 6.通知观察者线程进入休眠

    • 7.将线程置于休眠直到任意下面的事件发生:

      • 某一事件到达基于端口的源
      • 定时器启动
      • RunLoop设置的时间已经超时.(系统底层会给RunLoop设置一个超时时间,源码中设置的是:9999999999.0)
      • RunLoop被手动唤醒
    • 8.通知观察者线程将被唤醒.

    • 9.处理未处理的事件

      • 若是用户定义的定时器启动,处理定时器事件并重启RunLoop.进入步骤2
      • 若是事件源启动,传递相应的消息
      • 若是RunLooop被显示唤醒并且时间还没超时,重启RunLoop.进入步骤2
    • 10.通知观察者RunLoop结束.

  • 如何让子线程成为常驻线程(让一个子线程不进入消亡状态,等待其余线程发来消息,处理其余事件)

// // ViewControllerRunLoop.m // // Created by yuxuan on 16/2/17. // Copyright © 2016年 apple. All rights reserved. // #import "ViewControllerRunLoop.h" @interface ViewControllerRunLoop () @property(nonatomic,strong)NSThread * thread; @end @implementation ViewControllerRunLoop -(void)viewDidLoad{ //建立子线程执行任务 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [self.thread start]; } -(void)run{ NSLog(@"跑起来"); //默认状况下,子线程是不会常驻的 //只有子线程中runloop启动,而且runloop中有source或timer,才会常驻 //只有常驻线程才能再次执行任务,由于线程中有runloop来处理事件了 //子线程的runloop是须要手动建立的, 而且须要手动启动 NSRunLoop * rl = [NSRunLoop currentRunLoop]; //若是子线程的runloop没有 source / timer 的话, 哪么子线程的runloop会当即关闭 //在runLoop中添加一个timer [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerRun) userInfo:nil repeats:YES]; //启动runloop [rl run]; //若是线程成为了常驻线程,你会发现,不会执行到这行代码 //也就是说这个方法不会执行完, NSLog(@"end"); } -(void)timerRun{ NSLog(@"%s",__func__); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // 让子线程再次执行任务 [self performSelector:@selector(againRun) onThread:self.thread withObject:nil waitUntilDone:NO]; } -(void)againRun{ NSLog(@"再次跑起来"); } @end



原文连接:http://www.jianshu.com/p/94d61de9e139
相关文章
相关标签/搜索