iOS中內(nèi)存管理漫談

內(nèi)存管理凝危!MRC說一堆blabla(當(dāng)然啦些己,各種release的痛想必記憶猶新)亥啦,ARC說一堆blabla...

多么老生常談的問題躯砰,想必每個(gè)開發(fā)者在起初不止一次的被問起這個(gè)問題枝恋,但是一旦被問起创倔,一般大家都會(huì)說起'黃金法則’,然后@property鼓择,blabla一大堆…

大家都知道iOS的內(nèi)存管理是通過引用計(jì)數(shù)來管理的,當(dāng)引用計(jì)數(shù)為0的時(shí)候三幻,對(duì)象會(huì)自動(dòng)釋放掉,在ARC環(huán)境下系統(tǒng)會(huì)“自動(dòng)管理”對(duì)象的引用計(jì)數(shù)呐能,然而事實(shí)上并非如此念搬,我們常常遇到試圖控制不能正常釋放問題,block使用的時(shí)候內(nèi)存泄露.

好吧摆出,多么痛才能領(lǐng)悟朗徊,今天咱們就聊聊這個(gè)蛋疼的問題,當(dāng)然啦是在ARC環(huán)境下偎漫,分別從這么幾個(gè)地方聊聊

  • @property — 幾個(gè)內(nèi)存管理相關(guān)的修飾符
  • self — 大家有沒有想過爷恳,selfARC下到底什么類型的,strong象踊?weak温亲?
  • block — 這玩兒大家每天都在寫,但是說起來他的內(nèi)存管理方式杯矩,不知道有多少能說全面的

Property

Property想必大家都寫過栈虚,當(dāng)然不排除還有很多一直在使用_ivar的,當(dāng)然啦他們之間的區(qū)別以及該用那個(gè)好咱們今天不聊史隆,如有想了解的同學(xué)請移步這里.屬性的內(nèi)存管理語義有 以下幾個(gè):

assign:適用于基本數(shù)據(jù)類型魂务,其set方法只會(huì)進(jìn)行簡單的賦值操作NSInteger,CGFloat...

/** 基本數(shù)據(jù)類型*/
@property (assign, nonatomic) NSInteger age;

strong:強(qiáng)引用類型,被定義了該類型的屬性會(huì)被其所指對(duì)象持有,為這種屬性設(shè)置新值的時(shí)候,set方法先保留新值,并釋放掉舊值,然后再把新值賦值上去

/** 適用于對(duì)象類型粘姜,并且被持有*/
@property (strong, nonatomic) UIView *view;

weak:愛恨交織的weak!弱引用類型鬓照,被定義了該類型的屬性不會(huì)被持有,在set的時(shí)候,既不保留新值也不會(huì)釋放舊值孤紧,和assign類似豺裆,不同的是在該屬性所指對(duì)象被銷毀之后,屬性值也會(huì)被置為nil

/** 弱引用類型,對(duì)象釋放后置為nil*/
@property (weak, nonatomic) id<SSDelegate> delegate;

unsafe_unretained:這個(gè)跟assign就更像了,但不同的是它適用于對(duì)象類型.同樣的不會(huì)被持有,但與weak不同是,所指對(duì)象被銷毀之后不會(huì)把屬性置為nil

/** 弱引用類型,對(duì)象釋放后不會(huì)置為nil*/
@property (unsafe_unretained, nonatomic) UIView *view;

copy:它與所指對(duì)象是持有關(guān)系,與strong類似,然而set方法并不會(huì)保留新值,而是將其拷貝一份.所以,當(dāng)你聲明一個(gè)非可變集合類型(collection type)的時(shí)候應(yīng)該使用這個(gè)修飾符,不單單是NSString,同樣的NSArray,NSDictionary,NSSet也應(yīng)當(dāng)使用它來修飾,copy可以保護(hù)非可變集合類型(collection type)的封裝性,因?yàn)樵谄鋫鬟f過程中有可能會(huì)指向一個(gè)可變類型(mutable type),此時(shí)如果不是copy坛芽,那么設(shè)置完屬性之后,其值可能會(huì)在對(duì)象不知情的情況下被更改了.所以使用copy修飾符,保證set的時(shí)候copy一份不可變(immutable)類型的值,確保前后的一致性.

/** 適用于集合類型(collection type)*/
@property (copy, nonatomic) NSString *name;

Self

看到這個(gè)self估計(jì)很多人應(yīng)該開始吐槽了,這SB,self有啥內(nèi)存管理可言留储?需要管理么?

