iOS runloop的作用和应用小结

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

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

        RunLoop 就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。使用 RunLoop 的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。 runloop 的设计是为了减少 cpu 无谓的空转。

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.(默认包含NSDefaultRunLoopMode、NSModalPanelRunLoopMode、NSEventTrackingRunLoopMode)

        还有一种情况,当开辟子线程设置定时器的时候,不用设置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的作用和应用小结)