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.(默认包含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、阻止一次崩溃

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容