RunLoop

作者: CoderHong | 来源:发表于2018-03-15 21:36 被阅读19次

    概述

    • 什么是RunLoop
      • 运行循环
    • 基本的作用
      • 保持程序的持续运行
      • 处理App中的各种事件(触摸事件、定时器事件、Selector事件)
      • 节省CPU资源,提高程序的性能

    iOS项目中RunLoop

    • iOS程序的入口main函数
    #import <UIKit/UIKit.h>
    #import "AppDelegate.h"
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    UIApplicationMain函数内部开启了一个RunLoop。这个RunLoop是系统启动。跟我们的主线程相关联。也就是这个RunLoop处理主线程的各种事件。

    RunLoop对象

    • iOS提供了两套API来访问RunLoop
      • Foundation

      • Core Foundation

        • RunLoop

    NSRunLoop是基于CFRunLoop的一层OC包装。Core Foundation是纯C语言的。苹果开源RunLoop源码 官方文档

    RunLoop与线程

    • 每条线程都有唯一的一个与之对应的RunLoop对象
    • 主线程RunLoop已经自动创建好并开启了,子线程的RunLoop需要主动创建
    • 子线程的RunLoop在第一次获取时创建,在线程结束时销毁。
    image

    子线程的RunLoop是懒加载的方式,默认没有创建。在子线程第一次获取时创建。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        [thread start];
    }
    
    - (void)run
    {
        NSRunLoop *rp = [NSRunLoop currentRunLoop];
        NSLog(@"--------------------");
    }
    

    获取RunLoop函数的源码

    // t==0 is a synonym for "main thread" that always works
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
            t = pthread_main_thread_np();
        }
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
            CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
                CFRelease(dict);
            }
            CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
        // 1.线程作为Key去__CFRunLoops取对应的loop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        // 1.1 如果没有取到
        if (!loop) {
            // 1.1.1创建RunLoop
            CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
            loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
            if (!loop) {
                // 1.1.2 key作为当前线程 Value作为RunLoop
                CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
                loop = newLoop;
            }
            // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
            __CFUnlock(&loopsLock);
            CFRelease(newLoop);
        }
        if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    

    RunLoop相关的类

    • Core Foundation中关于RunLoop的5个类
      • CFRunLoopRef
      • CFRunLoopMode
      • CFRunLoopSourceRef
      • CFRunLoopTimerRef
      • CFRunLoopObserverRef
    image.png

    如果一个RunLoop想要运行起来,里面必须有一个Mode并且Model里面必须有一个Source或者Timer。否则该RunLoop直接退出。

    CFRunLoopMode
    • 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
    • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
    • 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
    • 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
    • 系统默认注册了5个Mode:
      • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
      • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
      • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
      • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
      • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode 是一种标记 带此标记的模式UITrackingRunLoopMode kCFRunLoopDefaultMode
    CFRunLoopTimerRef
    • CFRunLoopSourceRef是事件源(输入源)可以简单理解为NSTimer
    • NSTimer定时器
    • [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES];
       
      - (void)run
       {
         NSLog(@"--------------------");
       }
      

      上面通过NSTimer的scheduledTimerWithTimeInterval方式创建其内部做了很多事情。它等价于下面 timerWithTimeInterval的方式。

    • NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
       
      - (void)run
       {
         NSLog(@"--------------------");
       }
      

      默认我们的Timer被添加到RunLoop的NSDefaultRunLoopMode模式下。

    scheduledTimerWithTimeInterval 默认将Timer添加进RunLoop中并且对timer有强引用, 控制器引用timer使用weak。其实在Timer没有调用invalidate方法之前timer会强引用这控制器。控制器销毁了timer不一定销毁 。只有调用invalidateTimer才会销毁。

    当我们拖动UIScrollView的时候,主线程关联的RunLoop进入`UITrackingRunLoopMode`模式。开发为了在满足两种模式下定时器都能够工作。做如下设置。
    
    • NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES];
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
       
      - (void)run
       {
         NSLog(@"--------------------");
       }
      
    • 上面将Timer添加到标记为NSRunLoopCommonModes的模式中。其中有NSRunLoopCommonModes标记的模式为UITrackingRunLoopModeNSDefaultRunLoopMode
    CFRunLoopSourceRef
    • 苹果分类

      • Port-Based Sources
      • Custom Input Sources
      • Cocoa Perform Selector Sources
    • 基于调用栈分类

      • Source0:非基于Port的(用户的点击事件...)
      • Source1:基于Port的(通过内核和其他线程通信、接受、分发系统事件)
    CFRunLoopObserverRef
    • 是观察者,能够监听RunLoop的状态改变
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),
        kCFRunLoopBeforeTimers = (1UL << 1),
        kCFRunLoopBeforeSources = (1UL << 2),
        kCFRunLoopBeforeWaiting = (1UL << 5),
        kCFRunLoopAfterWaiting = (1UL << 6),
        kCFRunLoopExit = (1UL << 7),
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    
    • 使用Core Foundation API创建观察者并监听RunLoop的状态
    // @param  activities 需要监听RunLoop的哪些状态 kCFRunLoopAllActivities监听RunLoop的所有状态
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), 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;
            }
            
        });
        // 2.监听当前线程RunLoop的默认模式的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    RunLoop处理逻辑

    RunLoop实践

    Timer
    ImageView的显示
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        // 如果渲染很多图片 需要将显示图片放在default模式下显示 当拖拽ScrollView是不显示
        [self.imageview performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20180316_64"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
    }
    
    常驻线程
    • 默认一个线程的任务处理完毕就会死亡。通过RunLoop可以让一个线程不死(常驻)。
    #import "ViewController.h"
    #import <CoreFoundation/CoreFoundation.h>
    
    @interface ViewController ()
    @property (nonatomic, strong) NSThread *thread;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        _thread = thread;
        [thread start];
        
    }
    
    // 常驻线程
    - (void)run
    {
        NSLog(@"--------------------run");
        // 创建子线程的runloop
        NSRunLoop *rp = [NSRunLoop currentRunLoop];
        // 添加Source
        [rp addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        // 运行RunLoop
        [rp run]; // 等价于 [rp runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    
    - (void)test
    {
        NSLog(@"--------------------test");
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
    //    [self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:NO];
        [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
    

    相关文章

      网友评论

          本文标题:RunLoop

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