循环引用
static int num = 0;
@interface LGTimerViewController ()
@property (nonatomic, strong) NSTimer *timer;
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
这是NSTimer
的用法,会发生循环引用导致对象无法释放。
self对timer进行了强持有,timer又对self进行了强持有,如果timer的target交给weakSelf还是无法解决,因为NSRunLoop 对timer进行了强持有,导致weakSelf和self都无法释放。timer持有的是对象的内存,block持有的是指针地址。
(lldb) po self
(lldb) po weakSelf
(lldb) p &self
(LGTimerViewController **) $6 = 0x00000001336d0fc8
(lldb) p &weakSelf
(LGTimerViewController *const *) $7 = 0x00007ffee5f25b28
(lldb)
所以无法通过weak来打破。
思路一:dealloc 不能来 那我们能不能看看有没有其他的方法在pop的时候 就销毁timer
需要调用释放方法。
[self.timer invalidate];
self.timer = nil;
这个方法无法放在dealloc
中,因为对象没有释放就不会调用dealloc。
放在viewWillDisappear
,push到下一层返回就不会走了。我们可以放在didMoveToParentViewController
中
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 无论push 进来 还是 pop 出去 正常跑
// 就算继续push 到下一层 pop 回去还是继续
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
也可以使用block形式的NSTimer scheduledTimerWithTimeInterval
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer fire - %@",timer);
}];
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
思路二:中介者模式 - 不方便使用 self
@property (nonatomic, strong) id target;
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
我们把target交给其他对象。self就会释放,但是NSObject对象无法释放。最后在dealloc的时候
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
思路三:自定义封装timer
这种方式是根据思路二的原理,自定义封装timer,其步骤如下
自定义timerWapper
在初始化方法中,定义一个timer,其target是自己。即timerWapper中的timer,一直监听自己,判断selector,此时的selector已交给了传入的target(即vc对象),此时有一个方法fireHomeWapper,在方法中,判断target是否存在
如果target存在,则需要让vc知道,即向传入的target发送selector消息,并将此时的timer参数也一并传入,所以vc就可以得知fireHome方法,就这事这种方式定时器方法能够执行的原因
如果target不存在,已经释放了,则释放当前的timerWrapper,即打破了RunLoop对timeWrapper的强持有 (timeWrapper <-×- RunLoop)
自定义J_invalidate方法中释放timer。这个方法在vc的dealloc方法中调用,即vc释放,从而导致timerWapper释放,打破了vc对timeWrapper的的强持有( vc -×-> timeWrapper)
//*********** .h文件 ***********
@interface JTimerWapper : NSObject
- (instancetype)J_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)J_invalidate;
@end
//*********** .m文件 ***********
#import "JTimerWapper.h"
#import
@interface JTimerWapper ()
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL aSelector;
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation JTimerWapper
- (instancetype)J_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
//传入vc
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);
//给timerWapper添加方法
class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
//启动一个timer,target是self,即监听自己
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
}
}
return self;
}
//一直跑runloop
void fireHomeWapper(JTimerWapper *wapper){
//判断target是否存在
if (wapper.target) {
//如果存在则需要让vc知道,即向传入的target发送selector消息,并将此时的timer参数也一并传入,所以vc就可以得知`fireHome`方法,就这事这种方式定时器方法能够执行的原因
//objc_msgSend发送消息,执行定时器方法
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(wapper.target), wapper.aSelector,wapper.timer);
}else{
//如果target不存在,已经释放了,则释放当前的timerWrapper
[wapper.timer invalidate];
wapper.timer = nil;
}
}
//在vc的dealloc方法中调用,通过vc释放,从而让timer释放
- (void)J_invalidate{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
timerWapper的使用
//定义
self.timerWapper = [[JTimerWapper alloc] J_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
//释放
- (void)dealloc{
[self.timerWapper J_invalidate];
}
运行结果如下
思路三:虚基类NSProxy
NSProxy什么也没有做,我们需要自定义一个来继承,然后来进行消息转发。
@interface LGProxy()
@property (nonatomic, weak) id object;
@end
@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
LGProxy *proxy = [LGProxy alloc];
proxy.object = object;
return proxy;
}
// 仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。
// 转移
// 强引用 -> 消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
//// sel - imp -
//// 消息转发 self.object
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
//
// if (self.object) {
// }else{
// NSLog(@"麻烦收集 stack111");
// }
// return [self.object methodSignatureForSelector:sel];
//
//}
//
//- (void)forwardInvocation:(NSInvocation *)invocation{
//
// if (self.object) {
// [invocation invokeWithTarget:self.object];
// }else{
// NSLog(@"麻烦收集 stack");
// }
//
//}