深入總結(jié)iOS內(nèi)存管理

文章也同時(shí)在個(gè)人博客 http://kimihe.com/更新

引言

本文主要針對(duì)iOS內(nèi)存管理進(jìn)行總結(jié),相信看過(guò)之后一定會(huì)讓你有所收獲。對(duì)于內(nèi)存管理,網(wǎng)上有很多這方面的文章,但論質(zhì)量可謂良莠不齊。其中的很多總結(jié)已經(jīng)過(guò)時(shí),甚至有些內(nèi)容是似懂非懂的誤讀。因此,本文立足嚴(yán)謹(jǐn)?shù)闹螌W(xué)態(tài)度,希望能夠盡可能全面地讓大家理解內(nèi)存管理的要點(diǎn)。

本文涵蓋的知識(shí)點(diǎn)比較多,大家可以根據(jù)需要擇章節(jié)閱讀(部分章節(jié)待更),下面是目錄:

  • 基礎(chǔ)知識(shí)
  • 引用計(jì)數(shù)
  • ARC
  • delloc方法
  • 循環(huán)引用
  • 自動(dòng)釋放池@autoreleasepool
  • 關(guān)于retainCount

基礎(chǔ)知識(shí)

在理解內(nèi)存管理之前,你需要理解內(nèi)存的到底如何被我們的操作系統(tǒng)使用的,需要有一個(gè)清晰的內(nèi)存模型。

對(duì)于內(nèi)存的使用模型,大家應(yīng)該不陌生堆棧這次詞。但具體的內(nèi)部原理其實(shí)還是比較復(fù)雜的。大家谷歌或者百度一下,可以搜到如下這張內(nèi)存堆棧圖:

內(nèi)存堆棧圖

這張圖的信息量還是比較大的,請(qǐng)大家耐心閱讀筆者的講解。

首先常說(shuō)的堆棧一詞包含了“堆”和“棧”兩個(gè)概念。它們是有區(qū)別的,而且和數(shù)據(jù)結(jié)構(gòu)中的堆棧是不同的。“堆”和“棧”分別代表了兩種數(shù)據(jù)管理方式,前者有程序員自己控制,后者由操作系統(tǒng)(由編譯器在編譯時(shí)確定套路)控制。這句話是網(wǎng)上很多文章常說(shuō)的,但可能并沒(méi)有說(shuō)明白其中的本質(zhì)。如果你想更加清楚地了解堆棧在內(nèi)存中的底層細(xì)節(jié),可以參考一下筆者的這篇文章《內(nèi)存中的堆和棧到底是什么》

在明白了上述細(xì)節(jié)后,大家請(qǐng)看如下代碼:

NSString *someString = @"Some String";
NSString *anotherString = someString;

對(duì)于上述代碼,我們表明上看起來(lái)似乎創(chuàng)建了兩個(gè)NSString對(duì)象,其實(shí)它們二者是復(fù)用的關(guān)系。要理解這一點(diǎn),你可能需要明白什么是指針。可以看筆者的這篇文章《快速入門Linux下GDB和匯編開(kāi)發(fā)工具》中介紹指針原理的部分。

如果你清楚指針的含義,請(qǐng)繼續(xù)往后看。實(shí)際上,上述代碼只創(chuàng)建了一個(gè)NSString對(duì)象,它分配在堆中,需要進(jìn)行內(nèi)存管理。注意,我們并不是說(shuō)這個(gè)字符串常量在堆上,而是創(chuàng)建一個(gè)OC對(duì)象需要的內(nèi)存開(kāi)銷是在堆上,普通的字符串常量存在數(shù)據(jù)段的.rodata區(qū)。筆者強(qiáng)烈建議先閱讀一下介紹內(nèi)存堆棧的那篇文章。

