Block

Block


1.Block的定義和語法
2.Block的本質和分類
3.__block的實現原理

Block的定義和語法

Block是具有自動變量(局部變量)匿名函數

自動變量

因為局部變量的作用域不是全局的,有的時候就需要對變量的值進行保存,而Block可以捕獲變量,不需要使用全局變量或者靜態變量,更不需要聲明一個類去保存變量的值.

匿名函數

沒有名字的函數,由{}包裹起來,限制了其作用域
Block和函數指針的對比

//定義一個函數
int square(int value){
return value * value;
}

//函數指針
int (*funcptr)(int) = □
int result = (*funcptr)(10);

//Block  ()可以省略
^(){
NSLog(@"匿名函數");
}();

使用函數指針,當對函數指針進行賦值的時候還是需要知道函數的名字,才能知道函數的地址.

語法

^ 返回值類型 (參數列表){函數執行體||表達式};
^ 代表這是一個Block標記,便于查找

^int (int num){
return num * num;
};
//最簡單的Block,沒有返回值,沒有參數列表可以省略
^{
printf("Block\n");
};

Block類型變量

語法:返回值類型 (^變量名) 參數列表
等同于函數指針的聲明,將*換成^

//returnType (^BlockName)(type name....)
int (^blocks)(int value) = ^(int a){
return a * a;
};

block可以用作變量,函數參數,函數返回參數,結合typedef的使用更加方便

//定義int (^) int類型的Block 名字為Add
typedef int(^Add)(int num);

//作為函數返回值
Add Test(int num){
return ^int (int a){
   //這里對num變量進行了捕獲
  return num + a;
  };
}
//打印 15
NSLog(@"%d",Test(5)(10));

Block不允許修改局部變量的值
為什么?因為靜態變量存在于應用程序的整個生命周期,而非靜態局部變量,僅僅是存在于一個局部的上下文中。如果block執行過程中其所指向的非靜態局部變量還沒有被?;厥盏脑?,這樣執行沒有任何問題,但是絕大多數情況下,block都是延后執行的,修改已經被回收的值很可能拋出指針異常。

其實block對局部變量進行捕獲,會在block體內({}內)新定義一個變量,并進行賦值,所以在外部進行值的修改,對內部無任何影響,因為是兩個不同的變量
同樣當我們在Block內部修改外部變量的值,編譯器會報出錯,提示使用__block修飾符

__block 所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內存地址放到了堆中。進而在block內部也可以修改外部變量的值。

int num = 2;
NSLog(@"%d,%p",num,&num);
void (^block)() = ^{
NSLog(@"%d,%p",num,&num);
};
num = 3;
block();

//打印結果:
//2,0x7ffeefbff62c
//2,0x102803880
//在block捕獲之后對變量進行了改變,但是block打印的內容還是之前的值

使用__block修飾符

__block int num = 2;
NSLog(@"%d,%p",num,&num);
void (^block)() = ^{
NSLog(@"%d,%p",num,&num);
};
num = 3;
block();

//打印結果:
//2,0x7ffeefbff628
//3,0x10058ce38
//在block捕獲之后對變量進行了改變,block打印的內容也發生了改變

表面上來看,默認block捕獲變量是進行的值傳遞,使用了__block之后,傳遞的是地址,但是這種說法是不正確的,我們可以看到無論是否使用__block修飾符,在block內部和外部打印變量的地址是完全不相同的,地址的差別很大,而局部變量是存放在棧區的,可以推斷后面的變量是在堆區域的.而在ARC下,當對Block進行賦值,系統會自動的將block拷貝的堆區,下面我們介紹Block的本質和分類之后再進行剖析.

Block的本質和分類

定義一個Block,通過NSLog進行打印

void (^globalBlock)() = ^{
   NSLog(@"globalBlock");
};
NSLog(@"%@",globalBlock);

打印結果:<NSGlobalBlock: 0x1000020d0>,和打印對象的結果一樣,前面是類名,后面是對象地址.
這是一個未捕獲任何變量的Block,我們可以聲明一個捕獲變量的Block來查看結果是否一致

int num = 2;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",block);
block();

打印結果:<NSMallocBlock: 0x1004028a0> 我們知道在ARC下,只要對Block進行賦值,就會將Block復制到堆上,那么我們定義一個不賦值的Block進行查看

int a = 8;
NSLog(@"%@",^{
  NSLog(@"%d",a);
});

打印結果:<NSStackBlock: 0x7ffeefbff608>
通過打印結果我們可以猜測Block的本質就是一個對象,所屬不同的類

事實勝于雄辯,我們通過編譯器指令來查看底層轉換代碼來一探究竟
clang 是 Objective-C 的編譯器前端,用它可以將 Objective-C 代碼轉換為 C/C++ 代碼,然后可以來分析 Objective-C 中的一些特性是怎么實現的。
clang -rewrite-objc main.m -o main.cpp
指定架構模式進行轉換
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc

void (^test)(void) = ^{

};
test();

轉換后的代碼,只提取關鍵部分

struct __block_impl {
//isa 指向block所屬的類 ->block的本質是一個對象
void *isa;
//標識位
int Flags;
//預留字段
int Reserved;
//函數指針
void *FuncPtr;
};


