最佳方案一:使用新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循环引用的解决方案》
- Demo:点击下载
网友评论