someStringanotherString是兩個(gè)指針(請(qǐng)注意它們C語(yǔ)言風(fēng)格的*號(hào)),它們存在于棧中,跟隨棧的管理由系統(tǒng)控制,我們?nèi)斯o(wú)需也最好不要去控制。具體看來(lái),我們?cè)诋?dāng)前代碼區(qū)域的棧中分配了兩個(gè)內(nèi)存,但這兩個(gè)內(nèi)存只是用來(lái)存放指針,與堆中用來(lái)存儲(chǔ)字符串實(shí)例是完全不同的。這兩個(gè)內(nèi)存的大小剛好能夠分別存下一個(gè)指針(在32位CPU上是4字節(jié),在64位上是8字節(jié)),而且兩個(gè)內(nèi)存中的值是一樣的,即兩個(gè)指針的值是一樣的,于是兩個(gè)指針指向同一個(gè)堆內(nèi)存區(qū)域,從而導(dǎo)致someStringanotherString這兩個(gè)指針指向的是相同的字符串實(shí)例。如下圖:

NSString堆棧示例圖

總結(jié)一下:someStringanotherString分配在棧上,無(wú)需我們控制。NSString: @"Some String"分配在堆上,需要我們進(jìn)行內(nèi)存管理。而這里的內(nèi)存管理你需要理解引用計(jì)數(shù),請(qǐng)看下一小節(jié)。

除此之外,OC雖然是一門面向?qū)ο笳Z(yǔ)言,但本質(zhì)上它是一個(gè)C的超級(jí)。很多理念和C是通用的,如果你熟悉C,學(xué)習(xí)OC將很快。注意到上述指針的*號(hào),OC中的對(duì)象都是以指針來(lái)引用的,因此對(duì)象類型的實(shí)例的名字前都會(huì)帶有一個(gè)*號(hào)。

對(duì)于普通的非對(duì)象類型的變量,比如CGRect結(jié)構(gòu),它們也會(huì)使用棧空間,而非對(duì)象類型所用的堆空間。因此,相比于創(chuàng)建結(jié)構(gòu)體創(chuàng)建對(duì)象需要分配和釋放內(nèi)存等額外開(kāi)銷,所以若需要存儲(chǔ)int,float,double,char,bool等非對(duì)象類型,建議直接使用結(jié)構(gòu)體。

建議花一點(diǎn)時(shí)間學(xué)習(xí)一下C,這將有助于OC的學(xué)習(xí)。

其余章節(jié)待更~

自動(dòng)釋放池@autoreleasepool

OC對(duì)象的生命周期取決于引用計(jì)數(shù),我們有兩種方式可以釋放對(duì)象:一種是直接調(diào)用release釋放;另一種是調(diào)用autorelease將對(duì)象加入自動(dòng)釋放池中。自動(dòng)釋放池用于存放那些需要在稍后某個(gè)時(shí)刻釋放的對(duì)象。

自動(dòng)釋放池的創(chuàng)建

如果沒(méi)有自動(dòng)釋放池而給對(duì)象發(fā)送autorelease消息,將會(huì)收到控制臺(tái)報(bào)錯(cuò)。但一般我們無(wú)需擔(dān)心自動(dòng)釋放池的創(chuàng)建問(wèn)題。

我們的Mac以及iOS系統(tǒng)會(huì)自動(dòng)創(chuàng)建一些線程,例如主線程和GCD中的線程,都默認(rèn)擁有自動(dòng)釋放池。每次執(zhí)行 “事件循環(huán)”(event loop)時(shí),就會(huì)將其清空,這一點(diǎn)非常重要,請(qǐng)務(wù)必牢記! 關(guān)于事件循環(huán),其涉及到runloop,可以看這篇文章:深入理解RunLoop

因此我們一般不需要手動(dòng)創(chuàng)建自動(dòng)釋放池,通常只有一個(gè)地方需要它,那就是在main()函數(shù)里,如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

這個(gè)main()函數(shù)里面的池并非必需。因?yàn)閴K的末尾是應(yīng)用程序的終止處,即便沒(méi)有這個(gè)自動(dòng)釋放池,也會(huì)由操作系統(tǒng)來(lái)釋放。但是這些由UIApplicationMain函數(shù)所自動(dòng)釋放的對(duì)象就沒(méi)有池可以容納了,系統(tǒng)會(huì)發(fā)出警告。因此,這里的池可以理解成最外圍捕捉全部自動(dòng)釋放對(duì)象所用的池。

