什么是强引用
- 我们在使用
NSTimer
的scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
方法创建定时器使用的时候,timer会对传入的target进行强引用直到timer无效。 - 这里是官方文档上面的描述
- 我们通过使用一个上述方法创建的timer来观察这些现象,在一个push进来的
TimerViewController
实现一个timer打印事件
static int num = 0;
@interface TimerViewController ()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation TimerViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1. target:self selector:@selector(printString) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}
- (void)printString
{
num++;
NSLog(@"hello world--%d",num);
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
- 通过返回回到上一个界面的时候发现并没有执行
dealloc
,而且计时器仍然继续运行。因为有循环引用导致TimerViewController
无法调用dealloc
- 通过我们之前在block中解决循环引用的方法,我们用一个weakSelf尝试解决这种循环引用。修改代码
__weak typeof (&*self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1. target:weakSelf selector:@selector(printString) userInfo:nil repeats:YES];
- 运行代码,发现还是没有执行dealloc方法,原因是block捕获的weakSelf是对象的指针地址,然后通过地址再去操作内存数据,而timer持有的是weakSelf指向的对象的内存空间。
- timer对
target
就是强引用。
weakSlf&self
- 通过打印来观察
weakSlf
和self
的不同 - 可以看到
weakSlf
和self
的地址不是一样的,指向的是同一片内存空间 - 再来看下
weakSlf
有没有对self
引用计数进行操作,发现weakSelf
并没有对self
的引用计数有操作。
解决强引用问题
在界面结束的方法里面将timer设为nil
- 重写
didMoveToParentViewController
方法
- (void)didMoveToParentViewController:(UIViewController *)parent
{
[super didMoveToParentViewController:parent];
if (parent == nil) {
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 结束了");
}
}
}
- 运行代码,发现确实解决了循环引用的问题
中介者模式
- 中介者模式就是不使用self,由于使用self会有引用循环,所以我们可以将
target
换成其他对象,我创建一个NSObject
的实例对象作为target
。
NSObject *obj = [NSObject alloc];
class_addMethod([obj class], @selector(printString), (IMP)printStringWithObj, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1. target:obj selector:@selector(printString) userInfo:nil repeats:YES];
- 运行代码,发现
TimerViewController
执行了dealloc
,但是obj并没有被释放,timer也在继续执行,因为runloop强持有了timer,而且runloop的生命周期比控制器长,所以我们需要在dealloc
里将timer停止并设为nil。
- (void)dealloc
{
NSLog(@"%s",__func__);
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 结束了");
}
}
- 再次运行就发现已经解决了上述问题。
基于中介者模式的封装
- 通过上面代码我们知道中介者模式可以解决强引用的问题,我们在此基础上封装一个
timer
。
@interface XQTimerWapper : NSObject
- (instancetype)xq_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)xq_invalidate;
@end
#import "XQTimerWapper.h"
#import <objc/message.h>
@interface XQTimerWapper ()
@property (nonatomic, weak) id aTarget;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation XQTimerWapper
- (instancetype)xq_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo
{
if (self == [super init]) {
self.aTarget = aTarget;
self.aSelector = aSelector;
if ([self.aTarget respondsToSelector:aSelector]) {
Method method = class_getInstanceMethod([self.aTarget class], aSelector);
const char *type = method_getTypeEncoding(method);
class_addMethod([self class], aSelector, (IMP)doSomeThing, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
void doSomeThing(XQTimerWapper *timerWapper)
{
if (timerWapper.aTarget) {
void (*xq_msgSend)(void* ,SEL ,id ) = (void *)objc_msgSend;
xq_msgSend((__bridge void *)(timerWapper.aTarget),timerWapper.aSelector,timerWapper.timer);
}else{
[timerWapper.timer invalidate];
timerWapper.timer = nil;
}
}
- (void)xq_invalidate
{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
- 在
TimerViewController
直接赋值,再在dealloc
里调用停止方法。
- (void)viewDidLoad {
[super viewDidLoad];
self.timerWapper = [[XQTimerWapper alloc] xq_initWithTimeInterval:1. target:self selector:@selector(printString) userInfo:nil repeats:YES];
}
- (void)printString
{
num++;
NSLog(@"hello world--%d",num);
}
- (void)dealloc
{
NSLog(@"%s",__func__);
[self.timerWapper xq_invalidate];
}
基于虚基类NSProxy
- OC是只能单继承的语言,但是它是基于运行时的机制,所以可以通过NSProxy来实现伪多继承,填补了多继承的空白
NSProxy
和NSObject
是同级的一个类,也可以说是一个虚拟类,只是实现了NSObject的协议NSProxy
其实是一个消息重定向封装的一个抽象类,类似一个代理人,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
- NSProxy的使用场景主要有两种
- 实现多继承功能
- 解决了NSTimer&CADisplayLink创建时对self强引用问题,参考YYKit的YYWeakProxy。
循环引用解决原理:主要是通过自定义的NSProxy类的对象来代替self,并使用方法实现消息转发
- 自定义一个继承自
NSProxy
的类
@interface XQProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface XQProxy ()
@property (nonatomic, weak) id object;
@end
@implementation XQProxy
+ (instancetype)proxyWithTransformObject:(id)object
{
XQProxy *proxy = [XQProxy alloc];
proxy.object = object;
return proxy;
}
// 强引用 -> 消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
- 修改执行代码
- (void)viewDidLoad {
[super viewDidLoad];
self.proxy = [XQProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1. target:self.proxy selector:@selector(printString) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}
- (void)printString
{
num++;
NSLog(@"hello world--%d",num);
}
- (void)dealloc
{
NSLog(@"%s",__func__);
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 结束了");
}
}
- 运行代码,发现也成功解决了强引用的问题,我们平常开发中是推荐用这种方式的来解决循环引用问题的