快到年底了 無(wú)心工作脉执,哎
1.講了dealloc方法的使用。平時(shí)的使用過(guò)程中要注意下面幾點(diǎn)內(nèi)容:
- 在dealloc方法中命辖,需要做的僅僅是釋放指向其他對(duì)象的引用葵萎,并且取消原來(lái)的訂閱(KVO)或者是NSNotificationCenter等通知,其他的事情不要做塞椎。
- 不論是執(zhí)行異步任務(wù)還是執(zhí)行同步任務(wù)桨仿,都不要再dealloc方法中調(diào)用,因?yàn)榇藭r(shí)對(duì)象已經(jīng)處在即將被回收的狀態(tài)了
2.許多時(shí)下流行的語(yǔ)言都提供了“異嘲负荩”(exception)這一特性服傍。純C中沒(méi)有異常,而C++與OC都支持異常骂铁。實(shí)際上吹零,C++和OC的異常相互兼容,也就是說(shuō)从铲,從其中一門(mén)語(yǔ)言里拋出的異常能用另外一門(mén)語(yǔ)言來(lái)捕獲瘪校。
發(fā)生異常時(shí)應(yīng)該如何管理內(nèi)存是個(gè)值得研究的問(wèn)題∶危看下面的代碼
@try{
EOCSomeClass *object = [[EOCSomeClass alloc]init];
[object doSomethingThatMayThrow];
}
@catch(...)
{
NSLog(@"Whoops there was an error.oh well");
}
如果doSomethingThatMayThrow拋出異常,由于異常會(huì)跳至catch塊泣懊,因?yàn)槟切衦elease代碼不會(huì)運(yùn)行伸辟。在這種情況下,如果代碼拋出異常馍刮,那么對(duì)象就泄露了信夫。解決辦法是使用@finally塊,無(wú)論是否拋出異常卡啰,其中的代碼都保證會(huì)運(yùn)行静稻,且只運(yùn)行一次,如下所示:
@try{
EOCSomeClass *object = [[EOCSomeClass alloc]init];
[object doSomethingThatMayThrow];
[object release];
}
@catch(...)
{
NSLog(@"Whoops there was an error.oh well");
}
@finally{
[object release];
}
很遺憾匈辱,在ARC下振湾,由于不是手動(dòng)release,所以上面的情況ARC不會(huì)自動(dòng)處理亡脸。因?yàn)檫@樣做要插入大量樣板代碼押搪。
● 捕獲異常時(shí),一定要注意將try塊內(nèi)所創(chuàng)立的對(duì)象清理干凈
●默認(rèn)情況下ARC不會(huì)生成安全處理異常所需要的清理代碼浅碾。開(kāi)啟編譯選項(xiàng)后大州,可以插入代碼,不過(guò)會(huì)導(dǎo)致應(yīng)用程序過(guò)大垂谢,而且會(huì)降低效率厦画。
3.最簡(jiǎn)單的循環(huán)引用由兩個(gè)對(duì)象構(gòu)成,他們相互引用對(duì)方滥朱。如圖所示:
這樣的引用根暑,導(dǎo)致誰(shuí)也不會(huì)被釋放掉力试。還有多個(gè)對(duì)象的循環(huán)應(yīng)用,如圖:
避免循環(huán)引用的最佳方式就是弱引用购裙。這種引用經(jīng)常用來(lái)表示“非擁有關(guān)系”在MRC下使用unsafe_unratain和ARC下的weak懂版。
上圖中的虛線就是weak,
● 將某些引用設(shè)為weak躏率,可避免出現(xiàn)“循環(huán)引用”躯畴。
● weak引用可以自動(dòng)清空,也可以不清空薇芝。自動(dòng)情況(autonilling)是隨著ARC而引入的新特性蓬抄,由運(yùn)行期系統(tǒng)來(lái)實(shí)現(xiàn)。在具備自動(dòng)清空的弱引用上夯到,可以隨意讀取其數(shù)據(jù)嚷缭,因?yàn)檫@種引用不會(huì)指向已經(jīng)回收過(guò)的對(duì)象
4.考慮下面代碼:
for(int i=0;i<100000;i++){
[self doSomethingWithInt:i];
}
如果“doSomethingWithInt”方法要?jiǎng)?chuàng)建臨時(shí)對(duì)象,那么這些對(duì)象很可能會(huì)被放到自動(dòng)釋放池里耍贾。這樣一來(lái)在執(zhí)行for循環(huán)時(shí)阅爽,應(yīng)用程序所占用內(nèi)存就會(huì)持續(xù)上漲,而等到臨界值對(duì)象都釋放后荐开,內(nèi)存用量又會(huì)突然下降付翁。
for(int i=0;i<100000;i++){
@autoreleasepool{
[self doSomethingWithInt:i];
}
}
加上這個(gè)自動(dòng)釋放池之后,應(yīng)用程序循環(huán)時(shí)的內(nèi)存峰值就會(huì)降低晃听。自動(dòng)釋放池就像“棸俨啵”(stack)系統(tǒng)創(chuàng)建好自動(dòng)釋放池后,將其推入棧中能扒,而清空自動(dòng)釋放池佣渴,則相當(dāng)于從棧中彈出。
“自動(dòng)釋放池”本身又有開(kāi)銷(xiāo)初斑,所以是否使用“自動(dòng)釋放池”取決于應(yīng)用程序辛润。
● 自動(dòng)釋放池排布在棧中,對(duì)象收到autorelease消息后越平,系統(tǒng)將其放入最頂端的池子里
● 合理運(yùn)用自動(dòng)釋放池频蛔,可以降低應(yīng)用程序的內(nèi)存峰值
● @autoreleasepool這種新式寫(xiě)法能創(chuàng)建出更為輕便的自動(dòng)釋放池
5.Cocoa提供了“僵尸對(duì)象”(Zombie Object)這個(gè)非常方便的功能。它的實(shí)現(xiàn)代碼深植于OC的運(yùn)行期程序庫(kù)秦叛、Foundation框架以及CoreFoundation框架中晦溪。他的原理是這樣的:系統(tǒng)在即將回收對(duì)象時(shí),如果發(fā)現(xiàn)xcode啟用了對(duì)僵尸象功能挣跋,那么還將執(zhí)行一個(gè)附加步驟:把對(duì)象轉(zhuǎn)化為僵尸對(duì)象三圆,而不徹底回收。
即便是使用了ARC,也依然會(huì)出現(xiàn)這種內(nèi)存bug
● 系統(tǒng)在回收對(duì)象時(shí)舟肉,可以不將其真的回收修噪,而是把它轉(zhuǎn)化位僵尸對(duì)象。通過(guò)環(huán)境變量NSZombieEnable可以開(kāi)啟此功能路媚。
● 系統(tǒng)會(huì)修改對(duì)象的isa指針黄琼,令其指向特殊的僵尸類(lèi),從而使該對(duì)象變?yōu)榻┦瑢?duì)象整慎。僵尸類(lèi)能夠響應(yīng)所有的選擇子脏款,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息,然后終止應(yīng)用程序裤园。
6.這里我們禁止使用retainCount撤师,無(wú)論是ARC環(huán)境還是MRC環(huán)境。
NSObject協(xié)議中定義了下列方法拧揽,用于查詢(xún)對(duì)象的保留計(jì)數(shù):
-(NSUInterger)retainCount;
然而ARC已經(jīng)將此方法廢棄了剃盾。實(shí)際上,如果在ARC中調(diào)用淤袜,編譯器就會(huì)報(bào)錯(cuò)痒谴,跟ARC中調(diào)用retain、release铡羡、autorelease的情況是一樣的闰歪。但是在非ARC下還是可以調(diào)用retainCount接口。為啥不要使用retainCount呢蓖墅?
此方法之所以無(wú)用,首要原因在于:它返回的保留計(jì)數(shù)只是某個(gè)給定時(shí)間點(diǎn)上的值临扮。該方法并未考慮到系統(tǒng)會(huì)稍后把自動(dòng)釋放池清空论矾,所以未必真實(shí)反應(yīng)實(shí)際的保留計(jì)數(shù)了。
下面的寫(xiě)法是非常糟糕的:
while([object retainCount]){
[object release];
}
第二個(gè)錯(cuò)誤在于:retainCount可能永遠(yuǎn)不返回0杆勇,因?yàn)橛袝r(shí)候系統(tǒng)會(huì)優(yōu)化對(duì)象的釋放行為贪壳。
第三種情況:看下面的代碼
NSString *string = @"Some string";
NSLog(@"string retainCount=%lu",[string retainCount]);
NSNumber *numberI = @1;
NSLog(@"numberI retainCount = %lu",[numberI retainCount]);
NSNumber *numberF = @3.141f;
NSLog(@"numberF retainCount=%lu",[number retainCount]);
在64位MAC OSX 10.8.2系統(tǒng)中,用Clang 4.1編譯后蚜退,這段代碼輸出的消息如下:
string retainCount = 18446744073709551615 //2^64-1
numberI retainCount = 923372036854775807 //2^63-1
number retainCount = 1
string是個(gè)常量闰靴,編譯器把NSString對(duì)象所表示的數(shù)據(jù)放到應(yīng)用程序的二進(jìn)制文件里,這樣運(yùn)行程序時(shí)就可以直接用了钻注,無(wú)須再創(chuàng)建NSString對(duì)象蚂且。NSNumber也類(lèi)似,它使用了一種叫做“標(biāo)簽指針”(tagged pointer)的概念來(lái)標(biāo)注特定類(lèi)型的數(shù)值幅恋。這總做法不使用NSNumber對(duì)象杏死,而是把數(shù)值有關(guān)的全部消息放到指針值里面。運(yùn)行期系統(tǒng)會(huì)在消息派發(fā)期間檢測(cè)到這種標(biāo)簽指針,并對(duì)它志向相應(yīng)操作淑翼,使其行為看上去和真正的NSNumber一樣腐巢。這種優(yōu)化在某些場(chǎng)合使用,但是浮點(diǎn)數(shù)就沒(méi)有這個(gè)優(yōu)化玄括,保留計(jì)數(shù)還是1
7.OC中多線程編程的核心就是block與gcd冯丙。這雖然是兩種不同的技術(shù),但他們是一并引入的遭京。block是一種可在C胃惜、C++及OC代碼中使用的“詞法閉包”(lexical closure),它極為有用洁墙,借此機(jī)制蛹疯,開(kāi)發(fā)者可將代碼像對(duì)象一樣傳遞,令其在不同環(huán)境(context)下運(yùn)行热监。在block的范圍內(nèi)捺弦,它可以訪問(wèn)到其中的全部變量。
gcd是一種與block有關(guān)的技術(shù)孝扛,它提供了對(duì)線程的抽象列吼,而這種抽象基于“派發(fā)隊(duì)列”(dispatch queue)。開(kāi)發(fā)者可將block排入隊(duì)列中苦始,有g(shù)cd負(fù)責(zé)處理所有調(diào)度事宜寞钥。gcd會(huì)根據(jù)系統(tǒng)資源情況,適時(shí)得創(chuàng)建陌选、復(fù)用理郑、摧毀后臺(tái)線程,以便處理每個(gè)隊(duì)列咨油。此外您炉,使用GCD還可以方便的完成常見(jiàn)編程任務(wù),比如編寫(xiě)“只執(zhí)行一次的線程安全代碼”(thread-safe single-code execution)役电,或者根據(jù)可用的系統(tǒng)資源來(lái)并發(fā)執(zhí)行多個(gè)操作赚爵。
block和gcd是當(dāng)前OC的編程基石。因此必須理解其工作原理及功能
block可以實(shí)現(xiàn)閉包法瑟。這項(xiàng)語(yǔ)言特性是作為擴(kuò)展而加入GCC編譯器中的冀膝,在近期版本的Clang中都可以使用。從技術(shù)上講霎挟,這是位于C語(yǔ)言層面的特性窝剖,因此只要有支持此特性的編譯器以及能執(zhí)行block的運(yùn)行期組件,就可以在C氓扛、C++枯芬、OC论笔,PC++代碼中使用它。
下面是block的一個(gè)基本寫(xiě)法
void (^someBlock)() = ^{
//Block implementation here
};
block的強(qiáng)大之處是:在聲明它的范圍內(nèi)千所,所有變量都可以為其所捕獲狂魔。例如下面代碼:
int additional = 5;
int(^addBlock)(int a ,int b) = ^(int a,int b){
return a+b+additional;
};
int add = addBlock(2,5);//add = 12
默認(rèn)情況下,為block捕獲的變量淫痰,是不可以修改的最楷。如果在block中需要修改需要使用__block修飾符,修飾變量待错。
NSArray *array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count =0;
[array enumerateObjectUsingBlock:^(NSNumber* number,NSUInteger idx, BOOLBOOL *stop){
if([number compare:@2]==NSOrderAscending){
count++;
}
}];
//count =2
block中直接使用count的值籽孙。如果block捕獲的變量是對(duì)象類(lèi)型,那么就會(huì)自動(dòng)保留它火俄。系統(tǒng)在釋放這個(gè)block的時(shí)候也會(huì)將其一并釋放犯建,block本身也是變量,有引用計(jì)數(shù)瓜客。
block還可以使用self變量适瓦。block總能修改實(shí)例變量,所以在聲明時(shí)無(wú)需加__block谱仪。但是self卻被保留了玻熙。如果self所指代的哪個(gè)對(duì)象同時(shí)頁(yè)保留了塊,那么這種情況就會(huì)導(dǎo)致“循環(huán)引用”疯攒。
每個(gè)OC對(duì)象都占據(jù)者某個(gè)內(nèi)存區(qū)域嗦随。block本身也是對(duì)象,在存放對(duì)象的內(nèi)存區(qū)域中敬尺,首個(gè)變量是指向Class對(duì)象的指針枚尼,該指針也叫isa。其余內(nèi)存里含有block對(duì)象正常運(yùn)轉(zhuǎn)所需的各種信息砂吞,block對(duì)象的內(nèi)部實(shí)現(xiàn)參考:點(diǎn)擊打開(kāi)鏈接 如圖:
在內(nèi)存布局中姑原,最重要的是invoke變量,這是個(gè)函數(shù)指針呜舒,指向block的實(shí)現(xiàn)代碼。descriptor變量是指向結(jié)構(gòu)體的指針笨奠,每個(gè)block都包含了此結(jié)構(gòu)體袭蝗,其中聲明了block對(duì)象的總體大小,還聲明了copy與dispose兩個(gè)輔助番薯所對(duì)應(yīng)的函數(shù)指針般婆。前者是保留捕獲的對(duì)象到腥,后者則將之釋放。
block還會(huì)把它所捕獲的所有變量都拷貝一份蔚袍。這些拷貝放到descriptor變量后面乡范,捕獲了多少對(duì)象配名,就要占據(jù)多少內(nèi)存。請(qǐng)注意晋辆,拷貝的并不是對(duì)象本身渠脉,而是指向這些變量的指針變量。invoke函數(shù)為何要把block對(duì)象作為參數(shù)傳進(jìn)來(lái)呢瓶佳?原因就在于芋膘,執(zhí)行block時(shí),要從內(nèi)存中把這些捕獲的變量讀出來(lái)霸饲。
全局block为朋,棧block,堆block
定義block的時(shí)候厚脉,是分配在棧上的习寸,block只在定義它的范圍有效。下面的寫(xiě)法很危險(xiǎn):
void (^block)();
if(/*some condition*/){
block = ^{NSLog(@"Block A");};
}else {
block = ^{NSLog(@"Block B");};
}
block();
定義在if-else中的兩個(gè)block都是在棧上的傻工,作用范圍只限于兩個(gè)大括號(hào)之內(nèi)霞溪。所以上述可能運(yùn)行正確,可能錯(cuò)誤精钮。解決這個(gè)問(wèn)題的辦法是給block對(duì)象發(fā)送copy消息威鹿。這樣就可以把block復(fù)制到堆上了。修改后代碼:
void (^block)();
if(/*some condition*/){
block = [^{NSLog(@"Block A");} copy];
}else {
block = [^{NSLog(@"Block B");}copy];
}
block();
采用手動(dòng)計(jì)數(shù)的轨香,需要將其release忽你,采用ARC則不用。
除了“棧block”臂容、“堆block”之外科雳,還有一類(lèi)block叫做“全局Block”。這種block不會(huì)捕捉任何狀態(tài)(比如外圍的變量等)脓杉,運(yùn)行時(shí)也無(wú)需有狀態(tài)來(lái)參與糟秘。block所需要的整個(gè)內(nèi)存區(qū)域,在編譯期已經(jīng)完全確定了球散,因此尿赚,全局block可以聲明在全局內(nèi)存里,而不需要在每次用到的時(shí)候于棧中創(chuàng)建蕉堰。另外凌净,全局block的拷貝操作是一個(gè)空操作,因?yàn)槿謆lock絕不可能為系統(tǒng)回收屋讶。這種block相當(dāng)于單例冰寻。
更多block存儲(chǔ)區(qū)域參考點(diǎn)擊打開(kāi)鏈接
【本節(jié)要點(diǎn)】
● block是C、C++皿渗、OC中的詞法閉包
● block可以接收參數(shù)斩芭、也可以返回值
● block可以在棧上轻腺、堆上、全局划乖。分配在棧上的block可以拷貝到堆上贬养。
8.類(lèi)似于一種語(yǔ)法糖,看起來(lái)比較舒服
//before
-(void)startWithCompletionHandler:(void (^) (NSData *data,NSError *error))completion;
//after
typedef void(^EOCCompletionHandler) (NSData *data,NSError *error);
-(void)startWithCompletionHandler:(EOCCompletionHandler)completion;
9.沒(méi)有什么可說(shuō)的迁筛,講了一下block相對(duì)于delegate的優(yōu)勢(shì)煤蚌,摘抄一下總結(jié):
● 在創(chuàng)建對(duì)象時(shí),可以使用內(nèi)聯(lián)的handler block將相關(guān)業(yè)務(wù)邏輯一并聲明细卧。
● 在有多個(gè)實(shí)例需要監(jiān)視時(shí)尉桩,如果采用delegate模式,那么經(jīng)常需要根據(jù)傳入的對(duì)象來(lái)切換贪庙。而偌該用handler實(shí)現(xiàn)蜘犁,則可直接將block與相關(guān)對(duì)象放在一起。
● 涉及API時(shí)如果用到了handler block止邮,那么可以增加一個(gè)參數(shù)这橙,使調(diào)用者可通過(guò)此參數(shù)來(lái)決定鷹把block安排在哪個(gè)隊(duì)列上執(zhí)行。
10.注意循環(huán)引用的問(wèn)題导披,防止內(nèi)存泄露
在沒(méi)有出現(xiàn)block copy的情況下屈扎,是不會(huì)出現(xiàn)循環(huán)引用的。因?yàn)椋簵I系腷lock撩匕,雖然引用了self鹰晨,構(gòu)成環(huán)形引用,但是止毕,最終棧上的block是需要釋放的模蜡,這是一個(gè)出口。