要理解Block的實(shí)現(xiàn),要先理解runtime,然而理解Runtime要先理解C語言的結(jié)構(gòu)體(可見我基礎(chǔ)是TM有多差),
如下:
什么是結(jié)構(gòu)體,結(jié)構(gòu)體是通過已存在的類型來組合生成的數(shù)據(jù)類型,定義它的格式為:
1.結(jié)構(gòu)體定義
struct 結(jié)構(gòu)體名
{
結(jié)構(gòu)體成員聲明列表;
(結(jié)構(gòu)體的成員,需要指明它的類型和名稱)
};(結(jié)構(gòu)體定義也是語句,所以也要加“;”號)
2.定義結(jié)構(gòu)體變量
三種定義格式
(1)struct 結(jié)構(gòu)體名 變量名
(2)定義結(jié)構(gòu)體的同時(shí)定義結(jié)構(gòu)體變量
如 struct Date {
int year,age,day;
}dt(變量名);
或
struct {? ? ? <-省去了結(jié)構(gòu)體名,直接定義結(jié)構(gòu)體變量
int year,age,day;
}dt(變量名);
也可以通過typedef機(jī)制,去掉struct 結(jié)構(gòu)體名!
typedef struct Date MyDate;
或
typedef struct { int year,age, day }MyDate;
3.結(jié)構(gòu)體變量初始化和賦值
Date dt = {2016,8,30};常量初始化
Date dt0 = dt;? ? ? ? ? ? ? ? ? ? ? ? 復(fù)制初始化
Date dt1;
dt1 = dt;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 復(fù)制賦值
dt1 = {2016,8,30};//非法
*幾種相關(guān)的數(shù)據(jù)類型及訪問結(jié)構(gòu)體成員的方式
結(jié)構(gòu)體成員的表示方式
結(jié)構(gòu)體變量名 . 結(jié)構(gòu)成員名 ( . 是成員引用運(yùn)算符)
結(jié)構(gòu)指針 -> 結(jié)構(gòu)成員名? ? ? ( -> 是箭頭運(yùn)算符)
(*結(jié)構(gòu)指針). 結(jié)構(gòu)成員名
結(jié)構(gòu)體數(shù)組中元素的結(jié)構(gòu)體成員表示方式:
結(jié)構(gòu)體數(shù)組名 [下標(biāo)]. 結(jié)構(gòu)體成員
(*(結(jié)構(gòu)體數(shù)組名+下標(biāo))). 結(jié)構(gòu)體成員
(結(jié)構(gòu)體數(shù)組名+下標(biāo))->結(jié)構(gòu)體成員
Block的實(shí)質(zhì)
我們會把代碼通過Clang命令轉(zhuǎn)換為中間代碼來觀察block的實(shí)現(xiàn),探索它的本質(zhì)。
打開終端,進(jìn)入項(xiàng)目路徑,然后敲入Clang的命令clang -rewrite-objc BlockClang.c。此時(shí),F(xiàn)inder里多了個(gè)文件BlockClang.cpp,它正是轉(zhuǎn)換后的中間代碼。(clang 命令可以將 Objetive-C 的源碼改寫成 C / C++ 語言的,借此可以研究 block 中各個(gè)特性的源碼實(shí)現(xiàn)方式。)
Block的實(shí)現(xiàn):
structBlock_layout {
void*isa;
intflags;
intreserved;
void(*invoke)(void*, ...);
structBlock_descriptor *descriptor;
/* Imported variables. */
};
structBlock_descriptor {
unsignedlongintreserved;
unsignedlongintsize;
void(*copy)(void*dst,void*src);
void(*dispose)(void*);
};
從結(jié)構(gòu)體中看到isa,所以O(shè)C處理Block是按照對象來處理的在iOS中,isa常見的就是_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock這3種。
Block捕捉外部變量
說到外部變量,我們要先說一下C語言中變量有哪幾種。一般可以分為一下5種:
自動變量
函數(shù)參數(shù) (非外部變量)
靜態(tài)變量
靜態(tài)全局變量
全局變量
我們先實(shí)驗(yàn)靜態(tài)變量,靜態(tài)全局變量,全局變量這3類。測試代碼如下:
#import
intglobal_i =1;//全局變量
staticintstatic_global_j =2;//靜態(tài)全局變量
intmain(intargc,constchar* argv[]) {
staticintstatic_k =3;//靜態(tài)變量
intval =4;//自動變量
void(^myBlock)(void) = ^{
global_i ++;
static_global_j ++;
static_k ++;
NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
};
global_i ++;
static_global_j ++;
static_k ++;
val ++;
NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
myBlock();
return0;
}
運(yùn)行結(jié)果
Block 外? global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block 中? global_i = 3,static_global_j = 4,static_k = 5,val = 4//val因?yàn)闆]有__block修飾
我們在看下C語言的源碼:
intglobal_i =1;
staticintstatic_global_j =2;
struct__main_block_impl_0 {
struct__block_impl impl;
struct__main_block_desc_0* Desc;
int*static_k;
intval;
__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,int*_static_k,int_val,intflags=0) : static_k(_static_k), val(_val) {
impl.isa= &_NSConcreteStackBlock;
impl.Flags= flags;
impl.FuncPtr= fp;
Desc = desc;
}
};
staticvoid__main_block_func_0(struct__main_block_impl_0 *__cself) {
int*static_k = __cself->static_k;// bound by copy
intval = __cself->val;// bound by copy
global_i ++;
static_global_j ++;
(*static_k) ++;
NSLog((NSString*)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
}
staticstruct__main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = {0,sizeof(struct__main_block_impl_0)};
intmain(intargc,constchar* argv[]) {
staticintstatic_k =3;
intval =4;
void(*myBlock)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
global_i ++;
static_global_j ++;
static_k ++;
val ++;
NSLog((NSString*)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);
((void(*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return0;
}
我們看到
在__main_block_impl_0中,可以看到靜態(tài)變量static_k和自動變量val,被Block從外面捕獲進(jìn)來,成為__main_block_impl_0這個(gè)結(jié)構(gòu)體的成員變量了。
再看下面這個(gè)構(gòu)造函數(shù)
__main_block_impl_0( *fp,? __main_block_desc_0 *desc,int*_static_k,int_val,intflags=0) : static_k(_static_k), val(_val)
這個(gè)構(gòu)造函數(shù)中,自動變量和靜態(tài)變量被捕獲為成員變量追加到了構(gòu)造函數(shù)中。
我們還注意到
&static_k 靜態(tài)變量傳入的是地址
所以值也是被改變的
要明確,block外不會有很多變量,但只有是block內(nèi)部用到的變量才會被block捕獲,創(chuàng)建相應(yīng)的結(jié)構(gòu)體內(nèi)部變量。
我們可以發(fā)現(xiàn),系統(tǒng)自動給我們加上的注釋,bound by copy,自動變量val雖然被捕獲進(jìn)來了,但是是用 __cself->val來訪問的。Block僅僅捕獲了val的值,并沒有捕獲val的內(nèi)存地址。所以在__main_block_func_0這個(gè)函數(shù)中即使我們重寫這個(gè)自動變量val的值,依舊沒法去改變Block外面自動變量val的值。
自動變量(局部變量)是以值傳遞方式傳遞到Block的構(gòu)造函數(shù)里面去的。Block只捕獲Block中會用到的變量。由于只捕獲了自動變量的值,并內(nèi)存地址,所以Block內(nèi)部不能改變自動變量的值。
我們繼續(xù)分析 靜態(tài)變量,全局變量和靜態(tài)全局變量被改變的原因:
靜態(tài)全局變量,全局變量由于作用域的原因,于是可以直接在Block里面被改變。他們也都存儲在全局區(qū)。
靜態(tài)變量傳遞給Block是內(nèi)存地址值,所以能在Block里面直接改變值。
總結(jié)一下在Block中改變變量值有2種方式,一是傳遞內(nèi)存地址指針到Block中,二是改變存儲區(qū)方式(__block)。
OK下面在分析下Block的Copy問題
OC中,一般Block就分為以下3種,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。
1.從捕獲外部變量的角度上來看
_NSConcreteStackBlock:
只用到外部局部變量、成員屬性變量,且沒有強(qiáng)指針引用的block都是StackBlock。
StackBlock的生命周期由系統(tǒng)控制的,一旦返回之后,就被系統(tǒng)銷毀了。
_NSConcreteMallocBlock:有強(qiáng)指針引用或copy修飾的成員屬性引用的block會被復(fù)制一份到堆中成為MallocBlock,沒有強(qiáng)指針引用即銷毀,生命周期由程序員控制
_NSConcreteGlobalBlock:沒有用到外界變量或只用到全局變量、靜態(tài)變量的block為_NSConcreteGlobalBlock,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束。
沒有用到外部變量肯定是_NSConcreteGlobalBlock,這點(diǎn)很好理解。不過只用到全局變量、靜態(tài)變量的block也是_NSConcreteGlobalBlock。
只用到全局變量、靜態(tài)變量的block也可以是_NSConcreteGlobalBlock。
2.從持有對象的角度上來看:
_NSConcreteStackBlock是不持有對象的。
_NSConcreteMallocBlock是持有對象的。
_NSConcreteGlobalBlock也不持有對象
以下4種情況,系統(tǒng)都會默認(rèn)調(diào)用copy方法把Block賦復(fù)制
1.手動調(diào)用copy
2.Block是函數(shù)的返回值
3.Block被強(qiáng)引用,Block被賦值給__strong或者id類型
4.調(diào)用系統(tǒng)API入?yún)⒅泻衭singBlcok的方法
__block 的實(shí)現(xiàn)
帶有__block的變量被轉(zhuǎn)化為一個(gè)結(jié)構(gòu)體__Block_byref_i_0,這個(gè)結(jié)構(gòu)體有5個(gè)成員變量,第一個(gè)是isa指針,第二個(gè)是指向自身類型的__forwarding指針,第三個(gè)是一個(gè)標(biāo)記flag,第四個(gè)是它的大小,第五個(gè)是變量值,名字和變量名同名。
MRC
Block在捕獲住__block變量之后,并不會復(fù)制到堆上,所以地址也一直都在棧上。這與ARC環(huán)境下的不一樣。
ARC
ARC環(huán)境下,一旦Block賦值就會觸發(fā)copy,__block就會copy到堆上,Block也是__NSMallocBlock。ARC環(huán)境下也是存在__NSStackBlock的時(shí)候,這種情況下,__block就在棧上。
__forwarding 在棧上和堆上的作用不同
__forwarding指針這里的作用就是針對堆的Block,把原來__forwarding指針指向自己,換成指向_NSConcreteMallocBlock上復(fù)制之后的__block自己。然后堆上的變量的__forwarding再指向自己。這樣不管__block怎么復(fù)制到堆上,還是在棧上,都可以通過(i->__forwarding->i)來訪問到變量值。
然而當(dāng)Block在MRC下時(shí)候,我們不手動Copy的話,Block 依然在棧上,這時(shí)__forwarding 指針就只指向自己了。
Block 循環(huán)應(yīng)用問題
(嚴(yán)格的來說,捕獲是必須在Block結(jié)構(gòu)體__main_block_impl_0里面有成員變量的話,Block能捕獲的變量就只有帶有自動變量和靜態(tài)變量了。)
帶__block的自動變量 和 靜態(tài)變量 就是直接地址訪問。所以在Block里面可以直接改變變量的值。
靜態(tài)全局變量,全局變量,函數(shù)參數(shù)他們并不會被Block持有,也就是說不會增加retainCount值。
@interface AViewController:UIViewController
{
void (^ myBlock)(id myObj);
id _obj;
}
@end
@implementation AViewController
- (id)init {
if (self = [super init]){
__weak AviewController *weakSelf = self;
myBlock = ^{ NSLog(@“ Who is Block retainer : %@ %@“,weakSelf, _obj) };
}
}
- (void)dealloc {
NSLog (@“ AVcdealloc”);
}
@end
@implementation BViewController
- (void )viewDidLoad {
id obj = [AViewcontroller new];
NSLog(@“What is obj %@“, obj);
}
//此處 在Block語法內(nèi)使用 _obj 實(shí)際上截獲了self,因?yàn)閷τ诰幾g器來說_obj只不過是對象結(jié)構(gòu)體的成員變量。
一般我們使用__weak 來避免循環(huán)引用,其實(shí)我們也可以使用__block 來解決 但是在block 內(nèi)部一定要對__block變量賦值nil,以消除block變量對 對象的引用
(對象引用Block,block變量引用對象同時(shí)引用Block)
使用__block 有三個(gè)優(yōu)點(diǎn)
1.通過block變量可以控制對象的持有期間。
2.在不能使用__weak修飾符的環(huán)境中,不使用__unsafe_unretained修飾符也可以。不用擔(dān)心懸垂指針。
(在執(zhí)行block時(shí)可以動態(tài)地決定是否將nil或者其他對象愛哪個(gè)賦值在block變量中。)
缺點(diǎn):
1.為避免循環(huán)引用必須執(zhí)行Block;