iOS 循环引用的问题总结

原因:

self -> Timer -> target(self), 造成循环引用

导致控制器不会销毁,不会调用dealloc 方法,内存泄漏

- (void)dealloc{
    [_timer invalidate];
    NSLog(@"********************delloc**************8");
}

 

解决方式:

1.API block的方式 iOS10以后可用

__weak typeof(self)weakself = self;
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        weakself.count++;
        NSLog(@"-------%ld",weakself.count);
    }];

 

2.手动破解循环

在适当的时机调用 (达到一定条件或者 例如在- (void)viewDidDisappear:(BOOL)animated {}调用,)

[self.timer invalidate];
self.timer = nil;

 

3. 借助runtime给对象添加消息处理的能力, 这种方式虽然能破解循环,但是 test 方法里获取到的self 是 _objct ,故感觉不太实用

//        借助runtime给对象添加消息处理的能力
    _objct = [[NSObject alloc] init];
    class_addMethod([_objct class], @selector(test), class_getMethodImplementation([self class], @selector(test)), "v@:");
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:@"ddd" repeats:YES];

 

4.消息转发机制

创建一个中间类 PHJProxy,


@interface PHJProxy : NSObject

@property (nonatomic, weak) id target;

 

@end


@implementation
PHJProxy //方法1: //// 发送给target //- (void)forwardInvocation:(NSInvocation *)invocation { // [invocation invokeWithTarget:self.target]; //} //// 给target注册一个方法签名 //- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel { // return [self.target methodSignatureForSelector:sel]; //} // 方法2: //仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。 -(id)forwardingTargetForSelector:(SEL)aSelector { return self.target; } @end

这2种方式原理都是一样,使用消息的转发机制,把消息转发到 target 中

 

 

补充:如果PHJProxy类 继承至NSProxy 时:

@interface LWProxy : NSProxy

@property (nonatomic, weak) id  target;

+ (instancetype)proxyWithTarget:(id)target;

@end

/**
 NSProxy 类,是专门做消息转发的代理类,如果本类中没有方法实现,则不会去父类中查找,直接进入 -()methodSignatureForSelector{},进行转发
 因此此类做消息转发比NSObject 效率更高
 */
@implementation LWProxy

+ (instancetype)proxyWithTarget:(id)target
{
//    只有alloc方法,没有init方法
    LWProxy *proxy = [LWProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}

@end

 

5. NSTimer 分类的方式,

@implementation NSTimer (LWTimer)
+ (NSTimer *)lw_timerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats
{
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(timermethod:) userInfo:[block copy] repeats:YES];
}

+ (void)timermethod:(NSTimer *)timer
{
    void (^block)() = timer.userInfo;
    if (block) {
        block();
    }
}
@end

上述创建方式调用者是NSTImer自己,只是NSTimer捕获了参数block。这样我们在使用timer时,由于target的改变,就不再有循环引用了。

使用方式:

__weak typeof(self)weakself = self;

_timer = [NSTimer lw_timerWithTimeInterval:2 block:^{
       weakself.count++;
       NSLog(@"-------%ld",weakself.count);
    } repeats:YES];

iOS10中,定时器的API新增了block方法,实现原理与此类似,这里采用分类为NSTimer添加了带有block参数的方法,而系统是在原始类中直接添加方法,最终的行为是一致的。

 

注意:

1、把timer改成弱引用

@property (nonatomic, weak) NSTimer *timer;
虽然self对timer是弱引用,但是控制的delloc方法的执行依赖于timer的invalidate,timer的invalidate又依赖于控制器的delloc方法,这是一个鸡生蛋还是蛋生鸡的问题,依旧是循环引用

2. 使用__weak 那换个思路能不能让NSTimer弱引用target

__weak typeof(self) weakSelf = self;

 self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(showMsg) userInfo:nil repeats:YES];

 

weak关键字适用于block,当block引用了块外的变量时,会根据修饰变量的关键字来决定是强引用还是弱引用,如果变量使用weak关键字修饰,那block会对变量进行弱引用,如果没有__weak关键字,那就是强引用。
  但是NSTimer的 scheduledTimerWithTimeInterval:target方法内部不会判断修饰target的关键字,所以这里传self 和 weakSelf是没区别的,其内部会对target进行强引用,还是会产生循环引用。

 

posted @ 2020-06-30 16:00  新司机上路  阅读(282)  评论(0编辑  收藏  举报
本人qq1365102044,有问题欢迎沟通!