美文网首页Runloop
iOS runloop的作用和应用小结

iOS runloop的作用和应用小结

作者: Hedgehog___ | 来源:发表于2018-04-12 11:03 被阅读186次

            首先,本文借鉴Haley_Wong - 简书 的文章。

             每次当大家提起runloop的时候,脑海中总是浮现的是那么几个概念性的东西,所以我觉得应该学习和总结一下runloop的具体应用场景和作用。这样便于加强对runloop的理解。深入理解RunLoop | Garan no dou

    1、runloop保证线程的长久存活

            多线程开发在我们的工作工程中是常常用到,一个子线程当它的任务执行完毕之后都会销毁,所以每次执行异步任务都会频繁去创建和销毁线程,这样无疑是耗费资源的。这种情况下我们可以利用runloop来保证线程在执行完任务后不背销毁而进入“休眠”状态,等待下一个任务的执行再被唤醒。

            用代码验证首先创建一个类集成于NSThread --》MyThread 

    @implementation MyThread

    -(void)dealloc{

        NSLog(@"%@",NSStringFromSelector(_cmd));

    }

    @end

            重写他的dealloc方法

            在viewdidload里面写

    - (void)viewDidLoad {

        [super viewDidLoad];

        // Do any additional setup after loading the view, typically from a nib.

        [self threadTest];

    }

    -(void)threadTest{

        MyThread* thread = [[MyThreadalloc]initWithBlock:^{

            NSLog(@"threadTest");

        }];

        [threadstart];

    }

    控制台输出:

     threadTest

    dealloc

        得出结论线程执行任务后会自动销毁,我们为了防止这种情况可以利用Runloop来实现。

    - (void)viewDidLoad {

        [super viewDidLoad];

        // Do any additional setup after loading the view, typically from a nib.

        //[self threadTest];

        [self runloopThreadTest];

    }

    -(void)runloopThreadTest{

        MyThread* thread = [[MyThreadalloc]initWithBlock:^{

            NSLog(@"runloopThreadTest");

            //如果注释了下面这一行,子线程中的任务并不能正常执行

            [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];

            //一定要开启

            [[NSRunLoop currentRunLoop] run];

        }];

        [threadstart];

    }

    输出:

    runloopThreadTest 

    demo地址 GitHub - SionChen/Runloop  RunlooprThread

    2、保证NSTimer正常运转。 

            一般我们创建NSTimer计时器有两种方法一种是:

    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];

            这种是默认将timer加入到当前runloop中模式为NSDefaultRunLoopMode,而且为自动fire 。

            另一种是

    NSTimer*timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(timerUpdate) userInfo:nilrepeats:YES];

    [[NSRunLoopcurrentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

    [timer fire]

            这样在视图滑动的时候NSEventTrackingRunLoopMode 模式下 timer是不会计时的

            将第二种创建方法中的mode改为NSRunLoopCommonModes 即可在滚动的时候计时器也计时。

            从RunLoop官方文档iPhonedevwiki中的CFRunLoop可以看出,NSRunLoopCommonModes并不是一种Mode,而是一种特殊的标记,关联的有一个set ,官方文档说:For Cocoa applications, this set includes the default, modal, and event tracking modes by default.(默认包含NSDefaultRunLoopModeNSModalPanelRunLoopModeNSEventTrackingRunLoopMode

            还有一种情况,当开辟子线程设置定时器的时候,不用设置mode为NSRunLoopCommonModes 也能再滑动的时候正常计时。因为主线程的Runloop和子线程的Runloop是不互相影响的。mainrunloop 滑动NSEventTrackingRunLoopMode 时子线程并没有改变mode。

    3、滚动视图流畅性优化

            在我们的开发过程中经常遇到列表型上面有图片的,一般下载图片用异步,setimage则使用同步。为imageView设置image,是在UITrackingRunLoopMode中也可以进行的,如果图片很大,图片解压缩和渲染肯定会很耗时,那么卡顿就是必然的。我们可以再setImage的时候手动设置runloop的mode:

    [imageView performSelector:@selector(setImage:) withObject:image afterDelay:0inModes:@[NSDefaultRunLoopMode]];

            这样就解决了。

    4、监测iOS卡顿

            在写代码之前我们首先要了解几个原理,dispatch_semaphore_t这个类,dispatch_semaphore_t 是一个信号量机制,信号量到达、或者 超时会继续向下进行,否则等待,如果超时则返回的结果必定不为0,信号量到达结果为0。具体参考文章GCD信号量-dispatch_semaphore_t - 简书

            在就是CFRunLoopActivity的几个状态以及Runloop执行的顺序过程:

    /* kCFRunLoopEntry        = (1UL << 0), // 即将进入Loop

                     kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer

                     kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source

                     kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠

                     kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒

                     kCFRunLoopExit          = (1UL << 7), // 即将退出Loop*/

     /*RunLoop 顺序

         1、进入

         2、通知Timer

         3、通知Source

         4、处理Source

         5、如果有 Source1 调转到 11

         6、通知 BeforWaiting

         7、wait

         8、通知afterWaiting

         9、处理timer

         10、处理 dispatch 到 main_queue 的 block

         11、处理 Source1、

         12、进入 2

         13、退出

         */

            根据以上的原理我们可以通过监听mainRunloop的状态和信号量阻塞线程的特点来检测卡顿了。RunLoop 监控卡顿为什么要用kCFRunLoopBeforeSources和kCFRun... - 简书

            首先创建一个用于检测的类SemaphoreDetection:

    @interfaceSemaphoreDetection :NSObject

    /*单例获取*/

    + (instancetype) sharedInstance;

    /*开始检测*/

    - (void) startDetection;

    /*停止检测*/

    - (void) endDetection;

    @end

            在startDetection 初始化相关实例:

    //设置Run loop observer的运行环境

        CFRunLoopObserverContextcontext = {0, (__bridgevoid*)(self),NULL,NULL,NULL};

        //创建Run loop observer对象

        //第一个参数用于分配observer对象的内存

        //第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释

        //第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行

        //第四个参数用于设置该observer的优先级

        //第五个参数用于设置该observer的回调函数

        //第六个参数用于设置该observer的运行环境

        _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runloopObserverAction, &context);

        CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);

        //创建初始信号量为0 的dispatch_semaphore

        _semaphore = dispatch_semaphore_create(0);

        //开辟线程监听延时

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            //死循环监听 通过控制信号量 来实现 mainrunloop循环或者超时的时候才会执行

            while(YES) {

                // 累计延迟超过250ms包含--》 (设置连续5次超时50ms认为卡顿(当然也包含了单次超时250ms))

                //dispatch_semaphore_t 是一个信号量机制,信号量到达、或者 超时会继续向下进行,否则等待,如果超时则返回的结果必定不为0,信号量到达结果为0。信号量为小于等于0的时候会阻塞当前线程

                longsemaphoreInt =dispatch_semaphore_wait(self->_semaphore,dispatch_time(DISPATCH_TIME_NOW,50*NSEC_PER_MSEC));

                if(semaphoreInt!=0) {//超时

                    /* kCFRunLoopEntry        = (1UL << 0), // 即将进入Loop

                     kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer

                     kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source

                     kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠

                     kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒

                     kCFRunLoopExit          = (1UL << 7), // 即将退出Loop*/

                    if (self->_activity==kCFRunLoopBeforeSources || self->_activity==kCFRunLoopAfterWaiting)

                    {

                        if(++self->_countTime<5)

                            continue;

                        [selflogStack];//记录卡顿堆栈信息

                        NSLog(@"*************lag******************");

                    }

                }

                self->_countTime=0;

            }

        });

        附demo地址:GitHub - SionChen/Runloop RunloopDetection工程查看代码。

    5、阻止一次崩溃

    相关文章

      网友评论

        本文标题:iOS runloop的作用和应用小结

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