iOS 进阶 - RUNLOOP 运行循环

1、什么是 RUNLOOP?

RUNLOOP其实就是一个运行循环。基本作用是保持程序的持续运行,处理App的各种事件(比如:selector事件、触摸事件、定时器事件)。好处是节约cup资源,该工作的时候工作,该休息的时候休息。其实RUNLOOP就是相当于以下代码:

int main(int argc , char * argv[]){
     BOOL runing = YES;
     do {
          // 执行各种任务和处理事件
     }while(running);
     return 0;
}

其实UIApplicationMain中就是启动了一个RUNLOOP循环,UIApplicationMain一直没有返回,以保证程序的持续运行。RUNLOOP默认是与主线程相关的。

2、iOS中如何是访问RUNLOOP?

一种是通过Foundation(NSRunLoop),另一种是通过Core Foundation(CFRunLoopRef)。

这两个都代表着RUNLOOP的对象。 其实NSRunLoop是对CFRunLoopRef的一层OC包装,所以要研究CFRunLoopRef的功能,CFRunLoopRef也是一个开源的框架。

3、RUNLOOP与线程有什么关系?

(1 )每条线程都有唯一的一个与之对应的RUNLOOP对象。

(2)主线程的RUNLOOP已经自动创建好了,子线程的RUNLOOP需要主动创建。

(3)RUNLOOP在第一次获取时创建,在线程结束时销毁。

4、如何获得RUNLOOP对象?

上述提到iOS中有2套API来访问和使用RUNLOOP。NSRnLoop和CFRunLoopRef都代表着RUNLOOP对象。

第一种:Foundation

(1)[NSRunLoop currentRunLoop];// 获得当前线程的RUNLOOP对象

(2)[NSRunLoop mainRunLoop];// 获得主线程的RUNLOOP对象

第二种:CoreFoundation

(1)CFRunLoopGetCurrent();// 获得当前线程的RUNLOOP对象

(2)CFRunLoopGetMain();// 获得主线程的RUNLOOP对象

- (void)viewDidLoad {
    [super viewDidLoad];
    //
    NSRunLoop * mainRunLoop = [NSRunLoop mainRunLoop];
    NSRunLoop * currentRunLoop = [NSRunLoop currentRunLoop];
    NSLog(@"%@ \n\n %@" , mainRunLoop , currentRunLoop);
    //
    CFRunLoopRef currentRun = CFRunLoopGetCurrent();
    CFRunLoopRef mainRun = CFRunLoopGetMain();
    NSLog(@"%@ \n\n %@" , mainRun , currentRun);
    //
    CFRunLoopRef tranferRunLoop = mainRunLoop.getCFRunLoop;
    NSLog(@"%@", tranferRunLoop);
    // RunLoop和线程的关系
    [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];

}

- (void)run {

    NSLog(@"%@",[NSThread currentThread]);
    // 如何创建子线程的runloop?使用[NSRunLoop currentRunLoop]。这是个懒加载,此方法会判断当前线程有没有存在子线程的runloop,如果没有会创建。

}
5、CoreFoundation中关于RUNLOOP的类有哪些?

下面五个类构成RUNLOOP的所有内容:

(1)CFRunLoopRef:RUNLOOP本身

(2)CFRunLoopModeRef:Mode运行模式

(3)CFRunLoopSourceRef:Source事件源/输入源

(4)CFRunLoopTimerRef:Timer定时器

(5)CFRunLoopObserverRef:Observer监听状态

6、CoreFoundation里面五个类之间的有什么关系?

(1)每一个RUNLOOP有多种运行模式CFRunLoopModeRef。但只能启动选择一种运行模式CFRunLoopModeRef,此时这个Mode被称为CurrentMode。

(2)如果需要切换运行模式CFRunLoopModeRef,只能先退出CFRunLoopRef,再重新选择运行模式CFRunLoopModeRef。目的是分开不同组的CFRunLoopSourceRef、CFRunLoopTimerRef和CFRunLoopObserverRef。

(3)每种运行模式CFRunLoopModeRef至少必须有一个CFRunLoopSourceRef、一个CFRunLoopTimerRef、一个CFRunLoopObserverRef。因为Mode运行模式里面真正执行的就是Source。

(4)CFRunLoopObserverRef只是监听状态,没有实际作用。一般可以在需要的某个状态下执行事件或方法。

7、iOS开发中常见的CFRunLoopModeRef运行模式默认注册有哪五种?

(1)kCFRunLoopDefaultMode:这是App默认的运行模式,通常主线程在这个CFRunLoopModeRef下运行。