@autoreleasepool的作用

大家可以先看一下下面的iOS筆試題的第5題(修改代碼的錯(cuò)誤),如下圖:


優(yōu)酷iOS筆試題

這段代碼問(wèn)題在哪里呢?題目的解答請(qǐng)繼續(xù)閱讀。筆者先給一個(gè)提示:與內(nèi)存的釋放有關(guān)。

現(xiàn)考慮如下代碼:

for (int i = 0; i < 10000; i++) {
    [self doSthWith:object];
}

這段代碼和筆試題關(guān)鍵部分大同小異。如果"doSthWith:"方法要?jiǎng)?chuàng)建一個(gè)臨時(shí)對(duì)象,那么這個(gè)對(duì)象很可能會(huì)放在自動(dòng)釋放池里。筆試題中最后stringByAppendingString方法很有可能屬于上述的方法。因此如果涉及到了自動(dòng)釋放池,那么問(wèn)題也應(yīng)該就出在上面。

注意:即便臨時(shí)對(duì)象在調(diào)用完方法后就不再使用了,它們也依然處于存活狀態(tài),因?yàn)槟壳八鼈兌荚谧詣?dòng)釋放池里,等待系統(tǒng)稍后進(jìn)行回收。但自動(dòng)釋放池卻要等到該線程執(zhí)行下一次事件循環(huán)時(shí)才會(huì)清空,這就意味著在執(zhí)行for循環(huán)時(shí),會(huì)有持續(xù)不斷的新的臨時(shí)對(duì)象被創(chuàng)建出來(lái),并加入自動(dòng)釋放池。要等到結(jié)束for循環(huán)才會(huì)釋放。在for循環(huán)中內(nèi)存用量會(huì)持續(xù)上漲,而等到結(jié)束循環(huán)后,內(nèi)存用量又會(huì)突然下降。

而如果把循環(huán)內(nèi)的代碼包裹在“自動(dòng)釋放池”中,那么在循環(huán)中自動(dòng)釋放的對(duì)象就會(huì)放在這個(gè)池,而不是在線程的主池里面。如下:

for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSString *str = @"abc";
            str = [str lowercaseString];
            str = [str stringByAppendingString:@"xyz"];
        }        
}

新增的自動(dòng)釋放池可以減少內(nèi)存用量,因?yàn)橄到y(tǒng)會(huì)在塊的末尾把這些對(duì)象回收掉。而上述這些臨時(shí)對(duì)象,正在回收之列。

自動(dòng)釋放池的機(jī)制就像“棧”。系統(tǒng)創(chuàng)建好池之后,將其壓入棧中,而清空自動(dòng)釋放池相當(dāng)于將池從棧中彈出。在對(duì)象上執(zhí)行自動(dòng)釋放操作,就等于將其放入位于棧頂?shù)哪莻€(gè)池。

實(shí)驗(yàn)驗(yàn)證

我們可以通過(guò)實(shí)驗(yàn)進(jìn)行驗(yàn)證。新建工程加入上述代碼,并關(guān)閉ARC(不然是看不到區(qū)別的)。

在未添加autoreleasepool時(shí),我們的堆內(nèi)存實(shí)時(shí)分配情況如下圖:

HeapWithoutAutoreleasepool

大家可以看到Persistent Bytes不斷增加,到達(dá)100W次的創(chuàng)建峰值后(出for循環(huán))開(kāi)始逐步釋放。因此圖像是一個(gè)向上凸的曲線。

在加入autoreleasepool后,我們看到如下的曲線:

HeapWithAutoreleasepool

可以發(fā)現(xiàn)盡管字符串在不斷地創(chuàng)建,但由于得到了及時(shí)的釋放,堆內(nèi)存始終保持在一個(gè)很低的水平。

其他注意點(diǎn)

