小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
切断target强持有
除了常规的方法解决循环引用的问题,还可以通过切段target的强持有,解决循环引用的问题。
1.1 中介者模式
- (void)viewDidLoad {
[super viewDidLoad];
NSObject *objc = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(fireHome) userInfo:nil repeats:YES];
}
void fireHomeObjc(id obj){
num++;
NSLog(@"hello word - %d -%@",num, obj);
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
复制代码
- 创建的NSObject实例对象objc,通过Runtime对NSObject增加fireHome方法,IMP指向fireHomeObjc的函数地址
- 创建NSTimer,将objc传入target参数,这样避免NSTimer对self的强持有
- 当页面退出时,由于self没有被NSTimer持有,正常调用dealloc方法
- 在dealloc中,对NSTimer进行释放。此时NSTimer对objc的强持有接触,objc也跟着释放
1.2 封装自定义的Timer
创建LGTimerWapper,实现自定义Timer的封装 打开LGTimerWapper.h文件,写入以下代码:
#import <Foundation/Foundation.h>
@interface LGTimerWapper : NSObject
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)lg_invalidate;
@end
复制代码
打开LGTimerWapper.m文件,写入以下代码:
#import "LGTimerWapper.h"
#import <objc/message.h>
@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LGTimerWapper
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
self.target = aTarget;
self.aSelector = aSelector;
if ([self.target respondsToSelector:self.aSelector]) {
Method method = class_getInstanceMethod([self.target class], aSelector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
void fireHomeWapper(LGTimerWapper *warpper){
if (warpper.target) {
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
} else {
[warpper lg_invalidate];
}
}
- (void)lg_invalidate{
[self.timer invalidate];
self.timer = nil;
}
@end
复制代码
LGTimerWapper的调用代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
}
- (void)fireHome{
num++;
}
复制代码
- 在LGTimerWapper中,定义初始化lg_initWithtimInterval方法和lg_invalidate释放方法
- 初始化方法
- 控件内部的target使用weak修饰,对ViewController进行弱持有
- 监测target中是否存在该selector。如果存在,对当前类使用Runtime添加同名方法编号,指向自身内部fireHomeWapper的函数地址
- 创建真正的NSTimer定时器,将控件自身的实例对象传入target,避免NSTimer对ViewController强持有。
- 当NSTimer回调时,会进入fireHomeWapper函数
- 函数内部不负责业务处理,如果target存在,使用objc_msgSend,将消息发送给target自身下的selector方法。
- 当页面退出时,ViewController可以正常释放。但LGTimerWapper和NSTimer相互持有,双方都无法释放。
- 由于双方都无法释放,NSTimer的回调会继续调用
- 当进入fireHomeWapper函数,发现target已经不存在了,调用LGTimerWapper的lg_invalidate方法,内部对NSTimer进行释放
- 当NSTimer释放后,对LGTimerWapper的强持有解除,LGTimerWapper也跟着释放。