OC底層原理三十六:內(nèi)存管理(strong & weak & 強(qiáng)弱引用)

OC底層原理 學(xué)習(xí)大綱

  • ?? 上一節(jié) 牧抽,詳細(xì)介紹了TaggedPointer跋理、retain勺择、release丹弱、dealloc。本節(jié)俗批,我們將介紹:
  1. ARC & MRC
  2. strong & weak
  3. 強(qiáng)弱引用

準(zhǔn)備工作:


1. ARC & MRC

Objective-C提供了兩種內(nèi)存管理機(jī)制

  • MRCMannul Reference Counting 手動(dòng)管理引用計(jì)數(shù))
  • ARCAutomatic Reference Counting 自動(dòng)管理引用計(jì)數(shù))

MRC(手動(dòng)管理引用計(jì)數(shù))

  1. 通過alloc寓落、new溶诞、copy凌箕、mutableCopy生成的對(duì)象,持有時(shí)词渤,需要使用retain牵舱、releaseautoRelease管理引用計(jì)數(shù)
  • retain對(duì)象引用計(jì)數(shù)+1
  • release對(duì)象引用計(jì)數(shù)-1
  • autoRelease:自動(dòng)對(duì)作用域內(nèi)對(duì)象進(jìn)行一次retainrelease操作缺虐。
  1. MRC模式下芜壁,必須遵守:誰(shuí)創(chuàng)建誰(shuí)釋放高氮,誰(shuí)引用慧妄,誰(shuí)管理

ARC(自動(dòng)管理引用計(jì)數(shù))

  1. ARCWWDC2011上公布,iOS5系統(tǒng)引入的自動(dòng)管理機(jī)制剪芍,是LLVMRuntime配合的結(jié)果塞淹,在編譯期運(yùn)行時(shí)都會(huì)進(jìn)行內(nèi)存管理
  2. ARC禁止手動(dòng)調(diào)用retain罪裹、release饱普、retainCountdealloc状共,轉(zhuǎn)而使用weak套耕、strong屬性關(guān)鍵字。
  • 現(xiàn)在都是直接使用ARC峡继,由系統(tǒng)自動(dòng)管理引用計(jì)數(shù)了冯袍。

2. strong & weak

  • 關(guān)于strongweak,可以在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表的釋放
    weak流程.png
  • 弱引用表存儲(chǔ)細(xì)節(jié)
  1. weak使用weakTable弱引用表進(jìn)行存儲(chǔ)信息裤翩,是sideTable散列表(哈希表)結(jié)構(gòu)资盅。
  2. 創(chuàng)建weak_entry_t,將referent引用計(jì)數(shù)加入到weak_entry_t的數(shù)組inline_referrers中踊赠。
  3. 支持weak_table擴(kuò)容呵扛,把new_entry加入到weak_table

2.2 strong

  • strong修飾,實(shí)際是新值retain舊值release:
    image.png

總結(jié):

  1. weak處理引用計(jì)數(shù)筐带,使用弱引用表進(jìn)行信息存儲(chǔ)今穿,dealloc時(shí)移除記錄
  2. strong: 內(nèi)部使用retainrelease進(jìn)行引用計(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è)問題:

  1. 為什么?(timer加入后妓雾,控制器無法釋放)
  2. 如何解決?

3.1 強(qiáng)持有

拓展:

  • 無法釋放,一般是循環(huán)引用導(dǎo)致(可參考 ?? 循環(huán)引用)
    (注意: self作為參數(shù)傳入垒迂,不會(huì)被【自動(dòng)持有】君珠,除非內(nèi)部手動(dòng)強(qiáng)引用self)
  • NSTimertimerWithTimeInterval:target:selector:userInfo:repeats:方法娇斑,就手動(dòng)強(qiáng)引用self
    image.png
  • 一般來說策添,循環(huán)引用可以通過加入弱引用對(duì)象,打斷循環(huán):self -> timer -> 加入weakself -> self

  • 對(duì)毫缆,原理沒錯(cuò)唯竹。但前提是: timer?僅被self持有,且timer僅拷貝weakself指針苦丁!

  • 很不巧:

  1. 當(dāng)前timer除了被self持有浸颓,還被加入了[NSRunLoop currentRunLoop]
  2. 當(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
image.png
方法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
image.png
  • 虛基類代理模式使用非常方便使用場(chǎng)景也很掠哥。(注意proxy中是weak弱引用object

  • 下一節(jié)巩踏,介紹自動(dòng)釋放池runloop

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市续搀,隨后出現(xiàn)的幾起案子塞琼,更是在濱河造成了極大的恐慌,老刑警劉巖禁舷,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彪杉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡牵咙,警方通過查閱死者的電腦和手機(jī)派近,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洁桌,“玉大人渴丸,你說我怎么就攤上這事×砹瑁” “怎么了谱轨?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吠谢。 經(jīng)常有香客問我土童,道長(zhǎng),這世上最難降的妖魔是什么工坊? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任娜扇,我火速辦了婚禮,結(jié)果婚禮上栅组,老公的妹妹穿的比我還像新娘雀瓢。我一直安慰自己,他們只是感情好玉掸,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布刃麸。 她就那樣靜靜地躺著,像睡著了一般司浪。 火紅的嫁衣襯著肌膚如雪泊业。 梳的紋絲不亂的頭發(fā)上把沼,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音吁伺,去河邊找鬼饮睬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篮奄,可吹牛的內(nèi)容都是我干的捆愁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼窟却,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼昼丑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夸赫,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤菩帝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茬腿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呼奢,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年切平,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了控妻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揭绑,死狀恐怖弓候,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情他匪,我是刑警寧澤菇存,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站邦蜜,受9級(jí)特大地震影響依鸥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悼沈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一贱迟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧絮供,春花似錦衣吠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春忧换,著一層夾襖步出監(jiān)牢的瞬間恬惯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工亚茬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酪耳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓刹缝,卻偏偏與公主長(zhǎng)得像碗暗,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赞草,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348