哥負(fù)責(zé)任的告訴你,需要咙轩!需要!需要R跤薄;詈啊!

行量愧,你說需要就需要吧钾菊,哪里需要?Show me the code!!!

也許你可能會(huì)看到過這樣代碼偎肃,然后一頭霧水的就略過了...

#import "SSYNetwork.h"

- (void)startRequestData {
    SSYNetwork *strongSelf = self;
    [strongSelf.delegate finishedRequestData:strongSelf];
    if (strongSelf.successCompletionBlock) {
        strongSelf.successCompletionBlock(strongSelf);
    }
    [strongSelf clearCompletionBlock];
} 

我頭一次看到的時(shí)候也是一頭霧水煞烫,這是干嘛呢?寫得看起來很高大.仔細(xì)分析后就能夠發(fā)現(xiàn)累颂,這段代碼之所以增加一行strongSelf,是為了防止提前釋放導(dǎo)致crash.

這是一個(gè)請求網(wǎng)絡(luò)數(shù)據(jù)的start方法滞详,然而卻在里面調(diào)用了finish方法,其實(shí)還沒有等到start方法執(zhí)行結(jié)束并返回的時(shí)候紊馏,self已經(jīng)被finishedRequestData:方法釋放,造成crash料饥,簡單的來說大致是這樣的:

- (void)clickAvatar {
    // self持有delegate
    [self.delegate clickAvatar]; // clickAvatar這個(gè)代理方法釋放了self
    // 這個(gè)時(shí)候self成了野指針,然后就Boom??
}

那么現(xiàn)在我們可以考慮一下了朱监,在ARC里面self到底是個(gè)什么狀態(tài)?為何會(huì)這樣岸啡?

其實(shí)ARC下,self既不是strong赫编,也不是weak巡蘸,而是被我們忽略的unsafe_unretained

在我們調(diào)用方法的時(shí)候,ARC不會(huì)對(duì)selfretainrelease,self的生命周期由他的調(diào)用方法來決定

所以self還是需要做一定的內(nèi)存管理擂送,不然一不小心就會(huì)??(Boom)

Block

block其實(shí)是我們幾乎每天都在用的東西悦荒,然而又讓人討厭的東西,之所以討厭团甲,是因?yàn)闆]有做好內(nèi)存管理逾冬,產(chǎn)生循環(huán)引用導(dǎo)致內(nèi)存泄露甚至是crash,下面咱們就嘮嘮這個(gè)煩人的玩意兒

聲明方式

// 局部變量
returnType (^blockName)(parameterTypes) = ^returnType(parameters){...}

// 屬性,作為屬性的時(shí)候其修飾符一定是copy
@property (nonatomic, copy) returnType (^blockName)(parameters);

// 作為參數(shù)
- (void)someMethodThatTakesABlock:(returnType (^)(parameters))blockName;

// 方法調(diào)用是作為參數(shù)
[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

// 宏定義
typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

循環(huán)引用

在使用block的時(shí)候產(chǎn)生了循環(huán)引用是最讓人頭疼的,那么為什么會(huì)產(chǎn)生循環(huán)引用呢?先看看產(chǎn)生循環(huán)引用的代碼長什么樣身腻,再分析分析why?

// 兩個(gè)對(duì)象間相互引用
@property (nonatomic, copy) void(^block)(void);
self.block = ^{
    [self doSomething];
};

// 多個(gè)對(duì)象間相互引用,多對(duì)象相互引用更難以發(fā)現(xiàn)
ClassA* objA = [[ClassA alloc] init];
self.block = ^{
    [self doSomething];
};
self.objA = objA;

上面的這段代碼中selfblock之間相互持有,在block里面調(diào)用的doSomething方法,使得self引用計(jì)數(shù)+1,這個(gè)過程中self是不會(huì)被釋放,這就導(dǎo)致了循環(huán)引用.OK产还,那么問題來了,如何解決呢嘀趟?

其實(shí)脐区,解決這個(gè)問題就在于如何讓block不持有self,要解決這個(gè)問題她按,只需要使用__weak聲明一個(gè)弱引用(weakSelf)就可以了,代碼如下:

@property (nonatomic, copy) void(^block)(void);

// 弱引用
__weak typeof(self) weakSelf = self; 

self.block = ^{
    [weakSelf doSomething];
};

搞定牛隅,這樣之后我們再調(diào)用dealloc方法,打上斷點(diǎn),就可以看出self已經(jīng)被釋放了

Wow!??????????

不要天真的以為這樣就萬事大吉,就可以丟下鼠標(biāo)等勝利了!

如果我說,有時(shí)候即使你使用了__weak做了弱引用,也還是有crash的危險(xiǎn)!

咱不逗行么.png

請看下面的代碼:

// 弱引用
__weak typeof(self) weakSelf = self; 

// 異步線程,這里的block是作為一個(gè)參數(shù)的
dispatch_async(dispatch_get_main_queue(), ^{
    weakSelf.xx = xx;
});

讓我們分析一下原因,將block作為參數(shù)傳遞給dispatch_async時(shí),系統(tǒng)會(huì)將block拷貝到堆(heap)上,如果block中使用了self持有的對(duì)象,就會(huì)retain self,因?yàn)?code>dispatch_async并不知道self會(huì)在什么時(shí)候被釋放,所以為了確保系統(tǒng)調(diào)度執(zhí)行block中的任務(wù)時(shí)self沒有被意外釋放掉,dispatch_async必須自己retain一次self,任務(wù)完成后在release self.但是這里使用了__weak,使得dispatch_async沒有增加self的引用計(jì)數(shù),這就導(dǎo)致了在調(diào)度執(zhí)行block之前,self可能已經(jīng)被釋放掉了,然后就crash??,控制臺(tái)提示BAD_ACCESS

