iOS 【Mutithreading-NSRunLoop 运行循环】

iOS_multithreading 专栏收录该内容
11 篇文章 0 订阅
//
//  ViewController.m
//  0713-04NSRunLoop-01
//
//  Created by 王中尧 on 16/7/12.
//  Copyright © 2016年 wzy. All rights reserved.
//

/*
 NSRunLoop(运行循环) 和 线程 是一一对应的关系,因为底层实现是字典
 
 一、基本作用(作用重大)
 a 保持程序的持续运行(ios程序为什么能一直活着不会死,死循环)
 b 处理app中的各种事件(比如触摸事件、定时器事件(NSTimer)、selector事件(performSelector)
 c 节省CPU资源,提高程序性能,有事情就做事情,没事情就休息
 
 二、重要说明
 (1)如果没有Runloop,那么程序一启动就会退出,什么事情都做不了。
 (2)如果有了Runloop,那么相当于在内部有一个死循环,能够保证程序的持续运行
 (2)main函数中的Runloop
 a 在UIApplication函数内部就启动了一个Runloop,该函数返回一个int类型的值
 b 这个默认启动的Runloop是跟主线程相关联的
 
 三、Runloop对象
 (1)在iOS开发中有两套api来访问Runloop
 a.foundation框架【NSRunloop】
 b.core foundation框架【CFRunloopRef】
 (2)NSRunLoop和CFRunLoopRef都代表着RunLoop对象,它们是等价的,可以互相转换
 (3)NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)
 
 四、Runloop与线程
 (1)Runloop和线程的关系:一个Runloop对应着一条唯一的线程
 问题:如何让子线程不死
 回答:给这条子线程开启一个Runloop
 (2)Runloop的创建:主线程Runloop已经创建好了,子线程的runloop需要手动创建
 (3)Runloop的生命周期:在第一次获取时创建,在线程结束时销毁

 五、获得Runloop对象
 (1)获得当前Runloop对象
 //01 NSRunloop
 NSRunLoop * runloop1 = [NSRunLoop currentRunLoop];
 //02 CFRunLoopRef
 CFRunLoopRef runloop2 =   CFRunLoopGetCurrent();
 
 (2)拿到当前应用程序的主Runloop(主线程对应的Runloop)
 //01 NSRunloop
 NSRunLoop * runloop1 = [NSRunLoop mainRunLoop];
 //02 CFRunLoopRef
 CFRunLoopRef runloop2 =   CFRunLoopGetMain();
 
 (3)注意点:开一个子线程创建runloop,不是通过alloc init方法创建,而是直接通过调用currentRunLoop方法来创建,它本身是一个懒加载的。
 (4)在子线程中,如果不主动获取Runloop的话,那么子线程内部是不会创建Runloop的。可以下载CFRunloopRef的源码,搜索_CFRunloopGet0,查看代码。
 (5)Runloop对象是利用字典来进行存储,而且key是对应的线程,Value为该线程对应的Runloop。
 
 六、Runloop应用
 1)NSTimer
 2)ImageView显示:控制方法在特定的模式下可用
 3)PerformSelector
 4)常驻线程:在子线程中开启一个runloop
 5)自动释放池
 第一次创建:进入runloop的时候
 最后一次释放:runloop退出的时候
 其它创建和释放:当runloop即将休眠的时候会把之前的自动释放池释放,然后重新创建一个新的释放池

 七、五个相关的类(重点去记忆)
        a.CFRunloopRef
        b.CFRunloopModeRef【Runloop的运行模式】(下面有写 5种)
        c.CFRunloopSourceRef【Runloop要处理的事件源】(下面有写)
        d.CFRunloopTimerRef【Timer事件】(下面有写 2种)
        e.CFRunloopObserverRef【Runloop的观察者(监听者)】(下面有写)

 */

/* CFRunloopTimerRef-NSTimer */
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [self time1];
    
    // 创建一个新线程去执行操作
//    [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil];
    
    [self time3];
}

- (void)time1 {
    // 创建定时器(两种方法)
    // 创建定时器方法一:该方法内部会自动将创建的定时器对象添加到当前的runloop中,并且指定runloop的运行模式为默认(主线程的runLoop已经创建好了)
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task1) userInfo:nil repeats:YES];
}

- (void)time2 {
    // 如果该方法在子线程调用,则不会执行,因为子线程的runloop是没有创建出来的,需要手动 创建 和 开启
//    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task1) userInfo:nil repeats:YES];
    
    // 创建定时器方法二:该方法只是单纯创建了一个定时器,但不会添加到当前的运行循环中
//    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(task1) userInfo:nil repeats:YES];
    
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task1) userInfo:nil repeats:YES]; // 如果使用方法一创建定时器在子线程中运作,那么只需要创建子线程的运行循环并且开启运行循环就好了,不需要添加运行循环了
    
    NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // 此方法是懒加载创建运行循环。由于主线程已经默认创建好了一个,所以说主线程中调用此方法的作用是拿到运行循环。而在此处是子线程中调用该方法,由于子线程没有默认的运行循环,所以这条代码的作用为创建出一条运行循环并返回。
//    [runloop addTimer:timer forMode:NSDefaultRunLoopMode]; // 将定时器添加到运行循环中
    [runloop run];
}

