Block总结
二、代碼塊定義 例:int ?( ^ ? ?MyBlock)( int ) = ^ ?(int m){ return m * 3; }; ? ? ? ? 1 ? ?2 ? ? ? ? 3 ? ? ? ? 4 ? ? ? 5 ? ? ? ?6 ? ? ? ? ?7 含義: 1:返回類型 2:^是代碼塊的語法標(biāo)記 3:代碼塊的變量名 4:參數(shù)類型 5:定義代碼塊對象 6:參數(shù)名是m 7:代碼塊對象的主體部分
三、代碼塊調(diào)用 拿上面的為例:int newValue = MyBlock(3);
四、代碼塊語法的一些規(guī)則: (1)當(dāng)在block中直接使用局部變量時,局部變量會被當(dāng)做是常量編碼到block中(兩個變量),所以不能在Block中直接修改局部變量 (2)代碼塊如果想要遞歸調(diào)用,代碼塊變量必須為全局變量或者靜態(tài)變量。 (3)在代碼塊中可以使用和改變?nèi)肿兞亢挽o態(tài)變量。 (4)代碼塊可以使用局部變量,但是要改變值的話,要在局部變量前面加關(guān)鍵字__block。
五、Block存儲域 根據(jù)Block中是否引用了外部變量,可以將Block存儲區(qū)域分為三種:NSGlobalBlock、NSStackBlock、NSMallocBlock。 1.NSGlobalBlock-存儲在全局?jǐn)?shù)據(jù)區(qū)域: 對于沒有引用外部變量的Block,無論在ARC還是非ARC下,類型都是__NSGlobalBlock__,這種類型的block可以理解成一種全局的block,和全局變量一樣。同時,對他進(jìn)行Copy或者Retain操作也是無效的。 2.NSStackBlock-存儲在棧上:而對于引用了外部變量的block,如果沒有對他進(jìn)行copy,他的作用域只會在聲明他的函數(shù)棧內(nèi)(類型是__NSStackBlock__)。對其執(zhí)行retain操作沒有作用。 3.NSMallocBlock-存儲在堆上:對NSStackBlock類型的block,執(zhí)行copy操作,block會被復(fù)制到堆上。retain和copy對會使其引用計數(shù)加1。
說明:一般來說出問題的Block大部分都是NSStackBlock,超過了NSStackBlock的作用域NSStackBlock就會銷毀。
六、什么時候要對NSConcreteStackBlock執(zhí)行copy操作? 1.場景:配置在棧上的Block,如果其所屬的變量作用域結(jié)束該Block就會廢棄。這個時候如果繼續(xù)使用該Block,就應(yīng)該使用copy方法,將NSConcreteStackBlock拷貝為_NSConcreteMallocBlock。當(dāng)_NSConcreteMallocBlock的引用計數(shù)變?yōu)?,該_NSConcreteMallocBlock就會被釋放。 2.如果是非ARC環(huán)境,需要顯式的執(zhí)行copy或者antorelease方法。 3.而當(dāng)ARC有效的時候,實際上大部分情況下編譯器已經(jīng)為我們做好了,自動的將Block從棧上復(fù)制到堆上。包括以下幾個情況: (1)Block作為返回值時 類似在非ARC的時候,對返回值Block執(zhí)行[[returnedBlock copy] autorelease]; (2)方法的參數(shù)中傳遞Block時 (3)Cocoa框架中方法名中還有useringBlock等時 (4)GCD相關(guān)的一系列API傳遞Block時。 比如:[mutableAarry addObject:stackBlock];這段代碼在非ARC環(huán)境下肯定有問題,而在ARC環(huán)境下方法參數(shù)中傳遞NSConcreteStackBlock會自動執(zhí)行copy。
七、一般的應(yīng)用場景: 假設(shè)A要調(diào)用B完成一件事,但是在B完成事情之后要通知A一下,這時候可以使用Block。 1.首先在B中定義一個Block類型,比如: ? typedef void (^DoSomeThingFinished)(id parame); 2.定義Block實例變量,DoSomeThingFinished aDoSomeThingFinished; 3.定義B的動作方法:-(void)doSomeThing:(DoSomeThingFinished)doSomeThingFinished; 4.動作方法實現(xiàn)規(guī)則: (1)當(dāng)doSomeThing方法被調(diào)用時,首先將doSomeThingFinished要copy一下(block將從棧賦值到堆上),并且賦值給aDoSomeThingFinished,以防止調(diào)用block時,block已經(jīng)銷毀。 (2)當(dāng)事情完成時,首先檢查代碼塊變量aDoSomeThingFinished是否為nil,如果不為nil,調(diào)用aDoSomeThingFinished代碼塊變量,并傳入合適的值。然后release代碼塊變量aDoSomeThingFinished,并賦值nil。 5.在B銷毀時,檢查aDoSomeThingFinished是否為nil,如果不為nil,release,并且賦值nil。 6.A調(diào)用B的方法,如下: [b doSomeThing:^(id parame){ /*動作完成時要做的事情*/ }];
八、Block循環(huán)引用: (1)場景:假如A調(diào)用B,B的API使用了Block。在A中有一個B的實例作為成員變量,此時A引用了B;A在使用B的API的時候,在Block代碼中使用了self關(guān)鍵字或者A的成員變量,導(dǎo)致block引用了A,即B引用了A。從而導(dǎo)致循環(huán)引用。 (2)導(dǎo)致的問題:如果Block被很好的執(zhí)行,并且B release了Block,A的引用計數(shù)自然就降下來了,循環(huán)引用消失。但是,如果B長時間或者根本沒有調(diào)用Block,導(dǎo)致B一直引用Block并且沒有釋放它,從而A的引用計數(shù)一直降不下來,導(dǎo)致A不能釋放。 (3)解決辦法: MRC:重新定義一下self,如下:__block typeof(self) bself = self; 將self定義為__block類型,在block中使用bself變量,此時block就不會retain當(dāng)前控制器了。當(dāng)A銷毀前,首先將B銷毀掉,B銷毀時,代碼塊被release并置nil,block將不會被執(zhí)行。__block關(guān)鍵字告訴編譯器,不要retain該變量。 ARC:對于ARC下, 為了防止循環(huán)引用, 我們使用__weak來修飾在Block中使用的對象。 (4)說明:在使用Block時,可以大膽的說,百分之九十九的情況下是不需要使用weakSelf的。 Block代碼塊引用self以至retain當(dāng)前控制器,是有道理的,這樣做是為了讓代碼塊得到很好的執(zhí)行,如果當(dāng)前控制器已經(jīng)釋放了,在回調(diào)代碼塊的時候,就不正常了。 我們需要做的是,維護(hù)好block的調(diào)用關(guān)系以及生命周期就可以了,讓block及時釋放,對當(dāng)前控制器的引用自然而然也就釋放了。 那剩下的那百分之一是什么情況呢?即對Block的持有者一直保持Block不釋放的情況,比如以Block的方式使用加速計,除非將加速計停止,否則Block一直會被持有,如果不希望這樣,可以在Block中使用weakSelf。
九、Block內(nèi)存演示代碼:
//======================================================
//ARC:正確
//MRC:正確
void exampleA() {
? ? char a = 'A';
? ? ^{
? ? ? ? printf("%c\n", a);
? ? }();
}
//======================================================
//ARC:正確。
//MRC:不正確---EXC_BAD_ACCESS
//說明:添加的block在棧上,ARC下,block會被copy。MRC下,如果沒有執(zhí)行copy操作,此block在函數(shù)體結(jié)束之后就釋放了。
//addObject方法執(zhí)行的是retain操作,不起作用。
void exampleB() {
? ? NSMutableArray *array = [NSMutableArray array];
? ? exampleB_addBlockToArray(array);
? ? void (^block)() = [array objectAtIndex:0];
? ? block();
}
void exampleB_addBlockToArray(NSMutableArray *array) {
? ? char b = 'B';
? ? [array addObject:^{
? ? ? ? printf("%c\n", b);
? ? }];
}
//======================================================
//ARC:正確
//MRC:正確
//說明:添加的Block為全局Block,函數(shù)體結(jié)束之后,它還存在。
void exampleC() {
? ? NSMutableArray *array = [NSMutableArray array];
? ? exampleC_addBlockToArray(array);
? ? void (^block)() = [array objectAtIndex:0];
? ? block();
}
void exampleC_addBlockToArray(NSMutableArray *array) {
? ? [array addObject:^{
? ? ? ? printf("C\n");
? ? }];
}
//======================================================
//ARC:正確
//MRC:不正確,編譯不通過--Returning block that lives on the local stack
//說明:添加的Block在棧上,ARC下,block會被copy。MRC下,如果沒有執(zhí)行copy操作,此block在函數(shù)體結(jié)束之后就釋放了。
typedef void (^dBlock)();
dBlock exampleD_getBlock() {
? ? char d = 'D';
? ? return ^{
? ? ? ? printf("%c\n", d);
? ? };
}
void exampleD() {
? ? exampleD_getBlock()();
}
//=======================================================
//ARC:正確
//MRC:不正確。這個例子和例子4類似,除了編譯器沒有認(rèn)出有錯誤,所以代碼會進(jìn)行編譯然后崩潰。更糟糕的是,這個例子比較特別,如果你關(guān)閉了優(yōu)化,則可以正常運行。所以在測試的時候需要注意。
typedef void (^eBlock)();
eBlock exampleE_getBlock() {
? ? char e = 'E';
? ? void (^block)() = ^{
? ? ? ? printf("%c\n", e);
? ? };
? ? return block;
}
void exampleE() {
? ? eBlock block = exampleE_getBlock();
? ? block();
}
總結(jié)
- 上一篇: 计算机PPT03,南京大学计算机网络课件
- 下一篇: MVPVM模式介绍