包大小优化

Posted by LML on March 23, 2020

前言

本系列文章主要介绍 iOS 中几种常见的定时器的使用,主要包括:

  • NSTimer
  • CADisplayLink
  • dispatch_source_t

dispatch_source_t

创建、添加、开启方法

 (void)addDisPatch_source {
    //1.创建
    self.disTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    //2.设置时间
    dispatch_source_set_timer(self.disTimer, dispatch_walltime(NULL,0 * NSEC_PER_SEC), 1 * NSEC_PER_SEC, 0);
    //3.执行
    dispatch_source_set_event_handler(self.disTimer, ^{
        NSLog(@"=========dispatch_source_t=========");
    });
    //开始
    dispatch_resume(self.disTimer);
    //暂停
      dispatch_suspend(self.disTimer);
      // 停止
      dispatch_source_cancel(self.disTimer);
}

注意的点

  • dispatch_source_create
    • 最后参数:The dispatch queue to which the event handler block is submitted. 只要不是main queue,都将在子线程执行
    • dispatch_source_t time 是一个对象,遵循ARC 规则,所以如果想要定时,不能设置为临时变量
  • 关于 dispatch_suspend()ispatch_resume的使用
    • dispatch_resumedispatch_suspend 应该成对使用,且刚开始定义的 默认是suspend状态
    • 因为重复调用会让dispatch_resume代码里if分支不成立,从而执行了DISPATCH_CLIENT_CRASH("Over-resume of an object")导致崩溃。
    • source在suspend状态下,如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在resume状态下调用dispatch_source_cancel(source)释放当前的source
    • dispatch_source_cancel 并没有使引用计数-1,source 在 resum 状态下可以直接 = nil或者重新创建
  • dispatch_source_cancel
    • Asynchronously cancels the dispatch source, preventing any further invocation of its event handler block.——不影响当前正在执行的Block
    • The optional cancellation handler is submitted to the target queue once the event handler block has been completed.
  • dispatch_source_set_event_handler使用
    • source 会强持有block
    • 这里有个循环引用需要注意
      • 因为dispatch_source_set_event_handler回调是个block,在添加到source的链表上时会执行copy并被source强引用,如果block里持有了self,self又持有了source的话,就会引起循环引用。

内部实现原理

https://xiaozhuanlan.com/topic/9481560732

NSTimer

基本使用

创建

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable  
id)userInfo repeats:(BOOL)yesOrNo;

NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction)  
 userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];  


+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector   
userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(checkPermission) userInfo:nil repeats:YES];

  • 上述两种使用的区别市:第一种需要我们手动加入到runloop,第二种已经自动加入到Runloop
    • reates a timer and schedules it on the current run loop in the default mode. ——加入的是当前runloop的default mode里
    • 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配
    • 每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动
  • NSDefaultRunLoopMode UITrackingRunLoopMode NSRunLoopCommonModes 不同 mode
    • NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
    • NSDefaultRunLoopMode 会收到UI滑动事件的影响
  • 存在延迟
    • 不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。
  • 强持有问题
    • target:The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated. ——timer强持有target
    • RunLoop 强持有 Timer
      • You can add a timer to multiple input modes. While running in the designated mode, the receiver causes the timer to fire on or after its scheduled fire date. Upon firing, the timer invokes its associated handler routine, which is a selector on a designated object. The receiver retains aTimer. To remove a timer from all run loop modes on which it is installed, send an invalidate message to the timer. - (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;
  • aSelector
    • The message to send to target when the timer fires.
  • userInfo
    • The user info for the timer. The timer maintains a strong reference to this object until it (the timer) is invalidated. This parameter may be nil. ——timer 强持有userinfo

启动、暂停与销毁

  • 启动
    ```
  • (void)fire;
    ```
    You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

  • 停止
- (void)invalidate;  

Stops the timer from ever firing again and requests its removal from its run loop This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point. If it was configured with target and user info objects, the receiver removes its strong references to those objects as well. invalidate 完成:移除Runloop对timer的强持有和timer对target和userinfo的强持有

循环引用与持有问题

常见的问题

  • NStimer不是属性 且self是target
    • NStimer 会强引用self,Runloop强引用NStimer 然后如果不调用invalidate,在runloop结束前NStimer都不会释放,导致self不会释放,内存泄露
    • 如果传入target的为weakself,那么self会被释放,在self的dealloc中调用invalidate函数,可以释放NStimer,即使不释放NStimr,也仅仅导致了NStimrt没有释放,没有导致self泄漏
  • NStimer是属性 且self是target
    • Nstimer计数2(self+Runloop);self计数2(self+Nstimer),self自己-1,self 被NStimer强引用不释放,这个即使后面runloop释放,self也释放不了:因为runloop释放后,self1,Nstimer1,除非主动释放self,但是取不到self了.
    • 这种情况下,即使找到合适时机调用invalidate,也需要注意如果调用invalidate函数,必须在self-1之前,这样才会在流程走通:nstimer invalidate(释放NSrunloop对NStimer的引用,和NStimer对target的引用——Self-1——self在释放——Timer释放
    • 另外解决措施:
      • weakself传入NStimer的target,self(1) NStimer(2) self可执行dealloc函数,然后dealloc里面写invalidate函数,然后Nstimer-1(Runloop释放对NAtimer的强引用),然后 self dealloc Nstimer -1
  • NStimer不是属性是block
    • 一般block传入的话,都会注意block是weak,所以这种情况下,self是可以 执行到dealloc函数的,只是需要注意对Nstimer的invalidate函数调用,实现释放NStimer

正确的使用方法

总结:其实根本原因是Nstimer 的invalidate函数没有在合适时机调用.下面举个例子,合适的时机

如果实在找不到合适时机,需要在传入target的时候weakself,避免self的dealloc函数不执行,dealloc能执行可以在dealloc里面写invalidate,保证NStimer的释放