目錄
- block概要
- 自動變量的截獲
- block的調(diào)用本質(zhì)
- block的內(nèi)存管理
- block的循環(huán)引用
1.block概要
在剛接觸iOS的時候帘睦,block真是一個讓人頭疼的東西祷嘶,基本上所有的第三方框架都用了block蔗候,然后在我們閱讀這些大神框架的時候濒生,如果沒有學(xué)好block结耀,不了解block的用法抑党,那基本上無法把這些第三方框架給吃透润绎。
那么什么是block呢撬碟,我從一些書上得到的答案是:帶有自動變量的匿名函數(shù),顧名思義莉撇,匿名函數(shù)就是不帶有名稱的函數(shù)呢蛤。帶有自動變量這個意思我們在后面再具體講。下面我們看看block的語法:
block語法:
^ 返回值類型 參數(shù)列表 表達(dá)式
例如
^ int ( int a){
return a+1;
}
- 一般在開發(fā)過程中棍郎,我們可以省略返回值類型
^ 參數(shù)列表 表達(dá)式
例如
^( int a){
return a+1;
}
注意: 當(dāng)我們省略返回值時顾稀,如果表達(dá)式中有return語句,則返回值類型為該返回值的類型坝撑,如果沒有return語句静秆,則返回值類型為void類型粮揉。如果表達(dá)式中有多個return語句,所有的返回值必須是相同類型抚笔,不然會報錯扶认。 報錯信息為:
Return type '類型1 *' must match previous return type '類型2' when block literal has unspecified explicit return type
- 其次在沒有參數(shù)的時候,我們還可以省略其參數(shù)
例如
^{
NSLog(@"省略參數(shù)");
}
Block類型變量
聲明BLock屬性的時候殊橙,需要定義一個屬性類型:
@property (nonatomic,copy) void (^cpBlock)(void);
_cpBlock = ^{
NSLog(@"111");
};
_cpBlock();
- 當(dāng)block作為參數(shù)的時候:
- (void)add:(void (^)(void))block{
block();
}
[self add:^{
NSLog(@"111");
}];
- 當(dāng)block作為返回值的時候:
- (void (^)(void))sum{
return ^{
NSLog(@"111");
};
}
[self sum]();
但是 我們一般在開發(fā)中并不會直接這么用辐宾,因?yàn)檫@樣寫也太繁瑣了。我們一般使用typedef
來解決問題:
typedef void (^Blk)(void);
@property (nonatomic,copy) Blk block;
- (void)add:(Blk)block{
block();
}
- (Blk)sum{
return ^{
NSLog(@"111");
};
}
2.自動變量的截獲
上面我們講了block是帶有自動變量的匿名函數(shù)膨蛮,我們已經(jīng)解釋了匿名函數(shù)叠纹,那么什么是帶有自動變量呢,我們先來看看下面的代碼:
typedef int (^Blk)(void);
int age = 10;
Blk block = ^{
NSLog(@"age = %d", age);
};
age = 20;
block();
相信有點(diǎn)開發(fā)經(jīng)驗(yàn)的同學(xué)敞葛,應(yīng)該馬上知道結(jié)果:
結(jié)果是:age = 10
block在使用過程中誉察,會截獲表達(dá)式中所使用的自動變量的值,即保存該自動變量的瞬間值惹谐。因?yàn)閎lock在內(nèi)部保存了這個自動變量的值持偏,所以在執(zhí)行block語法后,即使在修改block中使用的自動變量的值也不會影響block執(zhí)行時自動變量的值氨肌。如果block表達(dá)式中不使用自動變量鸿秆,則不會截獲,因?yàn)榻孬@的自動變量會存儲于block的結(jié)構(gòu)體內(nèi)部, 會導(dǎo)致block體積變大怎囚。特別要注意的是默認(rèn)情況下block只能訪問不能修改局部變量的值卿叽。
截獲變量的類型
- 局部變量
對于基本數(shù)據(jù)類型的局部變量截獲其值
對于對象類型的局部變量連同所有權(quán)修飾符一起截獲
- 靜態(tài)局部變量
以指針形式截獲局部靜態(tài)變量
- 全局變量
不截獲
- 靜態(tài)全局變量
不截獲
__block修飾符
默認(rèn)情況下,block只能訪問不能修改局部變量的值恳守,而且在表達(dá)式后再修改block語法外聲明的自動變量考婴,無法影響block內(nèi)部的自動變量。那么如果我們想在block語法的表達(dá)式將值賦給block語法外聲明的自動變量井誉,則需要在該自動變量上附加__block修飾符蕉扮。
typedef void (^Blk)(void);
__block int age = 10;
Blk block = ^{
NSLog(@"age = %d", age);
age = 5;
NSLog(@"age = %d", age);
};
age = 20;
block();
結(jié)果是:
age = 20
age = 5
那么這是為什么呢,我們可以在命令行輸入代碼 clang -rewrite-objc 需要編譯的OC文件.m,看一下block底層是怎么實(shí)現(xiàn)的颗圣。
__block修飾的時候
__block int age = 10;
Blk block = ^{
age喳钟;
};
age = 20;
block();
轉(zhuǎn)化成cpp后是
Blk block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
struct __main_block_impl_0 {
struct __block_impl impl;//封裝了函數(shù)實(shí)現(xiàn)的結(jié)構(gòu)體
struct __main_block_desc_0* Desc;// 里面有內(nèi)存管理函數(shù),Block_size表示block的大小
__Block_byref_age_0 *age; // by ref
// 這個結(jié)構(gòu)體的初始化函數(shù) , 入?yún)?: fp,函數(shù)實(shí)現(xiàn)的函數(shù)指針, __main_block_desc_0,占用大小的描述,age傳入的是一個地址
// 返回一個__main_block_impl_0類型的結(jié)構(gòu)體,賦值給了block
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;//棧block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
無__block修飾的時候
int age = 10;
Blk block = ^{
age;
};
age = 20;
block();
轉(zhuǎn)化成cpp后是
Blk block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
struct __main_block_impl_0 {
struct __block_impl impl;//封裝了函數(shù)實(shí)現(xiàn)的結(jié)構(gòu)體
struct __main_block_desc_0* Desc;// 里面有內(nèi)存管理函數(shù),Block_size表示block的大小
int age;// 捕獲到的普通局部變量
// 這個結(jié)構(gòu)體的初始化函數(shù) , 入?yún)?: fp,函數(shù)實(shí)現(xiàn)的函數(shù)指針, __main_block_desc_0,占用大小的描述
// 返回一個__main_block_impl_0類型的結(jié)構(gòu)體,賦值給了block
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
相比之下在岂,__block修飾后奔则,把變量變成來__Block_byref_age_0結(jié)構(gòu)體對象,而沒有__block修飾時蔽午,捕獲的是變量的瞬間值易茬,所以結(jié)果顯而易見。
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
這個結(jié)構(gòu)體有一個__forwarding指針,這個指針指向這個結(jié)構(gòu)體抽莱,通過這個__forwarding可以訪問結(jié)構(gòu)體的成員變量age
下面我們來看下下面的代碼:
{
NSMutableArray *array =[NSMutableArray array];
void (^Block)(void) = ^{
[array addObject:@123];
};
Block()
}
這段代碼需要給array加__block嗎
不需要范抓,因?yàn)檫@個只是使用,并沒有對array賦值
注意 靜態(tài)局部變量食铐、全局變量匕垫、靜態(tài)全局變量不需要添加__block
3.block的調(diào)用本質(zhì),
block的調(diào)用其實(shí)就是函數(shù)的調(diào)用,我們通過源碼去看
(1)block定義的時候虐呻,把表達(dá)式傳給__main_block_func_0
Blk block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
看一下__main_block_impl_0的結(jié)構(gòu)體實(shí)現(xiàn):
struct __main_block_impl_0 {
struct __block_impl impl;//封裝了函數(shù)實(shí)現(xiàn)的結(jié)構(gòu)體
struct __main_block_desc_0* Desc;/ 里面有內(nèi)存管理函數(shù),Block_size表示block的大小
__Block_byref_age_0 *age; // by ref
//構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
//把__main_block_func_0函數(shù)賦值給__block_impl結(jié)構(gòu)體的FuncPtr
impl.FuncPtr = fp;
Desc = desc;
}
};
再看下這個函數(shù)實(shí)現(xiàn)結(jié)構(gòu)體__block_impl的代碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
(2)然后我們再看__main_block_func_0的函數(shù)定義
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
age;
}
(3) 然后再看下block調(diào)用的代碼
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
先是把block強(qiáng)轉(zhuǎn)成__block_impl類型象泵,然后取出FuncPtr屬性,而這個屬性就是__main_block_impl_0結(jié)構(gòu)體里面構(gòu)造函數(shù)的fp斟叼,也就是(1)里面的__main_block_func_0函數(shù)偶惠。
所以,block的調(diào)用其實(shí)就是__main_block_func_0函數(shù)的調(diào)用朗涩,也就是表達(dá)式的調(diào)用忽孽。
4.block的內(nèi)存管理
block的存儲域
block是一個對象,根據(jù)block對象的存儲域馋缅,可以分為以下幾種:
1._NSConcreteStackBlock 存儲在棧上
2._NSConcreteGlobalBlock 存儲在程序的數(shù)據(jù)區(qū)域(.data區(qū))
3._NSConcreteMallocBlock 存儲在堆上
根據(jù)結(jié)構(gòu)體__block_impl里面的的isa指針扒腕,可以判斷block的存儲域
Block的Copy操作
1.對于棧上的block绢淀,copy后會在堆上產(chǎn)生Block
2.對于已初始化數(shù)據(jù)取的全局block萤悴,copy后什么也不做
3.對于堆上的block,copy后會增加引用計(jì)數(shù)
那么什么時候block會被copy呢皆的?
1.調(diào)用Block的copy實(shí)例方法時
2.Block作為函數(shù)返回值返回時
3.將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
4.在方法名中含有usingBlock的Cocoa框架方法或GCD的Api中傳遞Block時
__block變量的存儲域
上面我們講了block的復(fù)制覆履,那么對__block變量又是怎么處理的呢?
__block變量的配置存儲域 | Block從棧復(fù)制到堆時的影響 |
---|---|
棧 | 從棧復(fù)制到堆并被Block持有 |
堆 | 被Block持有 |
(1)若只有1個block中使用__block變量费薄,則當(dāng)該Block從棧復(fù)制到堆時硝全,使用的所有__block變量也必定配置在棧上,在復(fù)制block的時候楞抡,也會把__block變量從棧復(fù)制到堆伟众。此時block持有__block變量。如果block已經(jīng)在堆上召廷,再進(jìn)行copy凳厢,那么堆block所使用的__block變量沒有任何影響。
(2) 若多個block中使用__block變量時竞慢,因?yàn)樽钕葘⑺械腷lock配置在棧上先紫,所以 __block變量也會配置在棧上。在任何一個block從棧復(fù)制到堆時筹煮,__block變量也會一并從棧復(fù)制到堆并被該block持有遮精。當(dāng)剩下的block從棧復(fù)制到堆時,被復(fù)制的block持有__block變量败潦,并增加__block變量的引用計(jì)數(shù)本冲。
注意 當(dāng)__block變量從棧復(fù)制到堆時准脂,會同時把棧上的__block變量的__forwarding指針指向堆上的__block變量,通過這一操作檬洞,無論在Block表達(dá)意狠、Block表達(dá)式外使用__block變量,不管__block變量配置在棧上還是堆上疮胖,都可以順利地訪問同一個__block變量环戈。
總結(jié):__forwarding的意義:不論在任何內(nèi)存位置,都可以順利的訪問同一個__block變量澎灸。
5.block的循環(huán)引用
在開發(fā)過程中院塞,block的循環(huán)引用應(yīng)該是我們經(jīng)常碰到也讓人頭疼的問題。比如我們看下面的代碼塊:
_array = [NSMutableArray arrayWithObject:@"block"];
_cpBlock = ^NSString *(NSString *str){
return [NSString stringWithFormat:@"hello_%@",_array[0]];
};
_cokong(@"hello");
這個代碼塊有什么問題的性昭,相信有開發(fā)經(jīng)驗(yàn)的同學(xué)應(yīng)該能立馬看出來拦止,這段代碼有block循環(huán)引用的問題。
因?yàn)槲覀儺?dāng)前block是用copy
修飾的糜颠,而array是用strong
修飾的,而在截獲變量中汹族,我們知道,關(guān)于block中對象類型的局部變量會連同其屬性關(guān)鍵字一起截獲其兴,所以在block中有一個strong指針指向self顶瞒,所以造成了自循環(huán)引用。
那么我們怎么去解決呢元旬?
(1)我們可以使用__weak
來解決循環(huán)引用榴徐,如下代碼塊
_array = [NSMutableArray arrayWithObject:@"block"];
__weak NSArray *weakArray = _ayyay;
_cpBlock = ^NSString *(NSString *str){
return [NSString stringWithFormat:@"hello_%@",weakArray[0]];
};
_cokong(@"hello");
這樣,因?yàn)閷ο蠼孬@的時候會連同其屬性關(guān)鍵字一起截獲匀归,這邊block指向的是一個__weak修飾的self坑资,所以不會造成循環(huán)引用。
(2)同理穆端,我們可以使用__unsafe_unretained來解決循環(huán)引用袱贮。如下代碼塊
_array = [NSMutableArray arrayWithObject:@"block"];
_unsafe_unretained NSArray *unsafe_unretainedArray = _ayyay;
_cpBlock = ^NSString *(NSString *str){
return [NSString stringWithFormat:@"hello_%@",unsafe_unretainedArray[0]];
};
_cokong(@"hello");
因?yàn)槌钟衏pBlock的self一定存在,所以使用__unsafe_unretainedArray修飾符体啰,不必?fù)?dān)心懸垂指針攒巍。
(3)我們還可以通過__block來解決循環(huán)引用狡赐。如下代碼塊:
_block id blockSelf = self;
_cpBlock = ^int(NSString *str){
return blockSelf.a;
};
_cokong(@"hello");
其實(shí)上面的代碼在MRC下一點(diǎn)問題都沒有常柄,但是在ARC下存在問題。
用__block修飾self后,self持有cpBlock,cpBlock持有self相种,這樣會造成循環(huán)引用寝并。所以我們要在block內(nèi)部做一個斷環(huán)的操作衬潦,才能解決循環(huán)引用。如下代碼塊:
_block id blockSelf = self;
_cpBlock = ^int(NSString *str){
int a = blockSelf.a;
blockSelf = nil;//斷環(huán)
return blockSelf.a;
};
_cokong(@"hello");
但是其實(shí)漂羊,這樣做還是有點(diǎn)問題,因?yàn)槿绻覀冎皇嵌x了block买喧,而一直不去調(diào)用block,那么就永遠(yuǎn)不能斷環(huán)算柳,循環(huán)引用就一直存在。
總的來說: __block相比較于上面兩種方法囱淋,優(yōu)點(diǎn)在于,__block變量可以控制對象的持有時間税手。缺點(diǎn)在于為了避免循環(huán)引用艺挪,必須執(zhí)行block。