深究Block的實現

先看一下Block使用的語法

聲明一個block
返回值 (^名稱)(參數列表) = ^(參數列表){
};

 int (^name)(int ,int) = ^(int a,int b){
    return (a+b);
};

作為一個函數的參數:
- (void)testBlock:(NSString *(返回類型) (^)(int a))s(block名字) {
NSString *a = s(1);
}
[self testBlock:^NSString *(int a) {
a = 5;
return @"1";
}];

然后通過底層代碼分析一下block的實現

iOS中有三種block,下文會細說
NSConcreteGlobalBlock;//在全局中定義的
NSConcreteStackBlock; //在局部定義的
NSConcreteMallocBlock;//分配在堆中

先從最簡單的看起

int main(int argc, char * argv[]) {
void (^block)(void) = ^{
    NSLog(@"1");
};

(通過 在終端找到這個.m文件,然后clang -rewrite-objc 代碼文件名 就可以看到文件夾有個.cpp的文件,本來想變量ViewController的文件,里面用了UIKit庫,編譯的時候總是顯示找不到,于是我編譯的main.m文件)

編譯過來是

block源碼

這里先來分析一下(便于理解):

static void _main_block_func_0(struct _main_block_impl_0 *__cself) {
 NSLog((NSString *)&__NSConstantStringImpl__var_
folders_yq_s_hjnhd12x79wq1ldg1jdr_w0000gn_T_main_50d1d6_mi_0);
}

可以看到,這里對應我們代碼中的block中的實現,所以可以知道,block使用的匿名函數,實際上被當作一個函數來處理。不過傳入的是:一個_main_block_impl_0類型的結構體,里面有一個block_impl的結構體,和一個_main_block_desc_0的結構體。跟著是他們的構造函數。
來簡單看一下這個_main_block_impl_0結構體吧:

isa指向這個block的類型。這里說明這個block是NSConcreteStackBlock類型的。
flag是標志,可以看到,默認構造為0;
還有一個FuncPtr,也就是指向函數地址的指針。
還有一個__main_block_desc_0的結構體,
在下面可以看到這個結構體的初始化,
一個是reserverd默認為0,
一個是block_size。是這個impl的size。
所以,這個_main_block_impl_0,我們可以理解為就是一個block實例,里面的成員變量有要執行的函數的指針,和isa(和所有的oc對象一樣),還有一個size。

現在看一下main函數里面的內容

    void (*name)(void) = ((void (*)())&__main_block_impl_0((void 
*)__main_block_func_0, &__main_block_desc_0_DATA));

這里執行的操作就是:初始化一個block實例,交給我們這么name名字變量,也就是用name這個指針指向這個block實例,執行的時候,直接找到這個block中的指向函數地址的指針。

通過以上可以了解:block的實質,就是一個對象,包含了一個指向函數首地址的指針,和一些與自己相關的成員變量。

接著看一下block訪問外部變量是怎么回事,這也是我們最關心的問題。

先來看一下局部變量:

int a = 10;
int b = 20;
void (^block)() = ^ {
    NSLog(@"%d--%d",a,b);
};
block ();
block訪問局部變量

首先在_main_block_impl_0的定義中看到,block中用到的變量被作為成員變量追加到了結構體中,

 _main_block_impl_0(void *fp, struct _main_block_desc_0 *desc, 
int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;

}
在初始化的時候,也需要把a和b的值傳入。

 void (**block)() = ((void (*)())&_main_block_impl_0((void *)
_main_block_func_0, &_main_block_desc_0_DATA, a, b));

這里看到,在初始化的時候,把a,b值傳入來初始化一個_main_block_impl_0結構體。這里需要注意,傳入的是a和b的值,所以我們要在結構體中改變a和b是無效的。并且編譯會報錯,如果要在block中改變里面的值。有以下幾種方法

1,靜態變量,全局變量

直接上代碼吧,嘻嘻

int global_val  = 1;
static int static_global_val = 2;
int main(int argc, char * argv[]) {
static int static_val = 3;
void (^block)() = ^ {
    global_val = 10;
    static_global_val = 20;
    static_val = 30;
};
NSLog(@"%d-%d-%d",global_val,static_global_val,static_val);
block ();
NSLog(@"%d-%d-%d",global_val,static_global_val,static_val);
}

然后rewrite一下


訪問全局變量

因為這個_main_block_func_0能直接訪問靜態變量,所以可以直接訪問這個變量的值,也可以改變,不用擔心在調用這個函數的時候,全局變量訪問不到,導致錯誤。所以這里面在調用全局變量的時候,就是很普通的調用全局變量,沒有什么不同。


局部靜態變量則傳的是一個指針進來。因為不用擔心它在函數結束時或者其他什么地方被釋放,所以可以放心的訪問這個值

  global_val = 10;
    static_global_val = 20;
    (*static_val) = 30;

改變的是這個指針指向的值

void (*block)() = ((void (*)())&__main_block_impl_0((void *)
_main_block_func_0, &__main_block_desc_0_DATA, &static_val));

可以看到,這里傳的是指針。
_main_block_func_0的作用域在main函數之外,要訪問這個變量,就只能傳指針。

第二種方法是給參數加__block屬性
int main(int argc, char * argv[]) {
__block int block_val = 3;
void (^block)() = ^ {       
 block_val = 30;
};
NSLog(@"%d",block_val);
block ();
NSLog(@"%d",block_val);
NSLog(@"%@",block);

}

__block參數

可以看到里面比其他多了一個這樣的結構體_Block_byref_block_val_0
里面有:
_isa初始化為0,
__forwarding;//持有該實例自身的的指針
int __flags;為0
int __s__Block_byref_block_val_0ize;size
int block_val;//存放這個變量的值
原來是把一個局部變量,封裝成了一個結構體
賦值的時候直接給這個結構體中的這個值賦值
(block_val->__forwarding->block_val) = 30;

所以,在訪問這個變量的時候,其實在訪問這個結構體的這個變量。

關于_forwarding的作用請不要著急。

Block有三種類型:

NSConcreteGlobalBlock;//在全局中定義的
NSConcreteStackBlock; //在局部定義的
NSConcreteMallocBlock;//分配在堆中

設置在棧上的block,當“name這個名字變量”作用域結束時,block變量也會廢棄。
所以,iOS提供了將block結構體和_block變量,復制到堆上的方法。即使block的name變量結束,那么堆上的block還可以繼續訪問。
而此時,_block變量結構體中的_forwarding變量可以實現,無論在堆上還是在棧上。都可以正確訪問_block變量。可以理解,當把_block變量復制到堆上的時候,_forwarding就指向堆里中的自己。所以無論是訪問棧中自己,還是堆中的自己,最終訪問都是堆中的這個值。

一個Block對_block的內存管理方式與 ARC機制完全相同。
而_main_block_desc 中的copy和dispose就是這個 __block的retain和release操作。

那什么block在時候會復制到堆呢
1,掉用block的copy方法,
2.block作為函數返回值返回時。
3,block調用外面的_strong的id的類時,或用_block時。
4,方法中,用usingblock或者GCD中的API時。

這里想講一下,在局部函數里,定義block時,打印出來還是NSConcreteGlobalBlock類型的,而且只要用了外部變量,不管是assign還是week還是strong類型的,打印出來都是NSConcreteMallocBlock類型的。所以我猜測這會不會是蘋果新版的改進,為了block在訪問無效的變量,直接把block拷貝到堆上,從而也拷貝一份變量。或許是我忽略了中間的某個步驟


其實到了這里,不用再描述,也知道為什么會發送死循環,又怎么解決了。當在block中用self的時候,block拷貝到堆上,首先,在棧上的這個block有一個持有者,是name這個變量。當name這個變量作用域之外,棧上這個block就release了。,那么當block拷貝到堆上的時候,block有一個持有者是self,那么block在拷貝時,它的變量一個self指針,也會拷貝,而self又指向這個block,block持有self,self持有block,兩者都不會釋放。要打破這個循環,需要將self置為__week,就算拷貝一個week指針,那也不影響self的引用計數。

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

推薦閱讀更多精彩內容