NSProxy&NSTimer

原文连接app

开发过程当中咱们必不可少的须要接触定时器,在iOS中,经常使用的定时器有如下几种:函数

  • GCD Timer
  • CADisplayLink
  • NSTimer

这里咱们主要来看下 NSTimer 的一个问题oop

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSTimer *t;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)startTImer {
    
    _t = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(someBussiness) userInfo:nil repeats:true];
    
    [[NSRunLoop currentRunLoop] addTimer:_t forMode:NSRunLoopCommonModes];
}

- (void)someBussiness {
    
    NSLog(@"timer triggered");
}

- (void)dealloc {
    
    NSLog(@"Controller dealloc");
    
    if (self.t) {
        
        [self.t invalidate];
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (self.presentingViewController) {
        [self dismissViewControllerAnimated:true completion:nil];
    }else {
        
        ViewController *vc = ViewController.new;
        vc.view.backgroundColor = UIColor.grayColor;
        [self presentViewController:vc animated:true completion:nil];
        [vc startTImer];
    }
}

@end
复制代码

这里在咱们点击页面以后,会present出来一个新页面,并开始使用 NSTimer 计时,并在 dealloc 中打印信息。 再次点击present出来的viewController把当前的Controller销毁掉。ui

再次点击咱们会发现,计时器并无中止,并且预期的dealloc中的信息也并无打印,这是为何呢?atom

这里咱们能够使用Xcode的 Debug Memory Graph ,就在下方控制台上面的按键里面,能够看到如图所示spa

image

咱们能够看到这里 Runloop 引用了 timer ,而 timer 又引用了当前的Controller,最终致使Controller没法释放3d

咱们一般会想,那把 NSTimer 的 property 用weak来修饰,或者把timer的target使用 weak 修饰不就行了吗。那咱们来修改一下代码代理

@property (nonatomic, weak) NSTimer *t;

- (void)startTImer {
    
    __weak typeof(self) ws = self;
    
    _t = [NSTimer timerWithTimeInterval:1.0f target:ws selector:@selector(someBussiness) userInfo:nil repeats:true];
    
    [[NSRunLoop currentRunLoop] addTimer:_t forMode:NSRunLoopCommonModes];
}
复制代码

这里咱们修改timer的property为weak,把target也修饰为weak,再次运行。指针

哈 , 仍是没有释放,timer 仍在打印。code

这里实际上是由于Runloop会对加入的Timer自动强引用 , 而timer会对target进行强引用,即便修饰为weak也没用,那么,有咩有什么办法来释放他呢?

- (void)startTImer {
    
    __weak typeof(self) ws = self;
    
    _t = [NSTimer timerWithTimeInterval:1.0f repeats:true block:^(NSTimer * _Nonnull timer) {
        
        [ws someBussiness];
    }];
    
    [[NSRunLoop currentRunLoop] addTimer:_t forMode:NSRunLoopCommonModes];
}
复制代码

😂 😂 😂 改成Block调用的方式就能够了,那么有没有别的方式也能够解决这个问题呢?(固然有了要不这篇我tm是在写啥)

NSProxy


An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.

一个抽象超类,用于定义对象的API,这些对象充当其余对象或尚不存在的对象的替身。

官方文档

使用NSProxy咱们能够把任意的对象隐藏在后面,由这个抽象类在前面为咱们真实的对象代理,固然,咱们须要实现两个方法

- (void)forwardInvocation:(NSInvocation *)invocation;
- 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
复制代码

第一个是方法决议,咱们能够在这里改变方法的指针,更换方法, 第二个是方法签名,用来提供相应的函数返回类型和参数,

接下来咱们新建 TimerProxy 类 继承 NSProxy

TimerProxy.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TimerProxy : NSProxy

+ (instancetype)proxyWithObject:(id)obj;

@end

NS_ASSUME_NONNULL_END
复制代码

TimerProxy.m

#import "TimerProxy.h"

@interface TimerProxy ()

@property (nonatomic, weak) id object;

@end

@implementation TimerProxy


- (instancetype)withProxy:(id)obj {
    
    _object = obj;
    
    return self;
}

+ (instancetype)proxyWithObject:(id)obj {
    
    return [[self alloc] withProxy:obj];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    
    SEL selector = invocation.selector;
    
    if ([_object respondsToSelector:selector]) {
        
        [invocation invokeWithTarget:_object];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_object methodSignatureForSelector:sel];
}

@end
复制代码

再更新一下viewController的实现

#import "ViewController.h"
#import "TimerProxy.h"

@interface ViewController ()

@property (nonatomic, strong) NSTimer *t;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)startTImer {
    
    _t = [NSTimer timerWithTimeInterval:1.0f target:[TimerProxy proxyWithObject:self] selector:@selector(someBussiness) userInfo:nil repeats:true];
    
    [[NSRunLoop currentRunLoop] addTimer:_t forMode:NSRunLoopCommonModes];
}

- (void)someBussiness {
    
    NSLog(@"timer triggered");
}

- (void)dealloc {
    
    NSLog(@"Controller dealloc");
    
    if (self.t) {
        
        [self.t invalidate];
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if (self.presentingViewController) {
        [self dismissViewControllerAnimated:true completion:nil];
    }else {
        
        ViewController *vc = ViewController.new;
        vc.view.backgroundColor = UIColor.grayColor;
        [self presentViewController:vc animated:true completion:nil];
        [vc startTImer];
    }
}

@end
复制代码

应该能够看到正常的dealloc的输出,而且timer也中止了, NSProxy是一个很是有用的抽象类,固然还有其余用途,好比可以模拟多继承,待后续补充。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息