@autoreleasepool語(yǔ)法還有一個(gè)好處,就是可以避免無(wú)意間誤用那些在清空池之后已被系統(tǒng)回收的對(duì)象,例如:

@autoreleasepool {
    id obj = [self createObject];
}
[self useObject:obj];

上述代碼在編譯時(shí)就會(huì)基于錯(cuò)誤警告,因?yàn)閛bj出了自動(dòng)釋放池就不可用了。

@autoreleasepool小結(jié)

  • 自動(dòng)釋放池排布在棧中,對(duì)象受到autorelease消息后,系統(tǒng)將其放入棧頂?shù)某乩铩?/li>
  • 合理運(yùn)用自動(dòng)釋放池,可以降低程序的內(nèi)存峰值。

關(guān)于retainCount

大家在搜索引擎中輸入“retainCount”,可以看到非常多的文章講述retainCount的作用。

大多數(shù)文章都在強(qiáng)調(diào)這個(gè)retainCount是用來(lái)標(biāo)記對(duì)象,進(jìn)行內(nèi)存管理的。所謂“reatin +1;release -1;到0釋放;打印retainCount查看當(dāng)前計(jì)數(shù)”等等。但其實(shí)retainCount并不應(yīng)該像上述那樣使用,OC的引用計(jì)數(shù)也不是那樣理解的。

誤區(qū)一

retainCount似乎可以返回某對(duì)象的保留計(jì)數(shù),其實(shí)即便在MRC下,retainCount在實(shí)際編程過(guò)程中也并沒(méi)有什么用。首要原因在于:它返回的保留計(jì)數(shù)只是某個(gè)時(shí)間點(diǎn)上的值。該方法并未考慮到系統(tǒng)會(huì)稍后把自動(dòng)釋放池清空,因而沒(méi)有將后續(xù)的釋放操作從返回值里減去。

如果你想根據(jù)這個(gè)數(shù)值來(lái)進(jìn)行某些釋放操作,那就很糟糕了。例如:

while ([object retainCount]) {
    [object release];
}

其中存在兩個(gè)錯(cuò)誤,第一個(gè)就是沒(méi)有考慮到自動(dòng)釋放操作,只是不停地release,直到對(duì)象被系統(tǒng)回收。假如此對(duì)象在自動(dòng)釋放池里,那么稍后系統(tǒng)在清理空池子時(shí)還要仔把它釋放一次,這將導(dǎo)致程序崩潰。

第二個(gè)錯(cuò)誤就是:retainCount可能永遠(yuǎn)也不會(huì)減少到0。因?yàn)橛袝r(shí)候系統(tǒng)會(huì)優(yōu)化對(duì)象的釋放行為,在保留計(jì)數(shù)還是1的時(shí)候就把它回收了。只有在系統(tǒng)不打算這么優(yōu)化時(shí),計(jì)數(shù)值才會(huì)遞減址0。所以靠上述條件進(jìn)行判斷,完全是在賭運(yùn)氣。

我們真正應(yīng)該做的是確保release和retain操作的匹配(即可以相互抵消),我們?cè)谄谕到y(tǒng)回收某對(duì)象時(shí),應(yīng)該確保沒(méi)有尚未抵消的保留操作,也就是不要讓保留計(jì)數(shù)大于我們的期望值。如果發(fā)現(xiàn)內(nèi)存泄漏了,那么應(yīng)該檢查還有社仍然保留這個(gè)對(duì)象,并查明為何沒(méi)有釋放。

雖然Apple建議大家使用ARC,實(shí)際也確實(shí)如此。但基于MRC的開(kāi)發(fā)也并非一無(wú)是處,至少它能夠讓我們更加清晰地理解內(nèi)存管理的真諦。

誤區(qū)二

如果我們打印一些字符串或者數(shù)值對(duì)象的retainCount,如下代碼:

NSString *string = @"Apple";
NSLog(@"string retainCount: %lu", (unsigned long)[string retainCount]);
    
