Block 的總結(jié)

要理解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;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,536評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內(nèi)容