(2)UITrackingRunLoopMode:界面跟踪的运行模式,用于UIScrollVIew触摸滑动,保证界面不受其他运行模式影响。

(3)UIInitializationRunLoopMode:界面初始化的运行模式,在第一次启动App时运行该模式,启动完成后不再使用。

(4)GSEventReceiveRunLoopMode:接受系统时间的运行模式,基本用不到。

(5)kCFRunLoopCommonModes:这是一个占位用的运行模式,不是一个真正的运行模式。

8、如何实现在多模式下的正常运行?

第一种方法:通过 forMode: 将RUNLOOP添加进多种Mode。

第二种方法:通过 forMode: 将RUNLOOP添加进多种kCFRunLoopCommonModes。

- (void)run {
    //  打印当前线程
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"%@",[NSRunLoop currentRunLoop].currentMode);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

#warning 在主线程中创建runloop

    // 1.创建定时器,该方法内部会自动添加到runloop里面,并且设置运行模式为默认模式NSDefaultRunLoopMode。
    NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    // 2.添加定时器到runloop中,第一个参数是定时器,第二个参数是运行模式
    // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

    // 当拖拽时会进入界面追踪模式,其他的运行模式便不再理会(包括定时器里面的模式)。把手松开时会进入定时器模式。如果需要在界面拖拽是工作,只需要把定时器模式改为UITrackingRunLoopMode界面追踪模式。
    // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    // 如果要定时器在默认运行模式下和拖拽运行模式下都运行 那么就有两种方法:

    // 第一种:
    // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    // 第二种:在这里 NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode。凡是添加NSRunLoopCommonModes中的时间都会被同时common标签的运行模式上。例如timmer里面就会有NSDefaultRunLoopMode和UITrackingRunLoopMode都被打上common标记。
     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

#warning 在子线程中创建runloop

    NSRunLoop * currentRL = [NSRunLoop currentRunLoop];
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    // 子线程的runloop是需要开启的
    [currentRL run];
    // [currentRL runUntilDate:<#(nonnull NSDate *)#>];
    // [currentRL runMode:(nonnull NSString *) beforeDate:<#(nonnull NSDate *)#>];

}
9、CFRunLoopObserverRef是观察者模式监听到RUNLOOP的哪些时间点?

(1)kCFRunLoopEntry:即将进入Loop

(2)kCFRunLoopBeforeTimers:即将处理Timer

(3)kCFRunLoopBeforeSources:即将处理Scource

(4)kCFRunLoopBeforeWaiting:即将进入休眠

(5)kCFRunLoopAfterWaiting:刚从休眠中唤醒

(6)kCFRunLoopExit:即将推出Loop

(7)kCFRunLoopAllActivities:表示所有状态

#import "ViewController.h"

@interface ViewController ()
// 必须要有强引用,不然对象会被释放
@property (nonatomic , strong) dispatch_source_t timer ;
@end

@implementation ViewController

-(void)viewDidLoad {

}
-(void)observe{

    // 下面两个runloop添加观察者不是等同的
    // [[NSRunLoop currentRunLoop] addObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(nullable void *)#>];
    // [CFRunLoopAddObserver(<#CFRunLoopRef rl#>, <#CFRunLoopObserverRef observer#>, <#CFStringRef mode#>)];

    // 下面两个runloop添加时间是等同的
    // [[NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSString *)#>];
    // [CFRunLoopAddTimer(<#CFRunLoopRef rl#>, <#CFRunLoopTimerRef timer#>, <#CFStringRef mode#>)];

    // 1.创建监听者
    // 1.1 函数调用的方式监听状态改变
    // CFRunLoopObserverRef observer = CFRunLoopObserverCreate(<#CFAllocatorRef allocator#>, <#CFOptionFlags activities#>, <#Boolean repeats#>, <#CFIndex order#>, <#CFRunLoopObserverCallBack callout#>, <#CFRunLoopObserverContext *context#>)
    // 1.2 函数调用的模块监听状态改变
    // 参数一:怎么分配存储空间,CFAllocatorGetDefault()默认方式
    // 参数二:要监听的状态,kCFRunLoopAllActivities表示监听所有状态改变
    // 参数三:是否要持续监听,YES持续
    // 参数四:优先级
    // 参数五:当状态改变时(五种状态),会回调该方法
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        // 判断runloop的五种状态
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入Loop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理Scource");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将推出Loop");
                break;
            default:
                break;
        }
    });

    // 添加监听者
    // 参数一:要监听哪个runloop,CFRunLoopGetCurrent()指的是当前runloop对象
    // 参数二:观察者observer
    // 参数三:运行模式,不能传OC的运行模式。
    //    NSDefaultRunLoopModes == kCFRunLoopDefaultModes
    //    NSRunLoopCommonModes == kCFRunLoopCommonModes
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self observe];
    // 两秒钟执行一次task
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
}

