前言
上篇文章整理了關(guān)于“內(nèi)存管理”的知識點,這篇主要整理“Blocks”的內(nèi)容救恨,在學(xué)習(xí)Blocks的內(nèi)容時忿薇,我一開始也采取了學(xué)習(xí)“內(nèi)存管理”時采用的先閱讀《iOS與OS X多線程和內(nèi)存管理》再配合《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法 》署浩,但是在學(xué)習(xí)《iOS與OS X多線程和內(nèi)存管理》時有些理解比較困難扫尺,恰恰《Effective Objective-C 2.0 編寫高質(zhì)量iOS與OS X代碼的52個有效方法 》中的內(nèi)容雖然比較少弊攘,但對閱讀《iOS與OS X多線程和內(nèi)存管理》中的內(nèi)容很有幫助,當(dāng)然還有別的學(xué)習(xí)途徑迈倍。以下內(nèi)容如有理解錯誤的地方啼染,希望大家指出迹鹅,我們一起學(xué)習(xí)和探討~
什么是blocks
BIocks是C語言的擴充功能贞言,可概括為:帶有自變量值的(局部變量)的匿名函數(shù)(匿名函數(shù)就是不帶名稱的函數(shù))
-
C語言的函數(shù)中可能使用的變量
- 自動變量(局部變量)
- 函數(shù)的參數(shù)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
-
在函數(shù)的多次調(diào)用之間能夠傳遞值的變量(雖然這些變量的作用域不同弟蚀,但在整個程序中粗梭,一個變量總保持在一個內(nèi)存區(qū)域断医,所以雖然多次調(diào)用函數(shù)奏纪,該變量值總保持不變)
- 靜態(tài)變量(靜態(tài)局部變量)
- 靜態(tài)全局變量
- 全局變量
C++和OC使用類可保持變量值且能夠多次持有該變量自身序调,它會聲明持有成員變量的類发绢,有類生成的實例或?qū)ο蟊3衷摮蓡T變量的值
-
‘帶有自變量值的的匿名函數(shù)’這一個概念不僅指Blocks边酒,它還存在于其他許多程序語言中墩朦,在計算機科學(xué)中,次概念也稱為‘閉包(closure)’陋气、‘lambda’
FD8CBA1E-3A58-48D1-8DE9-1BDD7061F00D.png
Blocks模式
Block語法(Block表達式語法)
-
Block語法與函數(shù)的不同
- 沒有函數(shù)名
- 帶有 ‘^’
Block語法(無省略)
^int (int count){return count + 1;}
//表達式中含有return語句時,其類型必須與返回值類型相同
- Block語法(省略返回值類型)
^ (int count){return count + 1;}
//省略返回值類型時晶渠,如果表達式中有return語句就使用該返回值的類型,如果表達式中沒有return語句就是void類型便瑟,表達式中含有多個return語句時到涂,所以return的返回值類型必須相同
- Block語法(省略返回值類型践啄、參數(shù)列表)
//不使用參數(shù)的Block語法
^void (void){ }
//省略后表達式為
^{ }
Block類型變量
在Block語法下昭灵,可將Block語法賦值給聲明為Block類型的變量中烂完,即代碼中一旦使用Block語法就相當(dāng)于生成了可賦值給Block類型變量的‘值’
Blocks中由Block語法生成的值也被稱為‘Block’
Block即指代碼中的Block語法诵棵,又指由Block語法所生成的值
聲明Block類型變量
int (^blk)(int);
返回類型(^變量名)(參數(shù))
- Block類型的變量用途
- 自動變量
- 函數(shù)參數(shù)
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
//-使用Block語法將Block賦值為Block類型變量
int(^blk)(int) = ^int(int count){return count + 1;};
//由Block類型變量向Block類型變量賦值
int(^blk1)(int) = blk嘶窄;
int(^blk2)(int)柄冲;
blk2 = blk1储耐;
//在函數(shù)參數(shù)中使用Block類型變量可以函數(shù)傳遞Block(值)
void fun(int(^blk)(int)){
}
//在函數(shù)返回值中指定Block類型,可以將Block作為函數(shù)的返回值返回
int(^func())int{
return ^(int count){return count + 1;};
}
//使用 typedef
typedef int(^blk_t) int;
//原記述方式
void fun(int(^blk)(int)){
}
//等價于
void fun(int(blk_t blk){
}
//原記述方式
int(^func())int{
return ^(int count){return count + 1;};
}
//等價于
blk_t func{
}
截獲自動變量值
- ‘帶有自動變量值’在Blocks中表現(xiàn)為‘截獲自動變量值’
int main{
int day = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^ blk)(void) = ^{
printf(fit , val);
};
val = 2;
fmt = "There values were changed . val = %d\n";
blk();
return 0;
}
/*
*Block語法的表達式使用的是它之前聲明的自動變量fmt和val
*Blokcs中,Block表達式中截獲所使用的自動變量的值得哆,即Block表達式保存該自動變量的瞬間值
*執(zhí)行Block語法后贩据,即使改寫B(tài)lock中使用的自動變量也不會影響B(tài)lock執(zhí)行時自定變量的值
*結(jié)果輸出 val = 10饱亮;
*/
_ _block說明符
- 自動變量的值截獲只能保存執(zhí)行Block語法瞬間的值近上,保存后就不能改寫該值
int val = 0;
void (^blk)(void) = ^{ val = 1;};
blk();
printf("val = %d\n");
//編譯報錯
- 如果想在Block語法表達式中將值賦給Block語法外聲明的自動變量(例如:val)壹无,需要在該自動變量上附加_ _block說明符
_ _block int val = 0;
void (^blk)(void) = ^{ val = 1;};
blk();
printf("val = %d\n");
//結(jié)果輸出:val = 1斗锭;
- 使用附有_ block說明符在自動變量可以在Block中賦值,該變量稱為 _block變量
截獲自動變量
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
id obj = [[NSObject alloc] init];
[arrray addObject:obj];
}
/*
*編譯不會報錯
*代碼中截獲的是變量值為NSMutableArray類的對象(即截獲的是NSMutableArray類對象用的結(jié)構(gòu)體實例指針)
*截獲OC對象帮毁,調(diào)用變更改對象的方法(addObject)作箍,即使用截獲對象不會出現(xiàn)任何錯誤
*/
id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
array = [[NSMutableArray alloc] init];
}
/*
*編譯報錯
*向截獲的變量array賦值會產(chǎn)生編譯錯誤
*/
_ _block id array = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
array = [[NSMutableArray alloc] init];
}
//編譯成功
Blocks實現(xiàn)
Block的實質(zhì)
- Block即為Object_C的對象
- C語言中有一下存儲類的說明符
- typedef
- extern
- static(作為靜態(tài)變量存儲在數(shù)據(jù)區(qū))
- auto(表示作為自動變量存儲在棧中)
- register
Block存儲域
- Block轉(zhuǎn)換為Block的結(jié)構(gòu)體類型自動變量
- _ block變量轉(zhuǎn)換為 _block變量的結(jié)構(gòu)體類型自動變量
- 結(jié)構(gòu)體類型的自動變量,即棧上生成的該結(jié)構(gòu)體的實例
-
Block也是OC對象阶剑,該Block的類
FDA8982E-C03A-46CE-8321-AE8D4E28DC85.png
- 記述全局變量的地方有Block語法時牧愁、Bolck語法的表達式中不使用應(yīng)截獲的自動變量時猪半,Block為_NSConcreteGlobalBlock類的對象,即Block配置的程序的數(shù)據(jù)區(qū)域中
- 除上述情況Block配置的程序的棧上
-配置在全局變量上的Block沽甥,從變量的作用域外也可以通過指針安全使用 - 配置在棧上的Block摆舟,如果其所屬的變量作用域結(jié)束恨诱,該Block就被廢棄照宝,由于_ _block變量也配置在棧上硫豆,同理笼呆。
- Blocks提供了將Block和_ _block變量從棧上復(fù)制到堆上的方法來解決,將配置在棧上的Block復(fù)制到堆上秸弛,這樣即使Block語法記述的作用域結(jié)束递览,堆上的Block還是可以繼續(xù)存在(ARC有效,自動生成將Block從棧上復(fù)制到堆上)
_ _block變量存儲域
- Block從棧中復(fù)制到堆上時镜雨,_ _block變量也會受影響(下圖)
- 若在1個Block中使用_ block變量,當(dāng)該Block從棧中復(fù)制到堆上菲盾,使用的所有 block變量也必定配置在棧上懒鉴,這些 block變量也全部被從棧中復(fù)制到堆上,此時奴璃,Block持有堆 _block變量溺健,即使在該Block已被復(fù)制在堆上的情況下钮蛛,復(fù)制Block也對所使用的_block變量沒有任何影響(下圖)
3FD99E0A-F6A2-4C4B-AEC0-98089992EEBC.png
-
在多個Block中使用_ block變量時岭辣,因為最先會將所有的Bolck配置在棧上甸饱,所以 block變量也會配置在棧上叹话,在任何一個Block從棧復(fù)制到堆時驼壶, block變量也會一并從棧中復(fù)制到堆并被該Block所持有,當(dāng)剩下的Block從棧中復(fù)制到堆時泵喘,被復(fù)制的Block持有 block變量(之前復(fù)制到堆上的)纪铺,并增加 _block變量的引用計數(shù)(下圖)
65B9DA56-8EC4-41CB-BE34-0884FC1F45E3.png -
如果配置在堆上的Block被廢棄鲜锚,那么它所使用的_ block變量也就被釋放(同理引用計數(shù)式內(nèi)存管理烹棉,使用 block變量的Block持有 block變量浆洗,如果Block被廢棄伏社,它所持有的 _block變量也就被釋放)(下圖)
723DF0DA-B19B-4D66-8A64-1FD9129DEDD8.png
- Block的_ _main_block_impl_0結(jié)構(gòu)體實例持有指向 _ _block變量的 _ _Block_dyref_val_0結(jié)構(gòu)體實例指針
- _ _Block_dyref_val_0結(jié)構(gòu)體實例的成員變量 _ _forwarding持有指向該實例自身的指針摘昌,通過成員變量 _ _forwarding訪問成員變量val(成員變量val是該實例自身持有的變量,它相當(dāng)于原自動變量)
- 不管_ block變量配置在棧上還是堆上罕容,都能夠正確的訪問該變量(通過Block的復(fù)制锦秒,_ block變量也從棧上復(fù)制到堆旅择,此時可同時訪問棧上的 block變量和堆上的 _block變量)
- 棧上的_ block變量用結(jié)構(gòu)體實例在 _block變量從棧復(fù)制到堆上時生真,會將成員變量 _ forwarding的值替換為復(fù)制目標(biāo)堆上 _block變量用結(jié)構(gòu)體實例的地址
- 通過該功能柱蟀,無論是在Block語法中产弹,Block語法外使用弯囊,_ block變量匾嘱,還是 block變量配置在棧上還是堆上霎烙,都可以順利訪問同一個 _block變量悬垃。
截獲對象
- 生成并持有NSMutableArray類的對象尝蠕,由于附有_strong修飾符的賦值目標(biāo)變量作用域立即結(jié)束看彼,所以對象也被立即釋放并廢棄
id array = [[NSMutableArray alloc] init];
- 在Block語法中使用該變量array
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObjct:obj];
NSLog(@"array count = %ld",[array coun]);
} copy];
}
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
//輸出結(jié)果
array count = 1
array count = 2
array count = 3
/*
*輸出結(jié)果意味著,賦值給變量array的NSMutableArray類的對象在該源代碼最后Block的執(zhí)行部分超出變量作用域而存在
*被賦值NSMutableArray類對象并被截獲的自動變量array标锄,它是Block用的結(jié)構(gòu)體中附有_strong修飾符的成員變量
struct _main_block_impl_0{
struct _block_impl impl;
struct _main_block_desc_0 *Desc;
id strong array;
}
*Block用結(jié)構(gòu)體中料皇,還有附有_strong修飾符的對象類型變量array践剂,所以需要恰當(dāng)管理賦值給變量array的對象舷手,因此_main_block_copy_0函數(shù)使用_Block_object_assjgn函數(shù)將對象類型對象賦值給Block用結(jié)構(gòu)體的成員變量array中并持有該對象
*_Block_object_assjgn函數(shù)調(diào)用相當(dāng)一retain實例方法函數(shù)男窟,將對象賦值給對象類型的結(jié)構(gòu)體成員變量中
*_main_block_dispose_0函數(shù)使用_Block_object_dispose函數(shù)贾富,釋放賦值在Block用結(jié)構(gòu)體成員變量array中的對象颤枪,相當(dāng)于release實例方法畏纲,釋放賦值在對象類型結(jié)構(gòu)體成員變量中的對象
*/
- 在Block從棧復(fù)制到堆時以及堆上的Block被廢棄時會調(diào)用的函數(shù)
-
什么時候棧上的Block會復(fù)制到堆盗胀?
- 調(diào)用Blcok的coyp實例方法時(如果Block配置在棧上票灰,那么該Block會從棧上復(fù)制到堆)
- Block作為函數(shù)返回值返回時(編譯器自動的將對象的Block作為參數(shù)并調(diào)用_Block_cpoy函數(shù)-等同與copy實例方法)
- 將Block賦值給附有_strong修飾符id類型的類或Block類型成員變量時(編譯器自動的將對象的Block作為參數(shù)并調(diào)用_Block_cpoy函數(shù)-等同與copy實例方法)
- 在方法名中含有usingBlock的Cocoa框架方法或GrandCentralDispatch的API中傳遞Block時(該方法或函數(shù)內(nèi)部對傳遞過來的Block調(diào)用Block的copy實例方法或_Block_cpoy函數(shù))
上述情況下棧上的Block被復(fù)制到堆上屑迂,其實實質(zhì)是_Block_cpoy函數(shù)被調(diào)用時Block從棧上復(fù)制到堆
-在釋放復(fù)制到堆上的Block后,誰都不持有Blcok而是其被廢棄時調(diào)用despose函數(shù)庸汗,相當(dāng)于對象的release實例方法綜上所述夫晌,通過使用附有_strong修飾符的自動變量,Block中截獲的對象就能夠超出其變量的作用域而存活
截獲對象時和使用_ _block對象時copy函數(shù)或dispose函數(shù)的不同
- 通過BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF參數(shù)所袁,區(qū)分copy函數(shù)和dispose函數(shù)的對象類型時對象還是_ _block變量
- 與copy函數(shù)持有截獲的對象燥爷,dispose函數(shù)釋放截獲的對象相同前翎,copy函數(shù)持有所使用的_ block變量港华,dispose函數(shù)釋放所使用的 _block變量
- Block中使用的賦值給附有strong修飾符的自動變量的對象和復(fù)制到堆上的 _block變量由于被堆上的Block所持有立宜,因而可超出其作用域而存在
- 只有調(diào)用_Block_cpoy函數(shù)才能持有截獲的附有_strong修飾符的對象類型的自動變量值臊岸,如果沒有調(diào)用_Block_cpoy函數(shù)帅戒,即使截獲了對象,它也會隨著變量的作用域結(jié)束而被廢棄
- Block 中使用對象類型自動變量時钟哥,除以下情況外腻贰,推薦調(diào)用Block的copy實例方法
- Blcok作為函數(shù)返回值返回時
- 將Block賦值給類的附有_strong修飾符的id類型或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或GrandCentralDispatch的API中傳遞Block時
_ _block變量和對象
_ _block說明符可指定任何類型的自動變量
在Block賦值給附有_strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下,當(dāng)Block從棧中復(fù)制到堆時践盼,使用_Block_object_assjgn函數(shù)咕幻,持有Block截獲的對象,到堆上的Block被廢棄時锣吼,使用_Block_object_dispose函數(shù)函數(shù),釋放Block截獲的對象
在_ _block變量為附有strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下古徒,當(dāng) _block變量從棧中復(fù)制到堆時读恃,使用Block_object_assjgn函數(shù)寺惫,持有賦值給 block變量的對象西雀,到堆上的 _block變量被廢棄時,使用Block_object_dispose函數(shù)函數(shù)腔呜,釋放賦值給 _block變量的對象
即使對象賦值復(fù)制到堆上的附有strong修飾符的對象類型 block變量中育谬,只有 _block變量在堆上繼續(xù)存在膛檀,那么該對象就會繼續(xù)處于被持有的狀態(tài)
在Block語法中使用該變量array
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id _ _weak array2 = array;
_ _block id _ _weak array3 = array;
blk = [^(id obj){
[array2 addObjct:obj];
NSLog(@"array2 count = %ld娘侍,array2 count = %ld",[array2 coun]憾筏,[array3 coun]);
} copy];
}
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
blk ([[NSObject alloc] init]);
//輸出結(jié)果
array2 count = 0 array3 count = 0
array2 count = 0 array3 count = 0
array2 count = 0 array3 count = 0
/*
*由于附有_strong修飾符的變量array在該變量作用域結(jié)束的同時釋放氧腰、廢棄,nil被賦值在附有_ _weak修飾符的變量array2中箩帚,即使附加 _ _block修飾符附有_strong修飾符的變量array在該變量作用域結(jié)束的同時釋放紧帕、廢棄是嗜,nil被賦值在附有_ _weak修飾符的變量array3中
*/
Block循環(huán)引用
- 通過_weak修飾符,避免循環(huán)引用
typedef void (^blk_t) (void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
blk_ = ^{ NSLog(@"self = %@", self); }
return self;
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int mian(){
id o = [[MyObject alloc] init];
NSLog(@"%@", o );
return o;
}
/*
* MyObject類的dealloc實例方法一定沒有被調(diào)用
* MyObject類對象的Block類型成員變量blk_持有賦值為Block的強引用(MyObject類對象持有Block)
*init實例方法中執(zhí)行的Block語法使用附有_strong修飾符的id類型變量self
*由于Block語法賦值在成員變量blk_中站绪,通過Block語法生成在棧上的Block此時有棧復(fù)制在堆上崇众,并持有所使用的self航厚,self持有Block幔睬,Block持有self,形成循環(huán)引用
*/
- 為避免此循環(huán)赦抖,可聲明附有_weak修飾符的變量队萤,并將self賦值使用
-(id)init{
self = [super init];
id _ _weak tmp = self;
blk_ = ^{ NSLog(@"self = %@", tmp); }
return self;
}
由于Block存在時要尔,持有該Block的Myobject類對象即復(fù)制在變量tmp中的self必定存在赵辕,因此不需要判斷變量tmp的值是否為nil
使用_ _block變量來避免循環(huán)引用
typedef void (^blk_t) (void);
@interface MyObject : NSObject
{
blk_t blk_;
}
@end
@implementation MyObject
-(id)init{
self = [super init];
_ _block id tmp = self;
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
}
return self;
}
-(void)execBlock{
blk_();
}
-(void)dealloc{
NSLog(@"dealloc");
}
@end
int mian(){
id o = [[MyObject alloc] init];
[o execBlock];
return o;
}
- 不執(zhí)行execBlock實例方法还惠,即不執(zhí)行賦值給成員變量blk_的Block蚕键,會引起循環(huán)引用引起的內(nèi)存泄露锣光,存在循環(huán)引用:
- MyObject類對象持有Block
- Block持有 _ _block 變量
- _ _block變量持有MyObject類對象
- 執(zhí)行execBlock實例方法嫉晶,Block被實行田篇,nil被賦值在 _ _block 變量tmp中
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
}
/*
*_ _block 變量tmp對MyObject類對象的強引用失效,避免循環(huán)引用
* MyObject類對象持有Block
* Block持有 _ _block 變量
*/
copy/release
- ARC無效時椎镣,一般需要手動使用copy實例方法將Block從棧上復(fù)制到堆状答。通過release實例方法釋放復(fù)制的Block
- 只要Block有一次復(fù)制并配置在堆上惊科,就可通過retain實例方法持有(但對于配置在棧上的Block調(diào)用retain實例方法則不起作用)
- 在ARC無效時馆截,_ block說明符被用來避免循環(huán)引用蜂莉,這是由于當(dāng)Block從棧復(fù)制到堆時映穗,若Block使用的變量為附有 block說明符的id類型或?qū)ο箢愋偷淖詣幼兞浚粫籸etain宿接,若Block使用的變量為沒有 block說明符的id類型或?qū)ο箢愋偷淖詣幼兞砍窝簦瑒t會被retain(ARC有效或無效時 _block說明符的用途不同踏拜,要注意)