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

最佳方案一:使用新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 
    
    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 
    NS_ASSUME_NONNULL_BEGIN
    @interface NSTimer (Weak)
    
    @end
    NS_ASSUME_NONNULL_END
    
    • NSTimer+weak.m

      #import "NSTimer+weak.h"
      #import 
      #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 
    
     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 
    
    @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循环引用的解决方案》
  • Demo:点击下载

你可能感兴趣的:(iOS之NSTimer循环引用的最佳解决方案)