void (^blk) (void) = ^{pringf("block \n");};
這個block的其實就是編譯成一個函數指針void (*blk) (void) = &impl_0(func_0, &desc_0)
impl_0 func_0 desc_0都是結構體,impl_0的構造函數是以func_0,desc_0型變量為參數的。
blk(); 使用block其實就是調用函數指針,(*blk->impl.FuncPtr)(blk); (func_0以impl_0為參數)
block如何截取局部變量的值的?
在編譯的時候,如果block聲明前有局部變量,并在block內被使用了,在impl_0的構造函數的參數列表就有這個局部變量,并且使用局部變量的值來初始化;在impl_0的成員變量也添加了這些成員變量。
在block聲明前如果有靜態(tài)局部變量,
static int static_val = 3;
相當于在impl_0內增加了int *static_val成員變量,使用該變量時即是使用它的指針進行訪問。
為什么非靜態(tài)的局部變量沒有用這個方式來進行訪問呢,如果這樣就不存在block截獲局部變量的值的情況了。因為如果局部變量被截獲后(在block聲明處被截獲),如果該局部變量作用域結束,這個局部變量就被廢棄了,block中就不能再對它訪問了。
__block實現
__block int val = 0;
被編譯成:(括號里面是賦的值)
struct __Block_byref_val_0{
? ? void *__isa;(0)
? ? __Block_byref_val_0 * __forwarding;(&val)
? ? int __flags;(0)
? ? int __size;(__Block_byref_val_0)
? ? int val;(10)
}
block內對val賦值:
^{val = 1;}
在func_0內被編譯成,在impl_0中同樣會添加成員變量
{
? ? __Block_byref_val_0 *val = __cself->val;
? ? val->__forwarding->val = 1;
}
Block存儲域
一般block設置在棧上,_NSConcreteStackBlock。isa=_NSConcreteStackBlock
有兩種情況在global數據區(qū) 即isa=_NSConcreteGlobalBlock:
1、block聲明為全局變量
2、block中不截獲局部變量的值(執(zhí)行時不依賴局部變量的值,整個程序只需一個實例)
設置在棧上有個問題,就是聲明block時的變量作用域結束了,棧也就沒了,block和__block變量也都沒了,所以arc時,大多數情況下編譯器會自動生成將block從棧上復制到堆上的代碼來解決這個問題。
比如下面這個將block作為函數返回值:
blk_t func(int rate)
{
? ? return ^(int count){return rate * count;};
}
blk_t func(int rate)
{
? ? blk_t tmp = &_func_block_impl_0(func_0,desc_0,rate);
? ? tmp = _Block_copy(tmp);從棧復制到堆
? ? return objc_autoreleaseReturnValue(tmp); 注冊到autoreleasePool并返回
}
有些情況編譯器不能判斷:
向函數參數傳遞block(例外:函數里面已經復制了參數的就不用在函數前手動復制了,還有框架的某個方法名中有usingBlock,GCD的API里)
對block調用copy方法時,原來在棧上的block復制到堆上;原來在數據區(qū)的,什么也不做;原來在堆的,引用計數加1
那么block中使用的__block變量在block從棧復制到堆上又發(fā)生了什么?
同樣是從棧復制到堆,并依然被該block所持有。
__block變量在從棧復制到堆上的時候,將__forwarding指向堆上的__block變量,所以不論是訪問棧上的(在block外使用__block變量)還是訪問堆上的(block內使用__block變量),都能通過val.__forwarding->val來訪問。(訪問的都是堆上的??)
block持有截獲的局部變量(如果是weak類型呢??)
typedef void(^blk_t)(id obj);
blk_tblk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id b){
[array addObject:obj];
NSLog(@"array count %ld",[array count]);
}copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);

block在編譯后成員變量添加了id __strong array;
由于[block copy]的調用,多了__main_block_copy_0和__main_block_dispose_0方法,在__main_block_copy方法中調用__Block_object_assign將對象賦值給array成員變量并持有該對象,同理,__main_block_dispose_0方法中調用_Block_object_dispose釋放賦值給array的對象。
所以在大括號結束時,即便array已經超過變量作用域,堆上的block依然持有該對象,不能被廢棄。
block從棧賦值到堆有四種情況:
1、[block copy]
2、return block; block作為函數返回值
3、id strong blk = block; / self.blk = block;
4、xxxusingBlock(blk_t blk) / ?GCD的API用block做參數的
變成weak之后 發(fā)現block結構體中的成員變量也變成weak了


array2在出變量作用域之后設為nil,所以三次輸出都是0
可用聲明為weak來避免循環(huán)引用。
看一下block截獲基礎類型變量和對象類型變量的區(qū)別
__block int ?val = 10;
該變量轉換為結構體__Block_byref_val_0
同時轉換后的文件頭部多了___main_block_copy_0和___main_block_dispose_0這兩個函數定義,
這兩個函數內部調用了__Block_object_assign和_Block_object_dispose方法,
__Block_object_assign方法中讓Block持有__block變量
這兩個方法有個參數block_field_is_byref(綠色的)代表是assign了__block變量,如果是id類型,但是沒有__block修飾符,則這里的值是BLOCK_FIELD_IS_OBJECT。
具體的__Block_object_assign實現是這樣的:
這兩個函數在main中并沒有被調用,那么是何時進行了調用呢?
當block從棧復制到堆上的時候,調用__main_block_copy_0,__block變量也從棧復制到堆上時,并被Block所持有。

__block id obj = [[NSObject alloc] init];的情況
__Block_object_assign函數將__block變量賦值給block成員變量,strong類型變量持有對象,從而block持有該對象
只要__block變量在堆上存在,對象就會一直被持有。

__block id __strong obj = [[NSObject alloc] init]; ?和不帶__strong的情況一樣
總結一下就是:
ARC下,Block中引用id類型的數據有沒有__block都一樣都是retain,而對于基礎變量如int而言,沒有的話無法修改變量值,有的話就是修改其結構體令其內部的forwarding指針指向拷貝后的地址達到值的修改。而MRC下,則都是拷貝一份指針。