-
?? 上一節(jié) 牧抽,詳細(xì)介紹了
TaggedPointer
跋理、retain
勺择、release
丹弱、dealloc
。本節(jié)俗批,我們將介紹:
- ARC & MRC
- strong & weak
- 強(qiáng)弱引用
準(zhǔn)備工作:
- 可編譯的
objc4-781
源碼: http://www.reibang.com/p/45dc31d91000
1. ARC & MRC
Objective-C
提供了兩種內(nèi)存管理機(jī)制
:
-
MRC
(Mannul Reference Counting
手動(dòng)管理引用計(jì)數(shù)) -
ARC
(Automatic Reference Counting
自動(dòng)管理引用計(jì)數(shù))
MRC(手動(dòng)管理引用計(jì)數(shù))
- 通過
alloc
寓落、new
溶诞、copy
凌箕、mutableCopy
生成的對(duì)象,持有時(shí)
词渤,需要使用retain
牵舱、release
、autoRelease
管理引用計(jì)數(shù)
retain
:對(duì)象
的引用計(jì)數(shù)
+1release
:對(duì)象
的引用計(jì)數(shù)
-1autoRelease
:自動(dòng)對(duì)作用域內(nèi)
的對(duì)象
進(jìn)行一次retain
和release
操作缺虐。
MRC模式
下芜壁,必須遵守:誰(shuí)創(chuàng)建
,誰(shuí)釋放
高氮,誰(shuí)引用
慧妄,誰(shuí)管理
ARC(自動(dòng)管理引用計(jì)數(shù))
ARC
在WWDC2011
上公布,iOS5
系統(tǒng)引入的自動(dòng)管理機(jī)制
剪芍,是LLVM
和Runtime
配合的結(jié)果塞淹,在編譯期
和運(yùn)行時(shí)
都會(huì)進(jìn)行內(nèi)存管理
。ARC
中禁止
手動(dòng)調(diào)用retain
罪裹、release
饱普、retainCount
、dealloc
状共,轉(zhuǎn)而使用weak
套耕、strong
屬性關(guān)鍵字。
- 現(xiàn)在都是直接使用
ARC
峡继,由系統(tǒng)
自動(dòng)管理引用計(jì)數(shù)了冯袍。
2. strong & weak
- 關(guān)于
strong
和weak
,可以在objc4源碼中進(jìn)行探索碾牌。 現(xiàn)在將流程圖
和總結(jié)
記錄一下:
2.1 weak
-
weak
是不處理
(對(duì)象)的引用計(jì)數(shù)
颠猴,而是使用
一個(gè)哈希結(jié)構(gòu)
的弱引用表
進(jìn)行信息
的保存
。 - 當(dāng)
對(duì)象
本身的引用計(jì)數(shù)
為0
時(shí)小染,調(diào)用dealloc
函數(shù)翘瓮,觸發(fā)weak
表的釋放
。
弱引用表
的存儲(chǔ)
細(xì)節(jié)
weak
使用weakTable弱引用表
進(jìn)行存儲(chǔ)信息
裤翩,是sideTable散列表
(哈希表)結(jié)構(gòu)资盅。- 創(chuàng)建
weak_entry_t
,將referent
引用計(jì)數(shù)加入到weak_entry_t
的數(shù)組inline_referrers
中踊赠。- 支持
weak_table
擴(kuò)容呵扛,把new_entry
加入到weak_table
中
2.2 strong
-
strong
修飾,實(shí)際是新值
的retain
和舊值
的release
:
總結(jié):
weak
:不
處理引用計(jì)數(shù)
筐带,使用弱引用表
進(jìn)行信息存儲(chǔ)
今穿,dealloc
時(shí)移除記錄
。strong
:內(nèi)部
使用retain
和release
進(jìn)行引用計(jì)數(shù)
的管理
伦籍。
- 關(guān)于
retain
蓝晒、release
腮出、dealloc
的內(nèi)容,請(qǐng)移步 ?? OC底層原理三十五:內(nèi)存管理(TaggedPointer芝薇、引用計(jì)數(shù))
3. 強(qiáng)弱引用
- 以
NSTimer(計(jì)時(shí)器)
為切入點(diǎn)
胚嘲,代碼案例
:
- (void)createTimer {
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)fireHome{
num++;
NSLog(@"hello word - %d",num);
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
NSTimer
創(chuàng)建后,需要手動(dòng)加入
到Runloop
中才可以運(yùn)行
洛二,但timer
會(huì)使得當(dāng)前控制器
不走dealloc
方法馋劈,導(dǎo)致timer
和控制器
都無法釋放。
下面晾嘶,我們就來解決2個(gè)問題:
- 為什么?(
timer
加入后妓雾,控制器無法釋放
) - 如何解決?
3.1 強(qiáng)持有
拓展:
無法釋放
,一般是循環(huán)引用
導(dǎo)致(可參考 ?? 循環(huán)引用)
(注意:self
作為參數(shù)
傳入垒迂,不會(huì)被【自動(dòng)持有】
君珠,除非內(nèi)部
手動(dòng)強(qiáng)引用
了self
。)
- 而
NSTimer
的timerWithTimeInterval:target:selector:userInfo:repeats:
方法娇斑,就手動(dòng)強(qiáng)引用
了self
一般來說策添,
循環(huán)引用
可以通過加入弱引用
對(duì)象,打斷循環(huán)
:self -> timer ->加入weakself
-> self對(duì)毫缆,
原理沒錯(cuò)
唯竹。但前提是:timer
?僅被self
持有,且timer
僅拷貝weakself
指針苦丁!很不巧:
- 當(dāng)前
timer
除了被self持有
浸颓,還被加入了[NSRunLoop currentRunLoop]
中- 當(dāng)前
timer
直接指向self
的內(nèi)存空間
,是對(duì)內(nèi)存
進(jìn)行強(qiáng)持有
旺拉,而不是
簡(jiǎn)單的指針拷貝
产上。
所以currentRunLoop
沒結(jié)束,timer
就不會(huì)釋放
蛾狗,self
的內(nèi)存空間
也不會(huì)釋放
晋涣。
block
捕獲外界變量:捕捉的是指針地址
。timer
捕捉的是對(duì)象本身(內(nèi)存空間)
3.2 解決方法
方法1:didMoveToParentViewController
手動(dòng)打斷循環(huán)
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 無論push 進(jìn)來 還是 pop 出去 正常跑
// 就算繼續(xù)push 到下一層 pop 回去還是繼續(xù)
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
方法2:不加入Runloop
沉桌,使用官方閉包API
- (void)createTimer{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer fire - %@",timer);
}];
}
方法3:中介者模式(不使用self)
- 既然
timer
會(huì)強(qiáng)持有
對(duì)象(內(nèi)存空間
)谢鹊,我們就給他一個(gè)中介者
的內(nèi)存空間
,讓他碰不到self
,我們?cè)賹?duì)中介者
操作和釋放
。 -
HTTimer.h
文件:
@interface HTTimer : NSObject
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)repeats;
- (void)invalidate;
- (void)fire;
@end
-
HTTimer.m
文件:
@interface HTTimer ()
@property (nonatomic, strong) NSTimer * timer;
@property (nonatomic, weak) id aTarget;
@property (nonatomic, assign) SEL aSelector;
@end
@implementation HTTimer
+ (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)repeats {
HTTimer * timer = [HTTimer new];
timer.aTarget = aTarget;
timer.aSelector = aSelector;
timer.timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:timer selector:@selector(run) userInfo:userInfo repeats:repeats];
[[NSRunLoop currentRunLoop] addTimer:timer.timer forMode:NSRunLoopCommonModes];
return timer;
}
- (void)run {
//如果崩在這里留凭,說明你沒有在使用Timer的VC里面的deinit方法里調(diào)用invalidate方法
if(![self.aTarget respondsToSelector:_aSelector]) return;
// 消除警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.aTarget performSelector:self.aSelector];
#pragma clang diagnostic pop
}
- (void)fire {
[_timer fire];
}
- (void)invalidate {
[_timer invalidate];
_timer = nil;
}
- (void)dealloc
{
// release環(huán)境下注釋掉
NSLog(@"計(jì)時(shí)器已銷毀");
}
@end
- 使用方法:
@interface TimerViewController ()
@property (nonatomic, strong) HTTimer * timer;
@end
@implementation TimerViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建
self.timer = [HTTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
}
- (void)fireHome{
NSLog(@"hello word" ); // 調(diào)用
}
- (void)dealloc{
// 釋放
[self.timer invalidate];
NSLog(@"%s",__func__);
}
@end
方法4 NSProxy虛基類
- 與
NSObject
同級(jí)佃扼,但內(nèi)部什么都沒有
,但是可以持有對(duì)象
蔼夜,并將消息
全部轉(zhuǎn)發(fā)
給對(duì)象
兼耀。
(ps: 我啥也沒有
,但我也是對(duì)象
,我可以
把你需求
全部傳遞
給能辦事
的對(duì)象
)
這就是代理模式
瘤运,timer
持有代理
窍霞,代理
weak弱引用
持有self
,再把所有消息轉(zhuǎn)發(fā)
給self
尽超。
-
HTProxy.h
文件
@interface HTProxy : NSProxy
/// 麻煩把消息轉(zhuǎn)發(fā)給`object`
+ (instancetype)proxyWithTransformObject:(id)object;
@end
-
HTProxy.m
文件
#import "HTProxy.h"
@interface HTProxy ()
@property (nonatomic, weak) id object; // 弱引用object
@end
@implementation HTProxy
/// 麻煩把消息轉(zhuǎn)發(fā)給`object`
+ (instancetype)proxyWithTransformObject:(id)object {
HTProxy * proxy = [HTProxy alloc];
proxy.object = object;
return proxy;
}
// 消息轉(zhuǎn)發(fā)。 (所有消息梧躺,都轉(zhuǎn)發(fā)給object去處理)
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
// 消息轉(zhuǎn)發(fā) self.object(可以利用虛基類似谁,進(jìn)行數(shù)據(jù)收集)
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
//
// if (self.object) {
// }else{
// NSLog(@"麻煩收集 stack111");
// }
// return [self.object methodSignatureForSelector:sel];
//
//}
//
//- (void)forwardInvocation:(NSInvocation *)invocation{
//
// if (self.object) {
// [invocation invokeWithTarget:self.object];
// }else{
// NSLog(@"麻煩收集 stack");
// }
//
//}
-(void)dealloc {
NSLog(@"%s",__func__);
}
@end
- 使用方法:
@interface TimerViewController ()
@property (nonatomic, strong) HTProxy * proxy;
@property (nonatomic, strong) NSTimer * timer;
@end
@implementation TimerViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建虛基類代理
self.proxy = [HTProxy proxyWithTransformObject: self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
}
- (void)fireHome{
NSLog(@"hello word" ); // 調(diào)用
}
- (void)dealloc{
// 釋放
[self.timer invalidate];
NSLog(@"%s",__func__);
}
@end
虛基類
的代理模式
使用非常方便
,使用場(chǎng)景
也很多
掠哥。(注意proxy
中是weak
弱引用object
)下一節(jié)巩踏,介紹
自動(dòng)釋放池
和runloop