struct __main_block_impl_0 {
//block實現
struct __block_impl impl;
//block描述
struct __main_block_desc_0* Desc;
//構造函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

//函數的實現 參數為創建block生成的結構體
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

}

static struct __main_block_desc_0 {
//預留字段
size_t reserved;
//大小
size_t Block_size;
}
//生成block_desc結構體對象
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};


int main(int argc, const char * argv[]) {

//函數指針  調用構造函數,傳入函數指針,block_desc對象,強轉為函數指針類型
void (*test)(void) = (
(void (*)())
&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
)
);

//調用函數的實現,傳入test指針(實際為__main_block_impl_0類型)當做參數
((void (*)(__block_impl *))
((__block_impl *)test)->FuncPtr
)((__block_impl *)test);

}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

當block捕獲局部變量時

int value = 10;
void (^test)(void) = ^{
NSLog(@"%d",value);
};
test();

轉換后

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//捕獲的變量
int value;
//構造函數  :后面是初始化列表 這里相當于value = _value;
//初始化類的成員有兩種方式,一是使用初始化列表,二是在構造函數體內進行賦值操作。
//初始化列表性能更好,對于內置類型,如int, float等,使用初始化類表和在構造函數體內初始化差別不是很大,但是對于類類型來說,使用初始化列表更加高效
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

//取出變量值 打印
int value = __cself->value; // bound by copy

//NSLog....
}

static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

int value = 10;
void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value));

((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);

}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

只是block內部多了一個捕獲變量類型的成員變量.
總結:

  • _NSConcreteStackBlock:
    只用到外部局部變量、成員屬性變量,且沒有強指針引用的block都是StackBlock。
    StackBlock的生命周期由系統控制的,一旦返回之后,就被系統銷毀了。

  • _NSConcreteMallocBlock:
    有強指針引用或copy修飾的成員屬性引用的block會被復制一份到堆中成為MallocBlock,沒有強指針引用即銷毀,生命周期由程序員控制

  • _NSConcreteGlobalBlock:
    沒有用到外界變量或只用到全局變量、靜態變量的block為_NSConcreteGlobalBlock,生命周期從創建到應用程序結束。

__block的實現原理

__block int a = 0;
void (^block)(void) = ^{
a++;
NSLog(@"%d",a);
};
block();

轉換后的代碼


struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

//__block
struct __Block_byref_a_0 {
void *__isa;
/**重要**/
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
//變量
int a;
};

struct __main_block_impl_0 {
//block
struct __block_impl impl;
//描述變量
struct __main_block_desc_0* Desc;
//__block修飾生成的結構體
__Block_byref_a_0 *a; // by ref

//構造函數
//a 的值是 a->__forwarding
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

//函數實現
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

__Block_byref_a_0 *a = __cself->a; // bound by ref

NSLog((NSString *)&__NSConstantStringImpl__var_folders_cm_kv9bb84x62b8y9g8ml72bc3w0000gn_T_main_a5279b_mi_0,(a->__forwarding->a));
}

//Copy操作  第一個為目標對象 第二個參數為原來的對象
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src){
_Block_object_assign((void*)&dst->a,
(void*)src->a,
8/*BLOCK_FIELD_IS_BYREF*/);

}

//Dispose
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->a,
8/*BLOCK_FIELD_IS_BYREF*/);

}

//Block描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}

//block描述的初始化方法
__main_block_desc_0_DATA = { 0,
//大小計算,僅僅是進行了sizeof
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};


int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {

__AtAutoreleasePool __autoreleasepool;

//__block定義一個變量,實際上就是生成了一個__Block_byref_xx_0類型的結構體變量
//結構體有5個成員變量。第一個是isa指針,第二個是指向自身類型的__forwarding指針,第三個是一個標記flag,第四個是它的大小,第五個是變量值,名字和變量名同名。
// 傳入的參數分別是:
// isa: void *0;
// __forwarding: &a,即a結構體變量的地址
// __flags: 0
// __size: sizeof(結構體)
// a: 0,即a的值


//__forwarding指針初始化傳遞的是自己的地址
__attribute__((__blocks__(byref)))
__Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
0};


void (*block)(void);
//Block的定義
block = ((void (*)())&__main_block_impl_0(//函數實現
(void *)__main_block_func_0,
//描述block函數函數
&__main_block_desc_0_DATA,
//__block_byref__
(__Block_byref_a_0 *)&a,570425344)
);

//a++
(a.__forwarding->a)++;


//拿到FuncPtr函數指針,FuncPtr函數有一個參數,即傳入的block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

///

棧上的Block會持有__block對象,把Block復制到堆上,堆上也會重新復制一份__block,并且該Block也會繼續持有該__block。當Block釋放的時候,__block沒有被任何對象引用,也會被釋放銷毀。


__block

__block變量是在??臻g,其forwarding指向自身,當變量從??臻gcopy到堆空間時,原來??臻g的變量的_forwarding指向了新創建的變量(堆空間上),這其實就達到了從Objective C層面改變原變量的效果,這樣不管__block怎么復制到堆上,還是在棧上,都可以通過(i->__forwarding->i)來訪問到變量值。

參考:
深入研究Block捕獲外部變量和__block實現原理

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

推薦閱讀更多精彩內容