iOS开发-RunLoop

2020-06-02 22:44:40 浏览数 (3)

RunLoop

从字面意思来看:跑圈、运动循环 基本用法:保持程序持续运行、处理App中的各种事件(触摸事件、定时器事件、SEL等等) 为什么需要它:节省CPU资源、 提高性能

如果没有RunLoop,程序在执行到7行就结束了。

代码语言:javascript复制
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

有Runloop后,程序就相当于一直在做循环

在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)

代码语言:javascript复制
int main(int argc, const char * argv[]) {
   
    BOOL running = YES;
    do{
        // 执行各种任务,处理各种时间
        
    }while (running);
    
    return 0;
}

程序中的Runloop----UIApplicationMain

代码语言:javascript复制
int main(int argc, char *argv[]) 
{
       @autoreleasepool { 
              return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 
       }
 }

1.在UIApplicationMain函数内部就启动了一个RunLoop 2.UIApplicationMain函数一直没有返回,保持了程序的持续运行 3.这个默认启动的RunLoop是主线程关联的。 4.一个线程对应一个RunLoop,主线程的RunLoop默认已经启动 5.子线程的RunLoop得手动启动(调用run方法) 6.RunLoop只能选择一个Mode启动,如果当前Mode中没有任Source、Timer、Observer,那么就直接退出RunLoop

RunLoop里面有两套api用来访问和使用RunLoop 1、Foundation--NSRunLoop 2、Core Foundation --- CFRunloopRef 二者异同点: NSRunLoop和CFRunloopRef都代表RunLoop对象,NSRunLoop是对CFRunloopRef一层OC的封装

RunLoop与线程:

每条线程都有一个RunLoop对象,主线程默认已经创建好了,子线程需要主动创建 Runloop在第一次获取时创建,在线程结束后销毁。

代码语言:javascript复制
   /* 1. Foundation  */
    // 获取当前线程
    NSRunLoop *roop = [NSRunLoop currentRunLoop];
    // 获取主线程
    [NSRunLoop mainRunLoop];

   /*2. Core Foundation  */
    // 获取当期线程
    CFRunLoopGetCurrent();
    // 获取主线程
    CFRunLoopGetMain();

RunLoop相关类(Runloop中如果没有Source,Observre,Timer,Mode,就会结束)

代码语言:javascript复制
    // Runloop对象
    CFRunLoopRef;
 
    // Runloop事件源(数据源)
    CFRunLoopSourceRef;
    
    // Runloop观察者
    CFRunLoopObserverRef;
    
    // Runloop时间源
    CFRunLoopTimerRef;

    // Runloop模式
    CFRunloopModeRef;

Runloop里面相关类的互相关系-

Paste_Image.png

代码语言:javascript复制
    // 获取当前Runloop的模式
    NSString *runloopMode = [NSRunLoop currentRunLoop].currentMode;

1.同一时间只可以运行其中的一种model 2.切换Model只能退出Runloop,重新进入 3.系统默认注册了5个Mode

代码语言:javascript复制
//App默认的Mode,通常主线程是在这个Mode下运行
NSDefaultRunLoopMode:
//界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
UITrackingRunLoopMode
//这是一个占位用的Mode,不是一种正真的Mode
NSRunLoopCommonModes

//在刚启动App进入的第一个Mode,启动完成以后就不再使用
UIInitializationRunLoopMode
//接收系统事件的内部Mode,通常用不到
GSEventReceiveRunLoopMode

CFRunLoopTimerRef

CFRunLoopTimerRef是基于时间的触发器

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

代码语言:javascript复制
NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
    
// 定时器会跑在标记为common modes的模式下
// 标记为common modes的模式:包含:UITrackingRunLoopMode和kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];

GCD不受RunLoop的Mode影响

