• 解决NSTimer循环引用


    NSTimer常见用法

     1 @interface XXClass : NSObject
     2 - (void)start;
     3 - (void)stop;
     4 @end
     5 
     6 @implementation XXClass {
     7     NSTimer *timer;
     8 }
     9 
    10 - (id)init {
    11     return [super init];
    12 }
    13 
    14 - (void)dealloc {
    15     [timer]
    16 }
    17 
    18 - (void)stop {
    19     [timer invalidate];
    20     timer = nil;
    21 }
    22 
    23 - (void)start {
    24     timer = [NSTimerscheduledTimerWithTimeInterval:5.0 
    25                                             target:self  
    26                                           selector:selector(doSomething) 
    27                                           userInfo:nil 
    28                                            repeats:YES];
    29 }
    30 
    31 - (void)doSomething {
    32     //doSomething
    33 }
    34 
    35 @end

    创建定时器的时候,由于目标对象是self,所以要保留此实例。然而,因为定时器是用实例变量存放的,所以实例也保留了定时器,这就造成了循环引用。除非调用stop方法,或者系统回收实例,才能打破循环引用,如果无法确保stop一定被调用,就极易造成内存泄露。

    当指向XXClass实例的最后一个外部引用移走之后,该实例仍然会继续存活,因为定时器还保留着它。而定时器对象也不可能被系统释放,因为实例中还有一个强引用正在指向它。这种内存泄露是很严重的,如果定时器每次轮训都执行一些下载工作,常常会更容易导致其他内存泄露问题。

    针对于此,有人想到利用block来避免这种循环应用。

    Block解决循环引用

     1 @interface NSTimer (XXBlocksSupport)
     2 
     3 + (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
     4                                          block:(void(^)())block
     5                                        repeats:(BOOL)repeats;
     6 
     7 @end
     8 
     9 @implementation NSTimer (XXBlocksSupport)
    10 
    11 + (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
    12                                          block:(void(^)())block
    13                                        repeats:(BOOL)repeats
    14 {
    15     return [self scheduledTimerWithTimeInterval:interval
    16                                           target:self
    17                                         selector:@selector(xx_blockInvoke:)
    18                                         userInfo:[block copy]
    19                                          repeats:repeats];
    20 }
    21 
    22 + (void)xx_blockInvoke:(NSTimer *)timer {
    23     void (^block)() = timer.userinfo;
    24     if(block) {
    25         block();
    26     }
    27 }
    28 
    29 @end
    30 //调用
    31 - (void)start {
    32     __weak XXClass *weakSelf = self;
    33     timer = [NSTimer xx_scheduledTimerWithTimeInterval:.5
    34                                                  block:^{
    35                                                  XXClass *strongSelf = weakSelf;
    36                                                  [strongSelf doSomething];
    37                                                         }
    38                                                repeats:YES];
    39 }

    定时器现在的target是NSTimer类对象,这是个单例,此处依然有类对象的循环引用.下面介绍更好的解决方式weakProxy。

    weakProxy解决循环引用

    NSProxy

    NSProxy本身是一个抽象类,它遵循NSObject协议,提供了消息转发的通用接口。NSProxy通常用来实现消息转发机制和惰性初始化资源。

    使用NSProxy,你需要写一个子类继承它,然后需要实现init以及消息转发的相关方法。

    1 //当一个消息转发的动作NSInvocation到来的时候,在这里选择把消息转发给对应的实际处理对象
    2 - (void)forwardInvocation:(NSInvocation *)anInvocation
    3 
    4 //当一个SEL到来的时候,在这里返回SEL对应的NSMethodSignature
    5 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    6 
    7 //是否响应一个SEL
    8 + (BOOL)respondsToSelector:(SEL)aSelector

    消息转发机制

    消息转发涉及到三个核心方法

    1 //消息转发第一步,在这里可以动态的为类添加方法,这样类自己就能处理了
    2 +resolveInstanceMethod:
    3 //消息转发第二步,在第一步无法完成的情况下执行。这里只是把一个Selector简单的转发给另一个对象
    4 - forwardingTargetForSelector:
    5 //消息转发第三步,在第二步也无法完成的情况下执行。将整个消息封装成NSInvocation,传递下去
    6 - forwardInvocation:

    消息转发机制使得代码变的很灵活:一个类本身可以完全不实现某些方法,它只要能转发就可以了。

    WeakProxy来实现弱引用

    @interface WeakProxy : NSProxy
    @property (weak,nonatomic,readonly)id target;
    + (instancetype)proxyWithTarget:(id)target;
    - (instancetype)initWithTarget:(id)target;
    @end
    
    @implementation WeakProxy
    - (instancetype)initWithTarget:(id)target{
        _target = target;
        return self;
    }
    + (instancetype)proxyWithTarget:(id)target{
        return [[self alloc] initWithTarget:target];
    }
    - (void)forwardInvocation:(NSInvocation *)invocation{
        SEL sel = [invocation selector];
        if ([self.target respondsToSelector:sel]) {
            [invocation invokeWithTarget:self.target];
        }
    }
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        return [self.target methodSignatureForSelector:aSelector];
    }
    - (BOOL)respondsToSelector:(SEL)aSelector{
        return [self.target respondsToSelector:aSelector];
    }
    @end

    外部创建Timer

      self.timer = [NSTimer timerWithTimeInterval:1
                                             target:[WeakProxy proxyWithTarget:self]
                                           selector:@selector(timerInvoked:)
                                           userInfo:nil
                                            repeats:YES];

    原理如下:

    我们把虚线处变成了弱引用。于是,Controller就可以被释放掉,我们在Controller的dealloc中调用invalidate,就断掉了Runloop对Timer的引用,于是整个三个淡蓝色的就都被释放掉了。

    Reference:

    1.用Block解决NSTimer循环引用

    2.NSProxy与消息转发机制

  • 相关阅读:
    CSP-S2020总结
    题解-P6687 论如何玩转 Excel 表格
    题解-UVA12995 【Farey Sequence】
    题解-P4159 [SCOI2009] 【迷路】
    题解-SP2916【GSS5
    102. 二叉树的层序遍历
    力扣 160 相交链表 快慢指针 双指针
    3. 无重复字符的最长子串 滑动窗口
    最大连续1的个数 III
    B树和B+树
  • 原文地址:https://www.cnblogs.com/H7N9/p/6540578.html
Copyright © 2020-2023  润新知