- (void)time3 {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task1) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; // 该行等于上面两行
    
    /*
     aRunloop要想跑起来,它的内部必须要有一个mode,这个mode里面必须有source\observer\timer,至少要有其中的一个。
     01.CFRunloopModeRef代表着Runloop的运行模式
     02.一个Runloop中可以有多个mode,一个mode里面又可以有多个source\observer\timer等等
     03.每次runloop启动的时候,只能指定一个mode,这个mode被称为该Runloop的当前mode
     04.如果需要切换mode,只能先退出当前Runloop,再重新指定一个mode进入
     05.这样做主要是为了分割不同组的定时器等,让他们相互之间不受影响
     06.系统默认注册了5个mode
     
     // CFRunloopModeRef     
     a.kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
     b.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
     c.UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
     d.GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
     e.kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
     */
}

- (void)task1 {
    NSLog(@"计时器正在运行。。。");
    NSLog(@"task1-----%@",[NSRunLoop currentRunLoop].currentMode); // 在静止状态和拖拽状态下输出两种不同的运行循环的名称
}

@end


/* CFRunloopTimeRef-GCD定时器 */
#import "ViewController.h"

@interface ViewController ()

// 注意:dispatch_source_t本质上是OC类,在这里是个局部变量,需要强引用(但没有星号)
@property (nonatomic, strong) dispatch_source_t timer;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //01 创建定时器对象 dispatch_source_t timer
    /*
     第一个参数:要创建的是一个定时器(type类型可选,这里我们选的是TIMER)
     第二个参数:默认总是传0 描述信息
     第三个参数:队列  决定下方的代码块(dispatch_source_set_event_handler)在哪个线程中调用(主队列-主线程)
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    //02 设置定时器 dispatch_source_set_timer
    /*
     第一个参数:定时器对象
     第二个参数:定时器开始计时的时间(开始时间)
     第三个参数:设置间隔时间 GCD的时间单位:纳秒
     第四个参数:精准度(0表示绝对精准。如果传大于0的数值,则表示该定时切换i可以接收该值范围内的误差,通常传0。
              该参数的意义:可以适当的提高程序的性能)
     */
    dispatch_time_t t = DISPATCH_TIME_NOW + 2.0 * NSEC_PER_SEC; // 因为单位是 纳秒,所以说要乘以10的9次幂,也就是这个宏 NSEC_PER_SEC。意为在当前时间2秒后调用定时器
    dispatch_source_set_timer(timer, t, 3.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    // GCD定时器到时间后会调用的任务block(第一个参数传哪个GCD定时器)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"调用gcd定时器");
    });
    
    // 恢复执行(GCD定时器创建出来默认是)
    dispatch_resume(timer);
    
    self.timer = timer;
}

@end

/* CFRunloopSourceRef */
(1)是事件源也就是输入源,有两种分类模式;
a.一种是按照苹果官方文档进行划分的
b.另一种是基于函数的调用栈来进行划分的(source0和source1)。
(2)具体的分类情况
a.以前的分法
     Port-Based Sources
     Custom Input Sources
     Cocoa Perform Selector Sources
     b.现在的分法
  Source0:非基于Port的(用户主动触发的事件)
      Source1:基于Port的(系统相关的事件)
(3)可以通过打断点的方式查看一个方法的函数调用栈

/* CFRunloopObserverRef */
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //01 创建监听者
    /*
     第一个参数:分配存储空间 CFAllocatorGetDefault
     第二个参数:要监听的状态
     第三个参数:时候要持续监听
     第四个参数:和优先级相关 0
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        //当发现监听对象状态改变的时候调用该block
        /*
         kCFRunLoopEntry = (1UL << 0),
         kCFRunLoopBeforeTimers = (1UL << 1),
         kCFRunLoopBeforeSources = (1UL << 2),
         kCFRunLoopBeforeWaiting = (1UL << 5),
         kCFRunLoopAfterWaiting = (1UL << 6),
         kCFRunLoopExit = (1UL << 7),
         kCFRunLoopAllActivities = 0x0FFFFFFFU
         */
        
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"runloop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"runloop即将处理timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"runloop即将处理source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"runloop进入到休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"runloop被唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"runloop退出");
                break;
                
            default:
                break;
        }

    });
    
    //02 设置监听(为runloop添加一个监听者)
    /*
     第一个参数:要监听的runloop对象
     第二个参数:监听者对象本身
     第三个参数:runloop的运行模式
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
//    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, NSDefaultRunLoopMode);
}

@end


 

Run Loop 的事件队列

/* 下面的一段C代码可以很好的表述上图的循环过程 */
#import <Foundation/Foundation.h>

void msg(int n)
{
    NSLog(@"runloop被唤醒");
    NSLog(@"runloop处理%zd事件",n);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        NSLog(@"runloop启动了");
        do {
            
            NSLog(@"runloop即将处理timer事件");
            NSLog(@"runloop即将处理source0事件");
            NSLog(@"source1事件");
            NSLog(@"runloop询问:还有事件需要我处理吗?");
            NSLog(@"runloop计入到休眠状态");
            
            int number = 0;
            scanf("%zd",&number);
            msg(number);
            
            
        } while (1);
    }
    return 0;
}

  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值