美文网首页iOS从入门到放弃
iOS RunLoop在实际开发中的应用

iOS RunLoop在实际开发中的应用

作者: 狗带儿 | 来源:发表于2017-05-11 13:38 被阅读37次

    WSBackgroundTaskCategory
    本来是想Runtime和RunLoop一起写,所以写了个Runtime的Demo

    是一个AppDelegate的分类,使用时需要引入文件后再

    - (void)applicationDidEnterBackground:(UIApplication *)application {
        
        // beginBackgroundTask
        [self beginBackgroundTaskWithBlock:^{
            NSLog(@"搞一些事情");
        }];
    }
    
    - (void)applicationDidBecomeActive:(UIApplication *)application {
        
        // beginBackgroundTask
        [self endBackgroundTask];
    }
    

    概要

    在iOS开发中,Runtime与RunLoop应该是iOS Developer技术进阶时需要掌握的两方面知识,相对来说,它俩也比较接近底层,就现在环境来看,面试时也比较容易问到。

    关于这两项,网上的文章大多是讲了很多知识点,然而实际开发中用不到,那就找一个很简单的问题来把这两项知识实战一下

    锁屏或切换至后台时计时器停止

    相信大家都遇到过的问题:在注册页面有一个NSTimer实现的验证码倒计时的按钮,在手机切出app,把app在后台挂起时,倒计时是停止的,如你切出时时间剩余50秒,当你从后台返回时,倒计时依然是50秒。

    什么原因?

    如果你有关于RunLoop的知识,你应该知道

    • 每一个线程都有一个自己的RunLoop,他们的关系是一一对应的
    • NSTimer 其实就是 CFRunLoopTimerRef

    上面两点不懂可以来这里 -- 深入理解RunLoop

    CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调

     简单来说就是NSTimer在初始化以后,必须被加入到RunLoop中才会生效
    
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
    

    使用此方法初始化时,会自动将我们创建的NSTimer加入到RunLoop中

    造成NSTimer停止的原因,是当app在后台挂起时,线程同时被挂起,RunLoop也就被挂起,而NSTimer是运行在RunLoop中的,所以在app挂起时,NSTimer就同时停止了工作。

    关于NSTimer想更深入的可以参考iOS开发之 不要告诉我你会用NSTimer!

    怎么解决?

    知道了NSTimer停止的原因是因为线程不活跃

    那解决NSTimer停止的方法就是app在挂起时,让其所在RunLoop的线程处于活跃状态

    我们需要的是UIApplication的:

    - (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler
    

    这个方法,这个方法就是开启一个后台任务,使线程处于活跃状态便于执行此后台任务,线程活跃了,NSTimer也就可以继续跑下去不会停止,当然这个方法只能让主线程活跃180秒

    - (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^ __nullable)(void))handler
    

    - (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier
    

    是一对,你调用了前者开启了一个后台任务,就要调用后者来结束这个任务

    // AppDelegate 中声明一个标识
     @property (nonatomic, unsafe_unretained) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
    

    然后在app进入后台时

    - (void)applicationDidEnterBackground:(UIApplication *)application{
        // 返回一个任务标识
        self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void){
            //  申请的时间到期后进入这里,即马上将被挂起,不再活跃
             [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
            //  将标识符标记为 UIBackgroundTasksInvalid,任务结束
            self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; 
        }];
    }
    

    当然如果app切回来的话也要把任务结束

    - (void)applicationDidBecomeActive:(UIApplication *)application {
        
         [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
         self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
    }
    

    到此我们解决了一个关于定时器停止的问题,看似解决问题的是开启/结束 后台任务的两个方法,但实际上是我们运用了RunLoop的知识来解决的,这是RunLoop知识运用在实际开发中的一个案例

    对了上述我们用到的方法也可以用于解决程序挂起时的复杂操作

    比如需要在程序挂起时向服务器post一些数据,以前做的一款产品就是要收集各种操作信息,收集用户的操作路径啊云云,包括用户切换至后台这样的操作都要收集。
    用这个方法同样可以解决,但是要注意:

    post要用同步方法,保证在主线程里进行

    相关文章

      网友评论

        本文标题:iOS RunLoop在实际开发中的应用

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