讀書筆記(二)

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]);


strong

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了


改為weak
持有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下,則都是拷貝一份指針。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容