内存管理一:NSTimer
NSTimer会对target产生强引用,如果target再对NSTimer产生强引用就会产生循环引用.我们直接用代码演示:
@interface ViewController ()
@property (nonatomic,strong)NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction{
NSLog(@".");
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s---%@...",__func__,self.obj);
}
@end
以上代码每秒中调用一次timerAction,即使已经退出当前控制器还会继续调用.虽然我们已经重写了dealloc方法,并且在dealloc方法内部调用了timer的invalidate方法,并且手动把timer置为nil.上述代码的dealloc是永远不会调用的,因为timer和viewcontroller已经产生了循环引用.有人会想使用__weak修饰self不就可以了吗,像下面这样:
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(timerAction) userInfo:nil repeats:YES];
结果是这样仍然也解决不了问题,之前我们使用__weak是解决block的循环引用的.之所以能解决block的循环引用是因为blcok内部捕获的外部变量的引用关系取决于外部变量的修饰符.大家不要搞混淆了.而在NSTimer内部会强引用传进来的target.
那我们怎么解决这个问题呢?可以换一种初始化方法,使用带有block的初始化方法:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@".");
}];
这样就能解决循环引用的问题.补充一点,使用scheduled的方式创建的定时器会自动加入到当前线程的default mode模式下.不需要我们手动添加到runloop,其他方式创建的需要手动添加到runloop.我自己做了一个总结:
创建timer的两种方式
- 还有一种计时器:
CADisplayLink.这种计时器不用设置间隔时间,它会和屏幕的刷帧保持一样的频率调用方法,也就是60FPS (一秒钟60次,可能会不准确,如果说主线程有耗时的操作会影响到刷帧频率).
self.linkTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(timerAction)];
[self.linkTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CADisplayLink没有block的创建方式,我们怎么解决循环引用呢?我们画图分析一下:
循环引用
造成循环引用的原因很简单,
NStimer中的target强引用了ViewController,而ViewController中的timer属性又强引用了NStimer.所以就造成了循环引用.我们可以像下面这样再中间加上一层:
中间层
创建一个中间层,让NSTimer强引用这个中间层,中间层弱引用ViewController,就打破了之前的循环引用关系:
@interface TestProxy : NSObject
@property (nonatomic,weak)id target;
+ (id)proxyWithTarget:(id)target;
@end
@implementation TestProxy
+ (id)proxyWithTarget:(id)target{
TestProxy *proxy = [[TestProxy alloc]init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}
@end
--------------------------------------------------------------
@interface ViewController ()
@property (nonatomic,strong)NSTimer *timer;
@property (nonatomic,strong)CADisplayLink *linkTimer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// __weak typeof(self)weakSelf = self;
// self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TestProxy proxyWithTarget:self] selector:@selector(timerAction) userInfo:nil repeats:YES];
//保证定时器的调用频率和屏幕的刷帧评率一样 60FPS
self.linkTimer = [CADisplayLink displayLinkWithTarget:[TestProxy proxyWithTarget:self] selector:@selector(timerAction)];
[self.linkTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)timerAction{
NSLog(@".");
}
- (void)dealloc{
NSLog(@"%s",__func__);
[self.linkTimer invalidate];
self.linkTimer = nil;
}
@end
需要注意的是CADisplayLink也需要手动调用invalidate才能停止.
我们创建了一个中间层TestProxy类,这个类中有一个weak属性target.我们可以把ViewController处理的事情交给这个target处理,在TestProxy内部直接使用消息转发转发给真正处理的对象.
在OC中有一个专门用来处理这种去求的类NSProxy:
@interface NSProxy <NSObject> {
Class isa;
}
我们让NSProxy和NSObject对比一下:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
会发现这两个类非常的像,他们都没有继承任何类,都实现了< NSObject >协议.其实NSProxy和NSObject一样都是基类.只不过NSProxy是专门用来做代理的类.我们看看这个类怎么用:
@interface FormalProxy : NSProxy
@property (nonatomic,weak)id target;
+ (id)proxyWithTarget:(id)target;
@end
@implementation FormalProxy
+ (id)proxyWithTarget:(id)target{
//NSProxy 类中没有init方法,直接alloc就可以使用
FormalProxy *proxy = [FormalProxy alloc];
proxy.target = target;
return proxy;
}
@end
我们在FormalProxy也设置一个target,然后FormalProxy调用ViewController的timerAction方法,但是我们并没有在FormalProxy内部把这个消息转发处理.然后运行代码看看会发生什么:
NSProxy 的报错
可以看到
NSProxy直接报methodSignatureForSelector:] called!找不到这个错误.再看看如果是继承自
NSObject的TestProxy如果没有消息转发会报什么错:
NSObject 报错
可以看到
NSObject直接报unrecognized selector sent to instance这个经典的方法找不到的错误.这就是
NSProxy和NSObject的区别.NSProxy首先查看自己的类中有没有这个方法,如果没有就不会走方法调用的三个阶段.而是直接走methodSignatureForSelector和forwardInvocation进入消息转发.所以我们要在FormalProxy中这样实现:
@interface FormalProxy : NSProxy
@property (nonatomic,weak)id target;
+ (id)proxyWithTarget:(id)target;
@end
@implementation FormalProxy
+ (id)proxyWithTarget:(id)target{
//NSProxy 类中没有init方法,直接alloc就可以使用
FormalProxy *proxy = [FormalProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
有人可能会说NSProxy代码比继承自NSObject的TestProxy代码更复杂一些,是不是TestProxy更方便呢?
当然不是,因为NSProxy是专门用来处理代理这种情况的.它的效率要比继承自NSObject的类更高.因为它不会像继承自NSObject的类那样检索方法.检索不到再进入方法解析.NSProxy会直接进入消息转发.
为了加深一下理解,我们做个小练习题:
TestProxy *objectProxy = [TestProxy proxyWithTarget:self];//继承自 NSObject
FormalProxy *formalProxy = [FormalProxy proxyWithTarget:self];//继承自 NSProxy
NSLog(@"%d",[objectProxy isKindOfClass:[self class]]);
NSLog(@"%d",[formalProxy isKindOfClass:[self class]]);
想想看会打印什么?直接运行一下看看结果:
结果
可以看到继承自NSObject的为false,而继承自NSProxy的为true.这是因为NSProxy直接把isKindOfClass转发给了ViewController处理,所以最后就是ViewController isKindOfClass [self class]结果就为true.