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是一個很是有用的抽象類,固然還有其餘用途,好比可以模擬多繼承,待後續補充。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息