NSNumber *numberI = @1;
NSLog(@"numberI retainCount: %lu", (unsigned long)[numberI retainCount]);
    
NSNumber *numberF = @3.14f;
NSLog(@"numberF retainCount: %lu", (unsigned long)[numberF retainCount]);

其輸出將會(huì)如下:

2016-12-04 20:04:51.007 tmp[1522:138528] string retainCount: 18446744073709551615
2016-12-04 20:04:51.008 tmp[1522:138528] numberI retainCount: 9223372036854775807
2016-12-04 20:04:51.008 tmp[1522:138528] numberF retainCount: 1

我們發(fā)現(xiàn)結(jié)果非常奇怪。怎么回事呢?

第一個(gè)string的保留計(jì)數(shù)其實(shí)為2的64次方-1,而第二個(gè)numberI是2的63次方-1。二者皆為“單例對(duì)象”(singleton object),所以保留計(jì)數(shù)都很大。

系統(tǒng)會(huì)盡可能地把NSString實(shí)現(xiàn)成單例對(duì)象。像上述代碼中的字符串,其實(shí)是一個(gè)編譯期常量,編譯器會(huì)把NSString對(duì)象所表示的數(shù)據(jù)放到應(yīng)用程序的二進(jìn)制文件里,這樣的話,運(yùn)行程序時(shí)就能直接用了。這一點(diǎn)對(duì)應(yīng)到底層的話,隸屬于程序的可執(zhí)行文件中數(shù)據(jù)段的.rodata小段的作用,如果你閱讀過(guò)開(kāi)頭筆者提到的《內(nèi)存中的堆和棧到底是什么》這篇文章,應(yīng)該可以很容易理解。

NSNumber也類似,它使用了一種叫做“標(biāo)簽指針”(tagged pointer)的概念來(lái)標(biāo)注特定類型的數(shù)值。關(guān)于tagged pointer,可以閱讀這篇唐巧的文章:深入理解Tagged Pointer

tagged pointer不使用NSNumber對(duì)象,而是把數(shù)值有關(guān)的全部消息都放在指針值里面。運(yùn)行期系統(tǒng)會(huì)在消息派發(fā)期間檢測(cè)到這種標(biāo)簽指針,并對(duì)它執(zhí)行相應(yīng)操作,使其行為看上去和真正的NSNumber對(duì)象一樣。這種優(yōu)化只在某些場(chǎng)合使用,比如上述代碼中的浮點(diǎn)數(shù)對(duì)象就沒(méi)有優(yōu)化,所以保留計(jì)數(shù)就是1。

上述的這種單例對(duì)象,其保留計(jì)數(shù)絕對(duì)不會(huì)變。這種對(duì)象的保留及釋放操作都是“空操作”(noop)。

retainCount小結(jié)

對(duì)象的保留計(jì)數(shù)看似有用,其實(shí)最好根本不要去使用。因?yàn)槿魏谓o定時(shí)間點(diǎn)上的“絕對(duì)保留計(jì)數(shù)”(absolute retain count)都無(wú)法反應(yīng)對(duì)象生命期的全貌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

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

  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制。與retain配對(duì)使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 1,989評(píng)論 1 16
  • 29.理解引用計(jì)數(shù) Objective-C語(yǔ)言使用引用計(jì)數(shù)來(lái)管理內(nèi)存,也就是說(shuō),每個(gè)對(duì)象都有個(gè)可以遞增或遞減的計(jì)數(shù)...
    Code_Ninja閱讀 1,521評(píng)論 1 3
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,198評(píng)論 30 471
  • 11.看下面的程序,第一個(gè)NSLog會(huì)輸出什么?這時(shí)str的retainCount是多少?第二個(gè)和第三個(gè)呢? 為什...
    AlanGe閱讀 746評(píng)論 1 4
  • 多線程、特別是NSOperation 和 GCD 的內(nèi)部原理。運(yùn)行時(shí)機(jī)制的原理和運(yùn)用場(chǎng)景。SDWebImage的原...
    LZM輪回閱讀 2,025評(píng)論 0 12