消息轉發、NSProxy解決NSTimer的內存泄漏

如果忘記消息轉發,我們就先來復習一下Runtime筆記四:動態消息轉發。

開始正文:

一.NSProxy是什么:

  • NSProxy是一個抽象的基類,是根類,與NSObject類似
  • NSProxy和NSObject都實現了<NSObject>協議
  • 提供了消息轉發的通用接口

二.解決NSTimer的內存泄漏(解決沒有被釋放的問題)

有一個SecondViewController用了NSTimer,SecondViewController里面按照以下寫法:
假如我PushSecondViewController,然后pop。 會發現Controller沒有被釋放,timer也沒有被取消。

#import "SecondViewController.h"
@interface SecondViewController ()

@property (strong,nonatomic)NSTimer * timer;

@end
@implementation SecondViewController

- (void)viewDidLoad{
    [super viewDidLoad];
    self.timer = [NSTimer timerWithTimeInterval:1
                                         target:self
                                       selector:@selector(timerInvoked:)
                                       userInfo:nil
                                        repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerInvoked:(NSTimer *)timer{
    NSLog(@"1");
}
- (void)dealloc{
    NSLog(@"Dealloc");
}
@end

我們可以在dealloc中,調用Timer取消嗎?比如

- (void)dealloc{
    [self.timer invalidate];
    NSLog(@"Dealloc");
}

當然不行,因為Controller根本沒有被釋放,dealloc方法根本不會調用。
當然,破壞這種循環引用的方式有很多種。本文主要講解如何用NSProxy來破壞。

解決:

1.寫一個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];
}
//消息轉發
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [self.target methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
    SEL sel = [invocation selector];
    if ([self.target respondsToSelector:sel]) {
        [invocation invokeWithTarget:self.target];
    }
}

@end
  1. 然后,這樣創建Timer。讓NSProxy來執行timer的方法,但是NSProxy里面并沒有timer要調用的這個方法,就會執行消息轉發,消息轉發里面讓controller去執行timer的事件。這樣你會發現可以釋放了。
self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerInvoked:) userInfo:nil repeats:YES];

如下圖,我們是把Controller和NSTimer之間的互相引用打破了。新建一個NSProxy來實現這個弱引用。表面上讓NSProxy來實現timer的事件,但是通過消息轉發的方法,讓controller去完成了。

原本是這樣的:


相互引用的時候

使用NSProxy打破循環引用:


使用NSProxy打破循環引用

注意:NSProxy里面對controller是弱引用的。這樣就可以打破循環,controller和timer就可以得到釋放。


總結:
解決NSTimer內存泄漏的辦法
1.使用NSProxy
2.重寫如下的2個方法(消息轉發里面的完整消息轉發):

  • methodSignatureForSelector:
  • forwardInvocation:

參考:NSproxy


寫完之后,我回顧了一下消息轉發。
調用方法即發送消息的時候,如果找不到要執行的方法,就會按以下順序執行消息轉發:
1.動態方法解析:讓當前類里面的其他方法替代未找到的方法執行
2.備用接受者:讓其他對象里面的方法來替代為找到的方法
3.完整消息轉發:和2相同

我們前面用的是3.完整消息轉發,那2.備用接受者應該也可以啊,讓controller成為備用接受者,去controller里面尋找要執行的方法。

如下,把完整消息轉發部分改為備用接收者的方法。成功實現弱引用!

//備用接收者
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

所以,解決NSTimer內存泄漏的辦法
1.使用NSProxy
2.重寫如下的2個方法(消息轉發里面的完整消息轉發):

  • methodSignatureForSelector:
  • forwardInvocation:

或者重寫這個方法

  • (id)forwardingTargetForSelector:(SEL)selector
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容