1. 循环引用
问题代码
日常开发中,经常会用到NSTimer定时器,一些不正确的写法,会导致NSTimer造成循环引用,如下:
@interface TargetViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TargetViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void**)timerAction {
static uint64_t num = 0;
NSLog(@"%s, num = %@", __func__ , @(num));
num++;
}
- (void)dealloc {
[self.timer invalidate];
self.title = nil;
NSLog(@"%s", __func__);
}
@end
这种代码必然会造成循环引用:
-
创建
timer时,将self传入target,导致timer持有self,而self又持有timer -
使用
timer的invalidate方法可以解除timer对self的持有,但是timer持有self,导致self不可能调用dealloc, 从而不能调用invalidate故此双方相互等待,造成循环引用
使用__weak 可行否
在解决 block 循环引用时,我们使用 __weak typeof(self) weakSelf = self;,那么 针对tiemer 是不是也可以这样呢?修改代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = elf;
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
运行代码,依然不行,为啥呢? 查看官文 :
-
target:定时器触发时指定的消息发送到的对象。计时器维护对该对象的强引用,直到它(计时器)失效 -
在
timerWithTimeInterval内部,使用强引用对象接收target参数,所以这里在外部定义为弱引用对象没有任何意义, 类似于这种代码:__weak typeof(self) weakSelf = self; // 外部 __strong typeof(weakSelf)strongSelf = weakSelf; // 内部
和Block的区别,Block将捕获到的弱引用对象,内部赋值给一个弱引用的临时变量,当Block执行完毕,临时变量会自动销毁,解除对外部变量的持有
2. 常规解决方法
使用 带block的 API
使用携带Block的方法创建NSTimer,避免target的强持有
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
在适当时机调用invalidate
如:在didMoveToParentViewController方法中
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
}
}
3. 打断强持有
除了常规解决方案,还可以通过打断target的强持有,解决循环引用的问题
中介者模式
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(timerAction), (IMP)timerActionObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(timerAction) userInfo:nil repeats:YES];
}
void timerActionObjc(id obj){
static uint64_t num = 0;
NSLog(@"%s - %@ - %@", __func__ , @(num), obj);
num++;
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s", **__func__** );
}
-
创建
NSObject实例对象objc,通过Runtime对NSObject增加timerAction方法,IMP指向timerActionObjc的函数地址 -
创建
NSTimer,将objc传入target参数,这样避免NSTimer对self的强持有 -
当页面退出时,由于
self没有被NSTimer持有,正常调用dealloc方法- 在
dealloc中,对NSTimer进行释放。此时NSTimer对objc的强持有解除,objc也跟着释放
- 在
封装自定义Timer
创建YJWeakTimer,实现自定义Timer的封装
打开YJWeakTimer.h文件,写入以下代码:
#import <Foundation/Foundation.h>
@interface YJWeakTimer : NSObject
- (instancetype)yj_initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;
- (void)yj_invalidate;
@end
打开YJWeakTimer.m文件,写入以下代码:
#import "YJWeakTimer.h"
#import <objc/message.h>
@interface YJWeakTimer()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation YJWeakTimer
- (instancetype)yj_initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
if (self == [super init]) {
self.target = target;
self.aSelector = selector;
if ([self.target respondsToSelector:self.aSelector])
Method method = class_getInstanceMethod([self.target class], selector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], selector, (IMP)fireHomeWapper, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:selector userInfo:userInfo repeats:repeats];
}
}
return self;
}
void fireHomeWapper(YJWeakTimer *wtimer){
if (wtimer.target) {
void (*yj_msgSend)(void *, SEL, id) = (void *)objc_msgSend;
yj_msgSend((__bridge void *)(wtimer.target), wtimer.aSelector, wtimer.timer);
} else {
[wtimer yj_invalidate];
}
}
- (void)yj_invalidate {
[self.timer invalidate];
self.timer = nil;
}
@end
YJWeakTimer 的使用:
- (void)viewDidLoad {
[super viewDidLoad];
self.weakTimer = [[YJWeakTimer alloc] yj_initWithTimeInterval:1 target:self selector: @selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction {
static uint64_t num = 0;
NSLog(@"%s, num = %@", __func__ , @(num));
num++;
}
-
在
YJWeakTimer中定义yj_initWithTimeInterval和yj_invalidate方法-
YJWeakTimer通过weak修饰的target,对ViewController`进行弱持有 -
检测
target中是否能响应selector。能响应,对当前类通过Runtime API添加同名方法编号,指向自身内部fireHomeWapper的函数地址 -
创建真正的
NSTimer定时器,将控件自身的实例对象传入target,避免NSTimer对ViewController强持有
-
-
当
NSTimer回调时,会进入fireHomeWapper函数- 函数内部不负责业务处理,如果
target存在,使用objc_msgSend,将消息发送给target自身下的selector方法
- 函数内部不负责业务处理,如果
-
当页面退出时,
ViewController可以正常释放。但YJWeakTimer和NSTimer相互持有,双方都无法释放 -
由于双方都无法释放,
NSTimer的回调会继续调用-
当进入
fireHomeWapper函数,发现target已经不存在了,调用YJWeakTimer的yj_invalidate方法,内部对NSTimer进行释放 -
当
NSTimer释放后,对YJWeakTimer的强持有解除,YJWeakTimer也跟着释放
-
NSProxy虚基类
NSProxy的作用:
-
OC不支持多继承,但是它基于运行时机制,可以通过NSProxy来实现伪多继承 -
NSProxy和NSObject属于同一级别的类,也可以说是一个虚拟类,只实现了NSObject的协议部分 -
NSProxy本质是一个消息转发封装的抽象类,类似一个代理人
可以通过继承NSProxy,并重写以下两个方法实现消息转发
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
NSProxy除了可以用于多继承,也可以作为切断强持有的中间人
打开YJProxy.h文件,写入以下代码:
打开YJProxy.m文件,写入以下代码:
YJProxy的调用使用:
- (void)viewDidLoad {
[uper viewDidLoad];
self.proxy = [YJProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction {
static uint64_t num = 0;
NSLog(@"%s, num = %@", __func__ , @(num));
num++;
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s", **__func__** );
}
-
YJProxy初始化方法,将传入的object赋值给弱引用对象 -
在
UIViewController中,创建YJProxy对象proxy。创建NSTimer对象,将proxy传入target,避免NSTimer对ViewController强持有 -
当
NSTimer回调时,触发YJProxy的消息转发方法methodSignatureForSelector:设置方法签名forwardInvocation:自身不做业务处理,将消息转发给object
-
当页面退出时,
ViewController可以正常释放- 在
dealloc中,对NSTimer进行释放。此时NSTimer对proxy的强持有解除,proxy也跟着释放
- 在
总结:
循环引用:
-
创建
NSTimer时,使用带有target参数的方法,会对传入的对象进行强引用。如果传入的是持有timer的对象,双发会相互持有,造成循环引用 -
不能在
UIViewController的dealloc方法中释放timer。只要timer有效,UIViewController的dealloc方法就不会执行。故此双方相互等待,谁都无法释放 -
对
NSTimer的target参数传入一个弱引用的self没有任何意义,因为在创建NSTimer的方法内部,使用强引用对象接收target参数
常规解决方案:
-
使用携带
Block的方法创建NSTimer,避免target的强持有 -
根据业务需求,在适当时机调用
invalidate。例如:viewWillDisappear、didMoveToParentViewController
切断target的强持有:
-
中介者模式
-
封装自定义
Timer -
使用
NSProxy虚基类