美文网首页
定时器启示录

定时器启示录

作者: Fsn_soul | 来源:发表于2020-05-31 15:01 被阅读0次

    早些时候看过一些分析定时器内存方面的文章,但在遇到这个bug前我是不屑的。不就是定时器强引用ViewController,而ViewController再用strong属性去引用定时器,必然会导致循环引用么。解决办法也很简单,只需使用weak属性去引用定时器即可。然而这一次的经历却证明我还是图样,我单知道使用weak属性不会导致循环引用,我没注意到此时的定时器在无形中延长了ViewController的生命周期。这就为这个bug埋下了隐患。

    记录一下bug的解决总是有必要的。

    下午的一次自测中,偶尔发现观众端听不到主播端的声音。我先是诧异,接着是很不安,因为墨菲定律告诉我们:如果你担心某种情况发生,那么它就更有可能发生。最开始以为是主播端的问题,便仔细检查了主播端的代码,又加入了另一台手机设置为观众,作为对照。运行之后发现对照组是好的,但刚才那台手机的问题仍然偶现。这就说明主播端的推流是没问题的。接下来的工作便是在问题手机上尽可能找到复现的操作,以便根据操作路径定位大致原因。在某次频繁进入离开直播房间时,APP直接卡死了,再无任何交互的响应。问题开始变得严重,时间也一分一秒的流逝在这一次次的调试中,一晃下班时间快到了,周围开始变的嘈杂,安卓兄弟开始催我下班还说要带我上王者但我是不信的。我整理了下东西,但又不想在节前留下些许问题,便又坐了回去。等到周围开始安静时,夕阳已经西下。我努力回想之前的操作,发现只在直播预约状态下,问题才会重现。于是在页面的dealloc函数中打好断点,点击返回,果然函数没有被调用。这说明页面依然被某个对象持有而没有释放。在检查了所有Block回调都使用的是weakSelf后,最后只剩下定时器了。

    问题的根源算是找到了。原来在直播预约状态下会启动一个定时器,但在点击返回时忘记invalid定时器了。这让定时器延长了ViewController的生命周期。加上invalid后,问题搞定,收工。回家的路上碰巧遇到了K君,便给K君讲述了这个问题,K君听后哈哈大笑说:加上invalid只能解决这一次的bug,却不能避免下一次又忘记,而且根据页面dealloc函数里逻辑的不同,bug的外在表现形式也必然不同,到时候又得花费不少的时间找bug啊。闻道于朝,不禁感叹K君的身经百战。

    回到家后,打开谷歌又搜到了早些时候看过的那些文章,感慨颇多。系统的NSTimer简单却又不那么简单:

    1. 不注意使用的话有内存泄漏的隐患
    2. 需要在合适的地方invalid定时器,否则定时器会一直强引用target从而延长target的生命周期。
    3. 使用时必须保证有一个活跃的runloop,并且需要指定mode
    4. 精度可能不够
    5. 创建和撤销必须在同一个线程上,在多线程环境下使用不便
    6. 不支持block,造成使用上的不便。(iOS10开始,已经支持block了)

    为了从根本上避免上述问题,一个弱引用target的、能够在自身销毁时自动invalid的定时器想必是极好的,但又该如何实现呢?好在互联网在经过这么多年的发展,第三方开源库从未像现在这般丰富,唾手可得。不多时,便在GitHub上找到了MSWeakTimer

    MSWeakTimer提供了和系统NSTimer一致的接口,好的代码就该这样美美与共,和而不同:

    @property (nonatomic, strong) MSWeakTimer *weakTimer;
    
    self.weakTimer = [MSWeakTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(doSome) userInfo:nil repeats:YES dispatchQueue:dispatch_get_main_queue()];
    //立即触发回调方法
    [self.weakTimer fire];
    
    - (void)doSome {
        NSLog(@"++++++%@", self);
    }
    

    至于MSWeakTimer的实现原理自然是和NSTimer不同的:通过封装GCD定时器实现NSTimer的功能,但内部却是弱引用target,不仅如此MSWeakTimer还支持在其他线程中执行回调函数。

    @interface MSWeakTimer ()
    {
        struct
        {
            uint32_t timerIsInvalidated;
        } _timerFlags;
    }
    
    @property (nonatomic, assign) NSTimeInterval timeInterval;
    @property (nonatomic, weak) id target; //弱引用target
    @property (nonatomic, assign) SEL selector;
    @property (nonatomic, strong) id userInfo;
    @property (nonatomic, assign) BOOL repeats;
    
    @property (nonatomic, ms_gcd_property_qualifier) dispatch_queue_t privateSerialQueue;
    
    @property (nonatomic, ms_gcd_property_qualifier) dispatch_source_t timer;
    
    @end
      
    ...
    
    //自身销毁时invalidate掉定时器
    - (void)dealloc
    {
      [self invalidate];
    
      ms_release_gcd_object(_privateSerialQueue);
    }
    

    当我们使用MSWeakTimer时,就可以避免因忘记invalid定时器,导致ViewController生命周期被延长不能及时销毁而产生的bug。从这之后,我便不再遇到和NSTimer相关的bug了。

    相关文章

      网友评论

          本文标题:定时器启示录

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