代码语言:javascript复制
    // 获取一个全局并发队列
    /*
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 1. 设置定时器
    // dispatchQueue :决定了将来回调的方法在哪里执行。
    // dispatch_source_t timer  是一个OC对象
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 2.指定定时期开始的时间和间隔的时间
    // DISPATCH_TIME_NOW :第2个参数:定时器开始时间   (也可以抽出来,设置间隔时间)
        dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 1.0 *NSEC_PER_SEC);
    
    // intervalInSeconds :第三个参数:定时器开始后的间隔时间
    // leewayInSeconds:第四个参数:间隔精准度,0代标最精准,传入一个大于0的数,代表多少秒的范围是可以接收的,主要为了提高程序性能,积攒一定的时间,Runloop执行完任务会睡觉,这个方法让他多睡一会,积攒时间,任务也就相应多了一点,而后一起执行
    // 开始时间

    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    // 3.指定定时器回调方法
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"___________");
    });
    // 开启定时器
    dispatch_resume(timer);

CFRunLoopSourceRef(事件源、输入源)

代码语言:javascript复制
Port-Based Sources (端口)
Custom Input (自定义事件)
Cocoa Perform Selector Sources 

按照函数的调用栈 Source0:非基于Port的 Source1:基于Port 通过内核和其他线程通信,接收分发系统事件

CFRunLoopObserverRef(观察者)

能够监听RunLoop的状态改变

Paste_Image.png

CFRunLoopObserverRef

代码语言:javascript复制
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),      // 1
    kCFRunLoopBeforeTimers = (1UL << 1), // 2
    kCFRunLoopBeforeSources = (1UL << 2), // 4
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32
    kCFRunLoopAfterWaiting = (1UL << 6), // 64
    kCFRunLoopExit = (1UL << 7),       // 128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

创建一个Observer

代码语言:javascript复制
 /*
     第1个参数:如何给Observer分配存储空间
     第2个参数:需要监听的状态类型/kCFRunLoopAllActivities监听所有状态
     第3个参数:是否每次需要监听?
     第4个参数:优先级(0)
     第5个参数:监听状态改变后的回调
     */
    
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入Runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出RunLoop");
                break;   
            default:
                break;
        }
    });
    /*
    第1个参数:要给那个RunLoop添加观察者
    第2个参数:添加的Observer对象
    第3个参数:在那种模式下监听
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);

    //  释放对象
    CFRelease(observer);

由于ARC只管理Foundation框架的内容,所以我们在Core Foundation 框架创建的对象必须手动释放。 规律: 凡是带有copy、create、retain等字眼的函数,创建出来的CF对象,都需要在最后做一次release

官方对于RunLoop的解释:

Paste_Image.png

RunLoop处理逻辑,整理:自动释放池的生命周期

RunLoop在进入这个 kCFRunLoopBeforeWaiting时,会对自动释放池销毁

Paste_Image.png

Runloop:在开发中有什么作用?

1.NSTimer 2.ImageView的显示 3.PerformSelector 4.常驻线程 5.自动释放池

PerformSelector

代码语言:javascript复制
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{  
  /*
     - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
     */
    // 指定模式下进行特定的操作
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"rightPic"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}

常驻线程: 默认情况下,一个线程只能使用一次,也就是只能执行一个操作 我们需要做得到就是强引用,保留线程,同时添加Source Or Timer,注:系统只会监测Source Or Timer,不会检查Observer

1.子线程的Runloop需要手动创建 2.子线程的Runloop需要手动开启 3.如果子线程的NSRunLoop没有设置Source Or Timer 那么子线程会立刻关闭。

代码语言:javascript复制
- (void)ViewDidLoad
{
    [super viewDidLoad];
    SYThread *thread = [[SYThread alloc] initWithTarget:self selector:@selector(show) object:nil];
    self.thread = thread;
    [thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:@"时间计算" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
}

- (void)show
{
    NSLog(@"%s", __func__);
    // 这句话并没有意义,只是保证线程不死
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
    // 上面线程不死,这句话永远不会打印
    NSLog(@"-------$$$$");
}
- (void)test
{
    NSLog(@"11111");
}

1 人点赞