iOS之RunLoop详解

作者: 云霄_云霄 | 来源:发表于2019-03-12 16:39 被阅读330次

    1.RunLoop的介绍:

    RunLoop即运行循环(跑圈),只不过它这种循环比较高级。一般的 while 循环会导致 CPU 进入忙等待状态,而 RunLoop 则是一种“闲”等待,这部分可以类比 Linux 下的 epoll。当没有事件时,RunLoop 会进入休眠状态,有事件发生时, RunLoop 会去找对应的 Handler 处理事件。RunLoop 可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。

    2.RunLoop的基本作用:

    • 保持程序的持续运行
    • 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
    • 节省CPU资源,提高程序性能,该工作的时候工作,该休息就休息

    3.iOS程序入口与RunLoop的关系:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    由于main函数里面UIApplicationMain启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,则程序并不会马上退出,而是保持持续运行状态,这个默认的RunLoop是跟主线程相关联的。

    4.RunLoop对象:

    • NSRunLoop(OC中Foundation框架下)
    • CFRunLoopRef(C中Core Foundation框架下)

    5.RunLoop与线程:

    • 每条线程都有唯一一个与之对应的RunLoop对象(字典的方式)。
    • 主线程的RunLoop默认已经创建并且开启了,子线程对应的RunLoop需要手动创建并开启。
    • RunLoop在第一次获取时创建,在线程结束时销毁。
    • RunLoop与线程的关系如下图


      runLoop.jpg

    6.获取RunLoop对象:

        //Foundation框架下
        NSRunLoop * currentRunLoop =  [NSRunLoop currentRunLoop]; //获取当前线程的RunLoop对象
        NSRunLoop * mainRunLoop = [NSRunLoop mainRunLoop]; //获取主线程的RunLoop对象
        
        //Core Foundation框架下
        /*
        CFRunLoopRef * currentRunLoop =  CFRunLoopGetCurrent() //获取当前线程的RunLoop对象
        CFRunLoopRef * mainRunLoop = CFRunLoopGetMain() //获取主线程的RunLoop对象
        */
    

    7.RunLoop的相关类:

    • CFRunLoopRef--RunLoop本身
    • CFRunLoopModeRef--RunLoop运行模式(CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Model,每个Model又包含若干个Source/Timer/Observer,每次RunLoop启动时,智能指定其中一个Model,这个Model被称作CurrentMode,如果需要切换Model,只能退出Loop,在重新指定一个Model进入,这样做主要是为了分隔开不同的Source/Timer/Observer,让其互不影响)
    • CFRunLoopSourceRef--RunLoop的事件(input Source)
    • CFRunLoopTimerRef --(Timer Source)
    • CFRunLoopObserverRef--监听者

    CFRunLoopModeRef的详解:

    • 系统默认注册了5个Model
    • kCFRunLoopDefaultMode :App的默认model,通常主线程是在这个model下进行
    • UITrackingRunLoopMode :界面跟踪model,用于scrolView追踪触摸滑动,保证界面滑动时不受其他model影响
    • UIinitializationRunLoopMode :在刚启动App时进入的第一个Mode,启动完成后不在使用
    • GSEventReceiveRunLoopMode :接受系统事件的内部mode,通常用不到
    • kCFRunLoopCommonModes :这是一个占位mode,不是一个真正的mode

    8.RunLoop的运行模式和Timer:

    -(void)timer{
        //01-创建定时对象
        NSTimer * timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        //02-把定时器添加到RunLoop中(切记:如果当前方法在子线程,需要手动开启当前子线程的RunLoop,并且开启,而且开启方法要在添加定时器之后,否则开启了因为没有事件就直接停止了)
        //(情况1)模式为默认:NSDefaultRunLoopMode--当滚动UItextView的时候定时器停止工作
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        
        //(情况2)界面追踪模式为:UITrackingRunLoopMode--(只有当滚动UItextView的时候定时器才工作)
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
        
        //(情况3)界面追踪模式为:NSRunLoopCommonModes(NSDefaultRunLoopMode|UITrackingRunLoopMode)--(不管有没有滚动,定时器一直工作)
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        
    }
    -(void)run{
        NSLog(@"%@",[NSRunLoop currentRunLoop].currentMode);
    }
    

    9.CFRunLoopObserverRef的详解:

    • CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。
    • 可以监听的时间点有以下:
       kCFRunLoopEntry = (1UL << 0), //即将进入Loop
       kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
       kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
       kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
       kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠中唤醒
       kCFRunLoopExit = (1UL << 7), //即将退出loop
       kCFRunLoopAllActivities = 0x0FFFFFFFU //所有的状态
      
    /*
         01-创建观察者
         参数说明
         第一个参数:分配存储空间
         第二个参数:要监听的状态
         第三个参数:是否持续监听
         第四个参数:0
         第五个参数:block回调,当runloop状态改变的时候会调用
         输出:
         2019-03-12 15:13:08.598453+0800 SmallProgram[3374:145215] runloop即将处理Timers事件
         2019-03-12 15:13:08.598653+0800 SmallProgram[3374:145215] runloop即将处理Sources事件
         2019-03-12 15:13:08.598906+0800 SmallProgram[3374:145215] runloop即将处理Timers事件
         2019-03-12 15:13:08.599048+0800 SmallProgram[3374:145215] runloop即将处理Sources事件
         2019-03-12 15:13:08.599180+0800 SmallProgram[3374:145215] runloop即将处理Timers事件
         2019-03-12 15:13:08.599295+0800 SmallProgram[3374:145215] runloop即将处理Sources事件
         2019-03-12 15:13:08.599620+0800 SmallProgram[3374:145215] runloop即将进入到休眠
         2019-03-12 15:13:08.693981+0800 SmallProgram[3374:145215] runloop被唤醒
         2019-03-12 15:13:08.694216+0800 SmallProgram[3374:145215] runloop即将处理Timers事件
         2019-03-12 15:13:08.694340+0800 SmallProgram[3374:145215] runloop即将处理Sources
         */
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"runloop启动");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"runloop即将处理Timers事件");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"runloop即将处理Sources事件");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"runloop即将进入到休眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"runloop被唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"runloop退出");
                    break;
                    
                default:
                    break;
            }
        });
        /*
        //02-监听runLoop的状态
         参数说明
         第一个参数:runLoop对象
         第二个参数:监听者
         第三个参数:runLoop在哪种模式下的状态(这里要写C情况下的状态)
                   kCFRunLoopDefaultMode
        */
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    

    10.RunLoop的应用:

    • (情景一)在tableView头视图上添加定时器轮播的时候,如果想要滑动tableView的时候,也要轮播,则修改runLoop的模式为NSRunLoopCommonModes
    • (情景二)定时器一直是准确运行的来执行对应的操作,则修改runLoop的模式为NSRunLoopCommonModes
    • 设置程序的常驻线程。
    -(void)application{
        
        NSThread * thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        [thread start];
        self.thread = thread;
        
    }
    -(void)run{
        
        //01子线程的RunLoop需要手动创建并启动
        //02RunLoop启动后会选择运行模式,判断运行模式是否为空
        NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
        //(方法一)往运行模式中添加timer
        //[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timeRun) userInfo:nil repeats:YES];
        //(方法二)往运行模式中添加source=port|custom|selector
        [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        
        [runLoop run];
        
    }
    //将下面方法放到需要子线程来做任务的事件中,比如点击按钮事件
    //按钮点击事件让子线程来执行任务run2
        [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
    //这样self.thread会一直不死,来等待处理run2的事件
    

    11.RunLoop的内部自动释放池:

    • App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
    • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
    • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。


      具体流程

    本文参考:
    线程、自动释放池、RunLoop的爱恨情仇https://www.jianshu.com/p/8b011b844231

    相关文章

      网友评论

        本文标题:iOS之RunLoop详解

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