好吧酌泰,算你對(duì)了媒佣,那你說該咋辦?

嘿嘿陵刹,既然沒有retain默伍,那就想辦法retain唄?代碼如下:

開啟裝逼模式.jpg
// 弱引用
__weak typeof(self) weakSelf = self; 

// 異步線程,這里的block是作為一個(gè)參數(shù)的
dispatch_async(dispatch_get_main_queue(), ^{
    // 加強(qiáng)引用防止提前釋放,并且做判斷
    __strong typeof(weakSelf) strongSelf = self; 
    if (strongSelf) {
        strongSelf.xx = xx;
    }
});

然而這樣寫就好了么衰琐?還有沒有更好的方法也糊?程序員都是有追求的,我們是一群高逼格的從業(yè)者羡宙!

再裝逼試試.jpg

好吧狸剃,我只是想說還有更優(yōu)雅的方式來寫下面兩句話

__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
別激動(dòng),開個(gè)小玩笑.jpg

有使用過ReactiveCocoa的同學(xué)應(yīng)該知道,其實(shí)我們可以使用影子變量@weakify/@strongify這種方式同樣能解決,至于關(guān)于影子變量的詳細(xì)介紹這里就不多說了,有興趣的同學(xué)可以到這里看看,使用影子變量后,代碼就會(huì)像下面這樣:

@weakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
    @strongify(self);
    if (!self) return;
    self.xx = xx; // 這里就可以放心的使用self,簡單粗暴
});

OK,裝逼結(jié)束9啡取3佟!
如有錯(cuò)誤之處斗搞,歡迎討論.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末指攒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子僻焚,更是在濱河造成了極大的恐慌允悦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虑啤,死亡現(xiàn)場離奇詭異隙弛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)狞山,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門全闷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萍启,你說我怎么就攤上這事总珠∑流ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵局服,是天一觀的道長钓瞭。 經(jīng)常有香客問我,道長淫奔,這世上最難降的妖魔是什么山涡? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮唆迁,結(jié)果婚禮上鸭丛,老公的妹妹穿的比我還像新娘。我一直安慰自己唐责,他們只是感情好鳞溉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鼠哥,像睡著了一般穿挨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肴盏,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音帽衙,去河邊找鬼菜皂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厉萝,可吹牛的內(nèi)容都是我干的恍飘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谴垫,長吁一口氣:“原來是場噩夢啊……” “哼章母!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起翩剪,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤乳怎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后前弯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚪缀,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年恕出,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了询枚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浙巫,死狀恐怖金蜀,靈堂內(nèi)的尸體忽然破棺而出刷后,到底是詐尸還是另有隱情,我是刑警寧澤渊抄,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布尝胆,位于F島的核電站,受9級(jí)特大地震影響抒线,放射性物質(zhì)發(fā)生泄漏班巩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一嘶炭、第九天 我趴在偏房一處隱蔽的房頂上張望抱慌。 院中可真熱鬧,春花似錦眨猎、人聲如沸抑进。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寺渗。三九已至,卻和暖如春兰迫,著一層夾襖步出監(jiān)牢的瞬間信殊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工汁果, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涡拘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓据德,卻偏偏與公主長得像鳄乏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棘利,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容