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虚基类



