美文网首页2021面试专题
iOS之NSTimer循环引用的最佳解决方案

iOS之NSTimer循环引用的最佳解决方案

作者: 片片碎 | 来源:发表于2021-09-09 16:18 被阅读0次

    最佳方案一:使用新API

    如果你的系统只支持iOS10以上,强烈建议使用新API

    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    
    - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block 
    

    这新的API,增加了block的参数。关于这个block参数,官方文档说明如下:

     block  The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
    

    翻译过来大概意思是:定时器在执行时,讲定时器自身作为参数传递给block,来帮助避免循环引用。使用很简单。只需要注意两点:

    • 1、避免block的循环引用,使用__weak和__strong来避免
    • 2、在持用NSTimer对象的类的方法中-(void)dealloc调用NSTimer 的- (void)invalidate方法;
      代码示范
    #import "SecondViewController.h"
    #define WEAKSELF typeof(self) __weak weakSelf = self;
    #define STRONGSELF typeof(weakSelf) __strong strongSelf = weakSelf;
    
    @interface SecondViewController ()
    
    @property (nonatomic, strong) NSTimer *timer;
    
    @end
    
    @implementation SecondViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        WEAKSELF
        self.timer =  [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            STRONGSELF //避免block循环引用
            [strongSelf timerAction];
        }];
    }
    
    -(void)viewWillAppear:(BOOL)animated {
        if (self.timer) {
            [self.timer setFireDate:[NSDate distantPast]];
        }
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        if (self.timer) {
            [self.timer setFireDate:[NSDate distantFuture]];
        }
    }
    
    - (void)dealloc {
        if (self.timer) {
            [self.timer invalidate]; //dealloc时候从runloop中删除timer
            self.timer = nil;
        }
        NSLog(@"Feng SecondViewController dealloc");
    }
    
    -(void)timerAction {
        NSLog(@"Feng timer test");
    }
    @end
    
    

    最佳方案二:使用NSProxy

    如果实在没有办法,需要兼容10以下版本,那就使用NSProxy吧。SDWebImage就使用了此方案。使用SDWeakProxy这个伪基类,避免timer和target造成循环。此处FengWeakProxy也就相当于SDWebImage中的SDWeakProxy。此方案的好处就是:不需要修改之前的代码,一句都不用修改

    • NSProxy的伪基类:FengWeakProxy
      • FengWeakProxy.h
      #import <Foundation/Foundation.h>
      
      NS_ASSUME_NONNULL_BEGIN
      
      @interface FengWeakProxy : NSProxy
      
      @property (nonatomic, weak, readonly, nullable) id target;
      
      - (nonnull instancetype)initWithTarget:(nonnull id)target;
      + (nonnull instancetype)proxyWithTarget:(nonnull id)target;
      
      @end
      
      NS_ASSUME_NONNULL_END
      
      
      • FengWeakProxy.m
      #import "FengWeakProxy.h"
      
      @implementation FengWeakProxy
      - (instancetype)initWithTarget:(id)target {
          _target = target;
          return self;
      }
      
      + (instancetype)proxyWithTarget:(id)target {
          return [[FengWeakProxy alloc] initWithTarget:target];
      }
      
      #pragma mark --转发实现
      //当不能识别方法时候,就会调用这个方法,在这个方法中,我们可以将不能识别的传递给其它对象处理
      //由于这里对所有的不能处理的都传递给_target了,所以methodSignatureForSelector和forwardInvocation不可能被执行的,所以不用再重载了吧
      //其实还是需要重载methodSignatureForSelector和forwardInvocation的,为什么呢?因为_target是弱引用的,所以当_target可能释放了,当它被释放了的情况下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation没实现的话,就直接crash了!!!
      //这也是为什么这两个方法中随便写的!!!
      
      // 转发目标选择器
      - (id)forwardingTargetForSelector:(SEL)selector {
        return _target;
      }
      
      // 方法签名的选择器
      - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
          return [NSObject instanceMethodSignatureForSelector:@selector(init)];
      }
      
      // 函数执行器
      - (void)forwardInvocation:(NSInvocation *)invocation {
          void *null = NULL;
          [invocation setReturnValue:&null];
      }
      
      #pragma mark --其他
      - (BOOL)respondsToSelector:(SEL)aSelector {
          return [_target respondsToSelector:aSelector];
      }
      
      - (BOOL)isEqual:(id)object {
          return [_target isEqual:object];
      }
      
      - (NSUInteger)hash {
          return [_target hash];
      }
      
      - (Class)superclass {
          return [_target superclass];
      }
      
      - (Class)class {
          return [_target class];
      }
      
      - (BOOL)isKindOfClass:(Class)aClass {
          return [_target isKindOfClass:aClass];
      }
      
      - (BOOL)isMemberOfClass:(Class)aClass {
          return [_target isMemberOfClass:aClass];
      }
      
      - (BOOL)conformsToProtocol:(Protocol *)aProtocol {
          return [_target conformsToProtocol:aProtocol];
      }
      
      - (BOOL)isProxy {
          return YES;
      }
      
      - (NSString *)description {
          return [_target description];
      }
      
      - (NSString *)debugDescription {
          return [_target debugDescription];
      }
      @end
      
    • NSTimer方法替换:NSTimer+weak
      • NSTimer+weak.h
      #import <Foundation/Foundation.h>
      NS_ASSUME_NONNULL_BEGIN
      @interface NSTimer (Weak)
      
      @end
      NS_ASSUME_NONNULL_END
      
      • NSTimer+weak.m

        #import "NSTimer+weak.h"
        #import <objc/runtime.h>
        #import "NSObject+Swizzing.h"
        #import "FengWeakProxy.h"
        @implementation NSTimer (Weak)
        
        +(void)load {
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
          @autoreleasepool {
              [self swizzleWithSysMethod:@selector(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:) swizzledMethod:@selector(scheduledSafeTimerWithTimeInterval:target:selector:userInfo:repeats:)];
              [self swizzleWithSysMethod:@selector(timerWithTimeInterval:target:selector:userInfo:repeats:) swizzledMethod:@selector(timerSafeWithTimeInterval:target:selector:userInfo:repeats:)];
                }
          });
        }
        
        + (NSTimer *)scheduledSafeTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
          return [self scheduledSafeTimerWithTimeInterval:ti target:[FengWeakProxy proxyWithTarget:aTarget] selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
        
        + (NSTimer *)timerSafeWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
              return [self timerSafeWithTimeInterval:ti target:[FengWeakProxy proxyWithTarget:aTarget] selector:aSelector userInfo:userInfo repeats:yesOrNo];
         }
        
          @end
        
        
    • 交换方法
      • NSObject+Swizzing.h
      #import <Foundation/Foundation.h>
      
       NS_ASSUME_NONNULL_BEGIN
      
       @interface NSObject (Swizzing)
        + (void)swizzleWithSysMethod:(SEL)originalSelector swizzledMethod:(SEL)swizzledSelector;
        + (void)swizzleInstanceWithSysMethod:(SEL)originalSelector swizzledMethod:(SEL)swizzledSelector;
      
        @end
      
        NS_ASSUME_NONNULL_END
      
      • NSObject+Swizzing.m
      #import "NSObject+Swizzing.h"
      #import <objc/runtime.h>
      
      @implementation NSObject (Swizzing)
       + (void)swizzleWithSysMethod:(SEL)originalSelector swizzledMethod:(SEL)swizzledSelector {
      // Method中包含IMP函数指针,通过替换IMP,使SEL调用不同函数实现
         Method origMethod = class_getClassMethod(self, originalSelector);
         Method replaceMeathod = class_getClassMethod(self, swizzledSelector);
         Class metaKlass = objc_getMetaClass(NSStringFromClass(self).UTF8String);
      
       //    class_addMethod:如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
           BOOL didAddMethod = class_addMethod(metaKlass,
                                         originalSelector,
                                         method_getImplementation(replaceMeathod),
                                         method_getTypeEncoding(replaceMeathod));
           if (didAddMethod) {
           // 原方法未实现,则替换原方法防止crash
               class_replaceMethod(metaKlass,
                             swizzledSelector,
                             method_getImplementation(origMethod),
                             method_getTypeEncoding(origMethod));
           }else {
       //   添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
               method_exchangeImplementations(origMethod, replaceMeathod);
             }
       }
      
         + (void)swizzleInstanceWithSysMethod:(SEL)originalSelector swizzledMethod:(SEL)swizzledSelector {
           Method origMethod = class_getInstanceMethod(self, originalSelector);
           Method replaceMeathod = class_getInstanceMethod(self, swizzledSelector);
      
             // class_addMethod:如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
           BOOL didAddMethod = class_addMethod(self,
                                         originalSelector,
                                         method_getImplementation(replaceMeathod),
                                         method_getTypeEncoding(replaceMeathod));
           if (didAddMethod) {
         // 原方法未实现,则替换原方法防止crash
               class_replaceMethod(self,
                             swizzledSelector,
                             method_getImplementation(origMethod),
                             method_getTypeEncoding(origMethod));
             }else {
               // 添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
               method_exchangeImplementations(origMethod, replaceMeathod);
           }
         }
      
       @end
      
         ```
      

    代码示范

    #import "SecondViewController.h"
    #define WEAKSELF typeof(self) __weak weakSelf = self;
    #define STRONGSELF typeof(weakSelf) __strong strongSelf = weakSelf;
    
    @interface SecondViewController ()
    
    @property (nonatomic, strong) NSTimer *timer;
    
    @end
    
    @implementation SecondViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[FengWeakProxy proxyWithTarget:self] selector:@selector(timerAction) userInfo:nil repeats:YES];
    }
    
    -(void)viewWillAppear:(BOOL)animated {
        if (self.timer) {
            [self.timer setFireDate:[NSDate distantPast]];
        }
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        if (self.timer) {
            [self.timer setFireDate:[NSDate distantFuture]];
        }
    }
    
    - (void)dealloc {
        if (self.timer) {
            [self.timer invalidate]; //dealloc时候从runloop中删除timer
            self.timer = nil;
        }
        NSLog(@"Feng SecondViewController dealloc");
    }
    
    -(void)timerAction {
        NSLog(@"Feng timer test");
    }
    @end
    

    相关文章

      网友评论

        本文标题:iOS之NSTimer循环引用的最佳解决方案

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