-(void)task {
    NSLog(@"%s",__func__);

//2016-08-30 22:55:34.345 testOne[21518:571296] 刚从休眠中唤醒
//2016-08-30 22:55:34.345 testOne[21518:571296] -[ViewController task]
//2016-08-30 22:55:34.346 testOne[21518:571296] 即将处理Timer
//2016-08-30 22:55:34.346 testOne[21518:571296] 即将处理Scource
//2016-08-30 22:55:34.346 testOne[21518:571296] 即将进入休眠

}

@end
10、RUNLOOP在开发中的使用

(1)开启常驻线程(开发过程中,如果需要保持子线程的生命周期一直处于工作状态)

#import "ViewController.h"
#import "UIButton+Categories.h"

@implementation ViewController{
    NSThread * _thread;
}

- (void)viewDidLoad
{
    CGFloat margin = 30;
    UIButton * keepThreadWork = [UIButton createButtonWithFrame:CGRectMake(100, 100, 120, 30) ImageName:nil selectedImageName:nil Target:self Action:@selector(keepThreadWorkClick) Title:@"保持线程运行" TitleColor:[UIColor blackColor]];

    UIButton * otherTask = [UIButton createButtonWithFrame:CGRectMake(100, CGRectGetMaxY(keepThreadWork.frame) + margin, 120, 30) ImageName:nil selectedImageName:nil Target:self Action:@selector(otherTaskClick) Title:@"开启其他事件" TitleColor:[UIColor blackColor]];

    [self.view addSubview:keepThreadWork];
    [self.view addSubview:otherTask];

}

// 保持线程运行
-(void)keepThreadWorkClick {
    _thread = [[NSThread alloc] initWithTarget:self selector:@selector(todoThread) object:nil];
    [_thread start];
}

// 开启其他事件
-(void)otherTaskClick {
    NSLog(@"----");
}

// 子线程执行事件
-(void)todoThread {
    [self performSelector:@selector(todoTask) onThread:_thread withObject:nil waitUntilDone:YES];
}

// 延迟执行的时间
-(void)todoTask {
    // [不可取]这样可以让线程不死,但是阻碍其他功能
    // while (true) {
          NSLog(@"currentThread = %@",[NSThread currentThread]);
    // }

    // 当前子线程对应的runloop,保持线程的工作状态仅限于子线程!
    NSRunLoop * runloop = [NSRunLoop currentRunLoop];

    // (1) 一般是网runloop里面添加source
    [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

    // (2) 10s内不再运行程序
    // [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];

    // [不可取]当前的runloop没有运行模式,需要为其添加timer,作用是为了不让runloop退出
    // [runloop addTimer:[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(add) userInfo:nil repeats:YES] forMode:NSDefaultRunLoopMode];

    // 默认没有开启,需要手动开启
    [runloop run];
}

//-(void)add {
//    NSLog(@"%s",__func__);
//}

@end

(2)控制定时器在某种运行模式下启动。

(3)可以天剑观察者Observer监听RUNLOOP的状态。比如在所有点击之前做事件统计。

(4)控制某种事件在特定的模式下执行。

11、RUNLOOP运行步骤有哪些?

(1)通知观察者Observer,即将进入RUNLOOP;

(2)通知观察者Observer,将要处理Timer;

(3)通知观察者Observer,将要处理事件源Source();

(4)处理事件源Source();

(5)如果有新的事件源Source,则跳转到第(2)步继续运行;

(6)通知观察者Observer,时间快结束了线程即将休眠;

(7)休眠,等待唤醒,这时候可以手动唤醒、Timer定时器唤醒...;

(8)通知观察者Observer,有新事件线程刚被唤醒;

(9)处理唤醒时收到的消息,之后跳转到第(2)步继续运行;

(10)通知观察者Observer,即将推出RUNLOOP。

12、RUNLOOP的自动释放池的作用是什么?

 

13、什么时候自动释放池什么时候释放?

(1)第一次创建:自动释放池是在RUNLOOP运行步骤的第(1)步时创建,用于装载不需要的变量。

(2)最后一次销毁:RUNLOOP退出的时候,即是在RUNLOOP运行步骤的第(10)步时销毁。

(3)其他时候的创建和销毁:当他即将进入休眠时,会把之前装载的变量全部释放掉,同时创建一个新的自动释放池。休眠意味着所有Timer、Source已经处理完毕了。

 

 

 

转载于:https://my.oschina.net/linweida/blog/742898

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值