iOS開發(fā)---Block詳解
Block的基礎
什么是Blocks蔑穴?
- 用一句話來描述:帶有自動變量的匿名函數(shù)(是不是一臉懵逼,不要擔心惧浴,整篇博客都會圍繞這句話展開)顧名思義:
Block
沒有函數(shù)名澎剥,另外Block
帶有"^
"標記,插入記號便于查找到Block
-
Blocks
也被稱作閉包、代碼塊赶舆。展開來講,Blocks
就是一個代碼塊祭饭,把你想要執(zhí)行的代碼封裝在這個代碼塊里芜茵,等到需要的時候再去調(diào)用。 -
Block
共享局部作用域的數(shù)據(jù)倡蝙。Block
的這項特征非常有用九串,因為如果您實現(xiàn)一個方法,并且該方法定義一個塊寺鸥,則該塊可以訪問該方法的局部變量和參數(shù)(包括堆棧變量)猪钮,以及函數(shù)和全局變量(包括實例變量)。這種訪問是只讀的胆建,但如果使用__block
修飾符聲明變量烤低,則可在Block
內(nèi)更改其值。即使包含有塊的方法或函數(shù)已返回笆载,并且其局部作用范圍已銷毀扑馁,但是只要存在對該塊的引用,局部變量仍作為塊對象的一部分繼續(xù)存在凉驻。
Blocks的語法
-
Block
的完整語法格式如下:
^ returnType (argument list) {
expressions
}
用一張圖來表示
- 也可以寫省略格式的
Block
,比如省略返回值類型:
^ (int x) {
return x;
}
Block
省略返回值類型時腻要,如果表達式中有return語句就使用該返回值的類型,沒有return語句就使用void
類型雄家。
- 如果沒有參數(shù)列表,也可以省略參數(shù)列表:
^ {
NSLog(@"hello world");
}
- 與c語言的區(qū)別
- 沒有函數(shù)名
- 帶有"^"符號
Block類型變量
-
Block
類型變量與一般的C
語言變量完全相同胀滚,可以作為自動變量趟济,函數(shù)參數(shù)乱投,靜態(tài)變量,全局變量使用 - C語言函數(shù)將是如何將所定義的函數(shù)的地址賦值給函數(shù)指針類型變量中
int func (int count)
{
return count + 1;
}
int (*funcptr) (int) = &func;
- 使用
Block
語法就相當于生成了可賦值給Block
類型變量的值咙好。
//Blocks 變量聲明與賦值
int (^blk) (int) = ^int (int count) {
return count + 1;
};
//把Block語法生成的值賦值給Block類型變量
int (^myBlock)(int) = blk;
與前面的使用函數(shù)指針的源代碼對比可知篡腌,聲明
Block
類型變量僅僅是將聲明函數(shù)指針類型變量的""變?yōu)?“^”*
- 在函數(shù)返回值中指定
Block
類型,可以將Block
作為函數(shù)的返回值返回勾效。
int (^func()(int)) {
return ^(int count){
return count + 1;
}
}
Block在oc中的使用
- 通過
property
聲明成員變量:@property (nonatomic, copy) 返回值類型 (^變量名) (參數(shù)列表);
@property (nonatomic, copy) void (^callBack) (NSString *);
- (void)useBlock {
self.callBack = ^ (NSString *str){
NSLog(@"useBlock %@", str);
};
self.callBack(@"hello world");
}
- 作為方法參數(shù):
- (void)someMethodThatTaksesABlock:(返回值類型 (^)(參數(shù)列表)) 變量名;
- (void)callBackAsAParameter:(void (^)(NSString *print))callBack {
callBack(@"i am alone");
}
//調(diào)用該函數(shù)
[self callbackAsAParameter:^(NSString *print) {
NSLog(@"here is %@",print);
}];
- 通過typedef定義變量類型
//typedef 返回值類型 (^聲明名稱)(參數(shù)列表);
//聲明名稱 變量名 = ^返回值類型(參數(shù)列表) { 表達式 };
typedef void (^callBlock)(NSSting *);
callBlock block = ^(NSString *str) {
NSLog(@"%@", str);
}
與上一個知識點中指定Block為函數(shù)返回值對比
//原來的記述方式
void func(void (^blk)(NSString 8))
//用了 typedef 定義后的記述方式
void func(callBlock blk)
//原來的記述方式
void (^func()(NSString *))
//用了 typedef 定義后的記述方式
callBlock func()
Block截取變量
截獲自動變量的值
- 我們先看一個??
// 使用 Blocks 截獲局部變量值
- (void)useBlockInterceptLocalVariables {
int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
printf("a = %d, b = %d\n",a, b);
};
myLocalBlock(); // 打印結果:a = 10, b = 20
a = 20;
b = 30;
myLocalBlock(); // 打印結果:a = 10, b = 20
}
為什么兩次打印結果都是 a = 10, b = 20
嘹悼?
明明在第一次調(diào)用 myLocalBlock();
之后已經(jīng)重新給變量 a、變量 b 賦值了层宫,為什么第二次調(diào)用 myLocalBlock();
的時候杨伙,使用的還是之前對應變量的值?
因為 Block
語法的表達式使用的是它之前聲明的局部變量a
萌腿、變量 b
限匣。Blocks
中,Block
表達式截獲所使用的局部變量的值毁菱,保存了該變量的瞬時值米死。所以在第二次執(zhí)行 Block
表達式時,即使已經(jīng)改變了局部變量 a
和 b
的值贮庞,也不會影響 Block
表達式在執(zhí)行時所保存的局部變量的瞬時值峦筒。
這就是 Blocks
變量截獲局部變量值的特性。
通過__block說明符賦值
- 上面我們想修改變量
a
窗慎,變量b
的值物喷,但是有沒有成功,那么我們難道就沒有方法來修改了么遮斥?別急峦失,__block
來也,只要用這個說明符修飾變量术吗,就可以在塊中修改尉辑。
// 使用 __block 說明符修飾,更改局部變量值
- (void)useBlockQualifierChangeLocalVariables {
__block int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
a = 20;
b = 30;
printf("a = %d, b = %d\n",a, b); // 打印結果:a = 20, b = 30
};
myLocalBlock();
}
可以看到较屿,使用__block
說明符修飾之后材蹬,我們在 Block
表達式中,成功的修改了局部變量值吝镣。
使用__block
修飾符的自動變量被稱為__blcok
變量
Block的實現(xiàn)
在上一部分我們知道了
Blocks
是 帶有局部變量的匿名函數(shù)堤器。但是 Block 的實質(zhì)究竟是什么呢?類型末贾?變量闸溃?要想了解 Block 的本質(zhì),就需要從 Block 對應的 C++ 源碼來入手。
下面我們通過一步步的源碼剖析來了解 Block 的本質(zhì)辉川。
Block的實質(zhì)是什么表蝙?
準備工作
- 在項目中添加
blocks.m
文件,并寫好block
的相關代碼乓旗。 - 打開『終端』府蛇,執(zhí)行
cd XXX/XXX
命令,其中XXX/XXX
為block.m
所在的目錄屿愚。 - 繼續(xù)執(zhí)行
clang -rewrite-objc block.m
- 執(zhí)行完命令之后汇跨,
block.m
所在目錄下就會生成一個block.cpp
文件,這就是我們需要的block
相關的C++
源碼妆距。
Block源碼預覽
- 轉(zhuǎn)換前OC代碼:
int main () {
void (^myBlock)(void) = ^{
printf("myBlock\n");
};
myBlock();
return 0;
}
- 轉(zhuǎn)換后c++代碼:
/* 包含 Block 實際函數(shù)指針的結構體 */
struct __block_impl {
void *isa;
int Flags;
int Reserved; // 今后版本升級所需的區(qū)域大小
void *FuncPtr; // 函數(shù)指針
};
/* Block 結構體 */
struct __main_block_impl_0 {
// impl:Block 的實際函數(shù)指針穷遂,指向包含 Block 主體部分的 __main_block_func_0 結構體
struct __block_impl impl;
// Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結構體
struct __main_block_desc_0* Desc;
// __main_block_impl_0:Block 構造函數(shù)
__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) {
printf("myBlock\n");
}
/* Block 附加信息結構體:包含今后版本升級所需區(qū)域大小娱据,Block 的大小*/
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升級所需區(qū)域大小
size_t Block_size; // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
/* main 函數(shù) */
int main () {
//myBlock的初始化
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//myBlock的調(diào)用
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
下面我們一步一步來拆解轉(zhuǎn)換后的源碼
Block結構體
- 我們先來看看
__main_block_impl_0
結構體( Block 結構體)
/* Block 結構體 */
struct __main_block_impl_0 {
// impl:Block 的實際函數(shù)指針蚪黑,指向包含 Block 主體部分的 __main_block_func_0 結構體
struct __block_impl impl;
// Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結構體
struct __main_block_desc_0* Desc;
// __main_block_impl_0:Block 構造函數(shù)
__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;
}
};
從上邊我們可以看出中剩,__main_block_impl_0
結構體(Block 結構體)包含了三個部分:
- 成員變量
impl
; - 成員變量
Desc
指針; -
__main_block_impl_0
構造函數(shù)忌穿。
struct __block_impl
結構
我們先看看第一部分
impl
是__block_impl
結構體類型的成員變量
/* 包含 Block 實際函數(shù)指針的結構體 */
struct __block_impl {
void *isa; // 用于保存 Block 結構體的實例指針
int Flags; // 標志位
int Reserved; // 今后版本升級所需的區(qū)域大小
void *FuncPtr; // 函數(shù)指針
};
-
__block_impl
包含了 Block 實際函數(shù)指針FuncPtr
,FuncPtr
指針指向 Block 的主體部分结啼,也就是 Block 對應 OC 代碼中的^{ printf("myBlock\n"); };
部分掠剑。 - 還包含了標志位
Flags
,在實現(xiàn)block
的內(nèi)部操作時會用到 - 今后版本升級所需的區(qū)域大小
Reserved
-
__block_impl
結構體的實例指針isa
妆棒。
static struct __main_block_desc_0
結構
第二部分 Desc 是指向的是
__main_block_desc_0
類型的結構體的指針型成員變量,__main_block_desc_0
結構體用來描述該 Block 的相關附加信息:
/* Block 附加信息結構體:包含今后版本升級所需區(qū)域大小沸伏,Block 的大小*/
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升級所需區(qū)域大小
size_t Block_size; // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_impl_0
構造函數(shù)
第三部分是
__main_block_impl_0
結構體(Block 結構體) 的構造函數(shù)糕珊,負責初始化__main_block_impl_0
結構體(Block 結構體) 的成員變量。
__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;
}
- 關于結構體構造函數(shù)中對各個成員變量的賦值毅糟,我們需要先來看看
main()
函數(shù)中红选,對該構造函數(shù)的調(diào)用。
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
- 我們可以把上面的代碼稍微轉(zhuǎn)換一下姆另,去掉不同類型之間的轉(zhuǎn)換喇肋,使之簡潔一點:
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;
- 這樣,就容易看懂了迹辐。該代碼將通過
__main_block_impl_0
構造函數(shù)蝶防,生成的__main_block_impl_0
結構體(Block 結構體)類型實例的指針,賦值給__main_block_impl_0
結構體(Block 結構體)類型的指針變量myBlock
明吩。
可以看到间学, 調(diào)用 __main_block_impl_0
構造函數(shù)的時候,傳入了兩個參數(shù)。
-
第一個參數(shù):
__main_block_func_0
低葫。- 其實就是
Block
對應的主體部分详羡,可以看到下面關于__main_block_func_0
結構體的定義 ,和OC
代碼中^{ printf("myBlock\n"); };
部分具有相同的表達式嘿悬。 - 這里參數(shù)中的
__cself
是指向 Block 的值的指針變量实柠,相當于OC
中的self
。
/* Block 主體部分結構體 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("myBlock\n"); }
- 其實就是
-
第二個參數(shù):
__main_block_desc_0_DATA
:__main_block_desc_0_DATA
包含該 Block 的相關信息善涨。
我們再來結合之前的__main_block_impl_0
結構體定義窒盐。-
__main_block_impl_0
結構體(Block 結構體)可以表述為:
struct __main_block_impl_0 { void *isa; // 用于保存 Block 結構體的實例指針 int Flags; // 標志位 int Reserved; // 今后版本升級所需的區(qū)域大小 void *FuncPtr; // 函數(shù)指針 struct __main_block_desc_0* Desc; // Desc:Desc 指針 };
-
__main_block_impl_0
構造函數(shù)可以表述為:
impl.isa = &_NSConcreteStackBlock; // isa 保存 Block 結構體實例
impl.Flags = 0; // 標志位賦值
impl.FuncPtr = __main_block_func_0; // FuncPtr 保存 Block 結構體的主體部分
Desc = &__main_block_desc_0_DATA; // Desc 保存 Block 結構體的附加信息
調(diào)用
在分析了Block結構體和成員變量委刘,現(xiàn)在我們看看main函數(shù)中是如何調(diào)用block的
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
-
myBlock
結構體的第一個成員變量為__block_impl
汗捡,所以myBlock
首地址疙描,就是__block_impl impl
的首地址恩脂,即可以直接轉(zhuǎn)換為__block_impl
類型 -
(void ()(__block_impl )) 是
__block_impl
中Func
的類型 - *((__block_impl )myBlock)->FuncPtr() 調(diào)用函數(shù)
- *((__block_impl )myBlock) 函數(shù)參數(shù)
Block的實質(zhì)總結
用一句話來說幌氮,Block是個對象
- 在C語言的底層實現(xiàn)里糟红,它是一個結構體纸兔。這個結構體相當于
objc_class
的類對象結構體誓沸,用_NSConcreteStackBlock
對其中成員變量isa
初始化姿锭,_NSConcreteStackBlock
相當于class_t
結構體實例(在我的理解中就是該block
實例的元類)塔鳍。在將Block
作為OC
對象處理時,關于該類的信息放置于_NSConcreteStackBlock
中呻此。
- 是對象:其內(nèi)部第一個成員為
isa
指針轮纫;- 封裝了函數(shù)調(diào)用:
Block
內(nèi)代碼塊,封裝了函數(shù)調(diào)用焚鲜,調(diào)用Block
掌唾,就是調(diào)用該封裝的函數(shù);- 執(zhí)行上下文:
Block
還有一個描述Desc
忿磅,該描述對象包含了Block
的信息以及捕獲變量的內(nèi)存相關函數(shù)糯彬,及Block
所在的環(huán)境上下文;
Block的類型
前面已經(jīng)說過了葱她,Block的本質(zhì)就是一個OC對象撩扒,既然它是OC對象,那么它就有類型吨些。
準備工作:
- 先把ARC關掉搓谆,因為ARC幫我們做了太多的事,不方便我們觀察結果豪墅。關掉ARC的方法在Build Settings里面搜索Objective-C Automatic Reference Counting泉手,把這一項置為NO。
static int weight = 20;
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(void) = ^{
NSLog(@"%d %d", height, age);
};
NSLog(@"%@\n %@\n %@\n %@", [block class], [[block class] superclass], [[[block class] superclass] superclass], [[[[block class] superclass] superclass] superclass]);
return 0;
}
}
//打印結果
__NSStackBlock__
__NSStackBlock
NSBlock
NSObject
- 這說明上面定義的這個
block
的類型是NSStackBlock
偶器,并且它最終繼承自NSObject
也說明Block
的本質(zhì)是OC
對象螃诅。
Block的三種類型
-
Block有三種類型啡氢,分別是NSGlobalBlock,MallocBlock,NSStackBlock。
這三種類型的Block
對象的存儲區(qū)域如下:
類 | 對象的存儲域 |
---|---|
NSStackBlock(_NSConcreteStackBlock) | 棧 |
NSGlobalBlock(_NSConcreteGlobalBlock) | 程序的數(shù)據(jù)區(qū)域(.data區(qū)) |
NSMallocBlock(_NSConcreteMallocBlock) | 堆 |
截獲了自動變量的Block是NSStackBlock類型术裸,沒有截獲自動變量的Block則是NSGlobalStack類型,NSStackBlock類型的Block進行copy操作之后其類型變成了NSMallocBlock類型倘是。
- 下面我們用一張圖來看一下不同block的存儲區(qū)域
- 從上圖可以發(fā)現(xiàn),根據(jù)
block
的類型不同袭艺,block存放在不同的區(qū)域中搀崭。- 數(shù)據(jù)段中的
__NSGlobalBlock__
直到程序結束才會被回收,不過我們很少使用到__NSGlobalBlock__
類型的block
猾编,因為這樣使用block
并沒有什么意義瘤睹。 -
__NSStackBlock__
類型的block
存放在棧中,我們知道棧中的內(nèi)存由系統(tǒng)自動分配和釋放答倡,作用域執(zhí)行完畢之后就會被立即釋放轰传,而在相同的作用域中定義block
并且調(diào)用block
似乎也多此一舉。 -
__NSMallocBlock__
是在平時編碼過程中最常使用到的瘪撇。存放在堆中需要我們自己進行內(nèi)存管理获茬。
- 數(shù)據(jù)段中的
-
_NSConcreteGlobalBlock
- 在以下兩種情況下使用
Block
的時候,Block
為NSConcreteGlobalBlock
類對象倔既。
- 記述全局變量的地方恕曲,使用
Block
語法時; -
Block
語法的表達式中沒有截獲的自動變量時渤涌。
NSConcreteGlobalBlock
類的Block
存儲在『程序的數(shù)據(jù)區(qū)域』佩谣。因為存放在程序的數(shù)據(jù)區(qū)域,所以即使在變量的作用域外实蓬,也可以通過指針安全的使用茸俭。記述全局變量的地方,使用 Block 語法示例代碼:
void (^myGlobalBlock)(void) = ^{ printf("GlobalBlock\n"); }; int main() { myGlobalBlock(); return 0; }
- 在以下兩種情況下使用
通過對應
C++
源碼安皱,我們可以發(fā)現(xiàn):Block
結構體的成員變量isa
賦值為:impl.isa = &_NSConcreteGlobalBlock;
调鬓,說明該Block
為NSConcreteGlobalBlock
類對象。_NSConcreteStackBlock
除了_NSConcreteGlobalBlock中提到的兩種情形练俐,其他情形下創(chuàng)建的 Block
都是 NSConcreteStackBlock
對象袖迎,平常接觸的 Block
大多屬于 NSConcreteStackBlock
對象冕臭。
NSConcreteStackBlock
類的 Block
存儲在『棧區(qū)』的腺晾。如果其所屬的變量作用域結束,則該 Block 就會被廢棄辜贵。如果 Block 使用了 __block
變量悯蝉,則當 __block
變量的作用域結束,則 __block
變量同樣被廢棄托慨。
- _NSConcreteMallocBlock
為了解決棧區(qū)上的 Block
在變量作用域結束被廢棄這一問題鼻由,Block
提供了 『復制』功能。可以將 Block 對象和 __block
變量從棧區(qū)復制到堆區(qū)上蕉世。當 Block
從棧區(qū)復制到堆區(qū)后蔼紧,即使棧區(qū)上的變量作用域結束時,堆區(qū)上的 Block
和 __block
變量仍然可以繼續(xù)存在狠轻,也可以繼續(xù)使用。
此時,『堆區(qū)』上的 Block 為 NSConcreteMallocBlock
對象,Block 結構體的成員變量 isa 賦值為:impl.isa = &_NSConcreteMallocBlock;
那么怕磨,什么時候才會將 Block 從棧區(qū)復制到堆區(qū)呢氢哮?
這就涉及到了 Block 的自動拷貝和手動拷貝蝎困。
Block的自動拷貝和手動拷貝
Block的自動拷貝
在使用ARC
時禾乘,大多數(shù)情形下編譯器會自動進行判斷澎埠,自動生成將 Block
從棧上復制到堆上的代碼:
- 將
Block
作為函數(shù)返回值返回時,會自動拷貝始藕; - 向方法或函數(shù)的參數(shù)中傳遞
Block
時蒲稳,使用以下兩種方法的情況下,會進行自動拷貝伍派,否則就需要手動拷貝:-
Cocoa
框架的方法且方法名中含有usingBlock
等時江耀; -
Grand Central Dispatch(GCD)
的 API。
-
- 將
Block
賦值給類的附有__strong
修飾符的id
類型或 Block 類型成員變量時
Block的手動拷貝
我們可以通過『copy 實例方法(即 alloc / new / copy / mutableCopy
)』來對 Block
進行手動拷貝诉植。當我們不確定 Block
是否會被遺棄祥国,需不需要拷貝的時候,直接使用 copy
實例方法即可晾腔,不會引起任何的問題舌稀。
關于 Block 不同類的拷貝效果總結如下:
Block 類 | 存儲區(qū)域 | 拷貝效果 |
---|---|---|
_NSConcreteStackBlock | 棧區(qū) | 從棧拷貝到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 不做改變 |
_NSConcreteMallocBlock | 堆區(qū) | 引用計數(shù)增加 |
__block變量的拷貝
在使用 __block
變量的 Block
從棧復制到堆上時灼擂,__block
變量也會受到如下影響:
__block 變量的配置存儲區(qū)域 | Block 從棧復制到堆時的影響 |
---|---|
堆區(qū) | 從棧復制到堆壁查,并被 Block 所持有 |
棧區(qū) | 被 Block 所持有 |
當然,如果不再有 Block
引用該 __block
變量剔应,那么 __block
變量也會被廢除睡腿。
Block截獲變量實質(zhì)
我們下面見根據(jù)變量修飾符,來探查 Block 如何捕獲不同修飾符的類型變量峻贮。
- auto:自動變量修飾符
- static:靜態(tài)修飾符
- const:常量修飾符
在這三種修飾符席怪,我們又細分為全局變量和局部變量。
Block截獲自動局部變量的實質(zhì)
// 使用 Blocks 截獲局部變量值
int c = 30;//全局變量
- (void)useBlockInterceptLocalVariables {
int a = 10, b = 20;//局部變量
void (^myLocalBlock)(void) = ^{
printf("a = %d, b = %d, c = %d\n",a, b月洛, c);
};
void (^Block)(int, int, int) = ^(int a, int b, int c){
printf("a = %d, b = %d, c = %d\n",a, b, c);
};
myLocalBlock(); // 輸出結果:a = 10, b = 20, c = 30
a = 20;
b = 30;
myLocalBlock(); // 輸出結果:a = 10, b = 20, c = 30
Block(a, b, c); // 輸出結果:a = 20, b = 30, c = 30
}
Block塊中直接調(diào)用局部變量
- 從中我們可以看出何恶,我們在第一次調(diào)用
myLocalBlock();
之后已經(jīng)重新給變量a
孽锥、變量b
賦值了嚼黔,但是第二次調(diào)用myLocalBlock();
的時候细层,使用的還是之前對應變量的值。
這是因為Block 語法的表達式使用的是它之前申明的局部變量
a
唬涧、變量b
疫赎。Blocks中,Block表達式截獲所使用的局部變量的值碎节,保存了該變量的瞬時值捧搞。所以再第二次執(zhí)行Block表達式的時候,即使已經(jīng)改變了局部變量a
和b
的值狮荔,也不會影響B(tài)lock表達式在執(zhí)行時所保存的局部變量的瞬時值胎撇。這就是Block變量截獲局部變量值的特性
??:
Block
語法表達式中沒有使用的自動變量不會被追加到結構體中,Blocks
的自動變量截獲只針對Block
中使用的自動變量殖氏。可是晚树,為什么 Blocks 變量使用的是局部變量的瞬時值,而不是局部變量的當前值呢雅采?讓我們看一下對應的
C++
代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int b;
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
printf("a = %d, b = %d, c = %d\n",a, b, c);
}
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 a = 10, b = 20;
void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
a = 20;
b = 30;
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
}
- 可以看到
__main_block_impl_0
結構體(Block 結構體)中多了兩個成員變量a
和b
爵憎,這兩個變量就是 Block 截獲的局部變量。a
和b
的值來自與__main_block_impl_0
構造函數(shù)中傳入的值婚瓜。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 增加的成員變量 a
int b; // 增加的成員變量 b
__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;
}
};
-
還可以看出 __main_block_func_0
(保存Block
主體部分的結構體)中宝鼓,變量a、b
的值使用的__cself
獲取的值巴刻。
而__cself->a
愚铡、__cself->b
是通過值傳遞的方式傳入進來的,而不是通過指針傳遞胡陪。這也就說明了a
茂附、b
只是Block
內(nèi)部的變量,改變Block
外部的局部變量值督弓,并不能改變Block
內(nèi)部的變量值营曼。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
printf("a = %d, b = %d\n",a, b);
}
- 我們可以看出全局變量并沒有存儲在Block的結構體中,而是在調(diào)用的時候通過直接訪問的方式來調(diào)用愚隧。
- 下面用一張圖我們把上面所說的全局作用域和局部作用域做一個總結
變量類型 | 抓獲到Block對象內(nèi)部 | 訪問方式 |
---|---|---|
局部變量 | 指傳遞 | |
全局變量 | 直接訪問 |
Block通過傳值間接訪問局部變量
// 使用 Blocks 截獲局部變量值
int c = 30;
- (void)useBlockInterceptLocalVariables {
int a = 10, b = 20;
void (^Block)(void) = ^(int a, int b){
printf("a = %d, b = %d\n",a, b);
};
a = 20;
b = 30;
Block(a,b); // 輸出結果:a = 20, b = 30, c = 30
}
- 我們來看看直接傳值和通過
block
截獲局部變量的區(qū)別
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself, int a, int b, int c) {
printf("a = %d, b = %d, c = %d\n",a, b, c);
}
static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10, b = 20;
void (*Block)(int,int) = ((void (*)(int, int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));
a = 20;
b = 30;
((void (*)(__block_impl *, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
}
return 0;
}
- 可以看見
__main_block_impl_1
結構體中沒有變量a
蒂阱、變量b
,說明通過直接傳值的方式,變量并沒有存進Block
的結構體中狂塘。 - 在
__main_block_func_1
函數(shù)中录煤,發(fā)現(xiàn)參數(shù)列表中多了int a, int b
這兩個參數(shù),還有調(diào)用Block
的時候荞胡,直接把變量a
妈踊、b
的值傳入進去了。
Block截獲static修飾變量的實質(zhì)
下面我們定義了三個變量:
- 全局
- 變量:c
- 局部
- 常量:a
- 變量:b
static int c = 30;
- (void)useBlockInterceptLocalVariables {
static const int a = 10;
static int b = 20;
void (^Block)(void) = ^{
b = 50;
c = 60;
printf("a = %d, b = %d, c = %d\n",a, b, c);
};
b = 30;
c = 40;
Block(); // 輸出結果:a = 10, b = 50, c = 60
}
- 從以上測試結果我們可以得出:
-
Block
對象能獲取最新的靜態(tài)全局變量和靜態(tài)局部變量泪漂; - 靜態(tài)局部常量由于值不會更改廊营,故沒有變化歪泳;
-
- 我們來看看
c++
代碼,到底發(fā)生了什么
static int c = 30;//全局靜態(tài)變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *b;//捕獲變量露筒,獲取變量地址
const int *a;//捕獲變量呐伞,獲取變量地址
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, const int *_a, int flags=0) : b(_b), a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//2.通過Block對象獲取到b和a的指針
int *b = __cself->b; // bound by copy
const int *a = __cself->a; // bound by copy
//通過b指針,修改b指向的值
(*b) = 50;
c = 60;
printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}
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;
static const int a = 10;
static int b = 20;
//1.傳入&a, &b地址進行Blcok對象的初始化
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &b, &a));
b = 30;
c = 40;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
- 可以看到在
__main_block_impl_0
結構體中慎式,靜態(tài)局部變量static int b
以指針的形式添加為成員變量伶氢,而靜態(tài)局部常量static const int a
以const int *
的形式添加為成員變量。而全局靜態(tài)變量static int c
并沒有添加為成員變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *b;//捕獲變量瘪吏,獲取變量地址
const int *a;//捕獲變量癣防,獲取變量地址
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, const int *_a, int flags=0) : b(_b), a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 再來看看
__main_block_func_0
結構體部分,靜態(tài)全局變量static int c
是直接訪問的掌眠,靜態(tài)局部變量static int b
是通過『指針傳遞』的方式進行訪問劣砍,靜態(tài)局部常量static const int a
也是通過『指針傳遞』的方式進行訪問,但是它是通過const
修飾的無法進行賦值操作扇救。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//2.通過Block對象獲取到b和a的指針
int *b = __cself->b; // bound by copy
const int *a = __cself->a; // bound by copy
//通過b指針刑枝,修改b指向的值
(*b) = 50;
c = 60;
printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}
-
我們?yōu)槭裁茨塬@取
static
變量最新的值?-
static
修飾的迅腔,其作用區(qū)域不管是全局還是局部装畅,不管是常量還是變量,均存儲在全局存儲區(qū)中沧烈,存在全局存儲區(qū)掠兄,該地址在程序運行過程中一直不會改變,所以能訪問最新值锌雀。 -
static
修飾后:
- 全局變量蚂夕,直接訪問。
- 局部變量腋逆,指針訪問婿牍。其中在局部變量中,又有局部靜態(tài)常量惩歉,即被
const
修飾的等脂。-
const
存放在text
段中,即使被static
同時修飾撑蚌,也存放text
中的常量區(qū)上遥;
-
-
Block截獲const修飾變量的實質(zhì)
- 如下定義:
-
const
全局變量:a -
const
局部變量:b
-
const int a = 10;
- (void)useBlockInterceptLocalVariables {
const int b = 20;
void (^Block)(void) = ^{
printf("a = %d, b = %d\n",a, b);
};
Block(); // 輸出結果:a = 10, b = 20
}
- 看看轉(zhuǎn)換后的
c++
代碼
static int a = 10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const int b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int _b, int flags=0) : b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const int b = __cself->b; // bound by copy
printf("a = %d, b = %d\n",a, b);
}
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;
const int b = 20;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
return 0;
}
-
從上面看出:
-
const
全局變量直接訪問; -
const
局部變量争涌,其實仍然是auto
修飾粉楚,值傳遞;
-
最后我們用一張圖來總結一下這一節(jié)所學的內(nèi)容
Block截獲對象實質(zhì)
- 在前一節(jié)我們學習了
Block
如何截獲基本類型,這一節(jié)我們主要學習截獲對象類型的auto
變量
Person *person = [[Person alloc] init];
person.age = 20;
void (^block)(void) = ^{
NSLog(@"age = %d", person.age);
};
block();
- 根據(jù)
Block
捕獲基本變量的規(guī)律模软,針對對象伟骨,仍然適用-
auto
變量捕獲后,Block
中變量的類型和變量原類型一致撵摆; -
static
變量捕獲后,Block
對應的變量是對應變量的指針類型害晦;
-
那么特铝,auto
對象與基本類型在 Block
內(nèi)部有什么區(qū)別呢。
- 我們把上述代換轉(zhuǎn)換成
C++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Person *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bp_sg6dyc5957s2j2v4l6z9k4dm0000gn_T_main_f5936b_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
- 我們看到
__main_block_impl_0
結構體中多了一個一個成員變量Person *person
壹瘟,因為person
是自動變量鲫剿,所以這里捕獲了自動變量person
作為_main_block_impl_0
結構體的成員變量。而且還要注意的是稻轨,由于是自動變量灵莲,所以在block外面,自動變量是什么類型殴俱,在結構體里面作為成員變量就是什么類型政冻。person在結構體外面作為自動變量是指針類型,所以作為結構體里面的成員變量也是指針類型线欲。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 我們看到
__main_block_desc_0
結構中多了兩個函數(shù)指針void (*copy)
和void (*dispose)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
針對這兩個函數(shù)明场,它們的作用就是:
函數(shù) | 作用 | 調(diào)用時機 |
---|---|---|
__main_block_copy_0 | 調(diào)用 _Block_object_assign ,相當于 retain李丰,將對象賦值在對象類型的結構體變量 __main_block_impl_0 中苦锨。 |
棧上的 Block 復制到堆時 |
__main_block_dispose_0 | 調(diào)用 _Block_object_dispose ,相當于 release趴泌,釋放賦值在對象類型的結構體變量中的對象舟舒。 |
堆上 Block 被廢棄時 |
Blocks內(nèi)改寫被截獲變量的值的方式
在Block中修飾被截獲變量的值只有一下兩種情況,我們先分析通過
__block
修飾符來修改截獲變量的方式
__block修飾符
-
__block 說明符
類似于static
嗜憔、auto
秃励、register
說明符,它們用于指定將變量值設置到哪個存儲域中吉捶。例如auto
表示作為自動變量存儲在棧中莺治,static
表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)中。
__block
修飾符又分為修飾局部變量和修飾對象
__blcok修飾局部變量
- (void)useBlockQualifierChangeLocalVariables {
__block int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
a = 20;
b = 30;
printf("a = %d, b = %d\n",a, b); // 輸出結果:a = 20, b = 30
};
myLocalBlock();
}
從中我們可以發(fā)現(xiàn):通過
__block
修飾的局部變量帚稠,可以在 Block 的主體部分中改變值谣旁。我們來轉(zhuǎn)換下源碼,分析一下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Block_byref_b_1 {
void *__isa;
__Block_byref_b_1 *__forwarding;
int __flags;
int __size;
int b;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_b_1 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__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
__Block_byref_b_1 *b = __cself->b; // bound by ref
(a->__forwarding->a) = 20;
(b->__forwarding->b) = 30;
printf("a = %d, b = %d\n",(a->__forwarding->a), (b->__forwarding->b));
}
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*/);_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
__Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};
void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
return 0;
}
可以看到滋早,只是加上了一個
__block
榄审,代碼量就增加了很多。我們從
__main_block_impl_0
開始說起:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_b_1 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我們在
__main_block_impl_0
結構體中可以看到: 原 OC 代碼中杆麸,被__block
修飾的局部變量__block int a
搁进、__block int b
分別變成了__Block_byref_a_0
浪感、__Block_byref_b_1
類型的結構體指針a
、結構體指針b
饼问。這里使用結構體指針a
影兽、結構體指針b
說明_Block_byref_a_0
、__Block_byref_b_1
類型的結構體并不在__main_block_impl_0
結構體中莱革,而只是通過指針的形式引用峻堰,這是為了可以在多個不同的 Block 中使用__block
修飾的變量。__Block_byref_a_0
盅视、__Block_byref_b_1
類型的結構體聲明如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Block_byref_b_1 {
void *__isa;
__Block_byref_b_1 *__forwarding;
int __flags;
int __size;
int b;
};
-
拿第一個
__Block_byref_a_0
結構體定義來說明捐名,__Block_byref_a_0
有 5 個部分:__isa
:標識對象類的isa
實例變量__forwarding
:傳入變量的地址__flags
:標志位__size
:結構體大小a
:存放實變量a
實際的值,相當于原局部變量的成員變量(和之前不加__block修飾符的時候一致)闹击。
再來看一下
main()
函數(shù)中镶蹋,__block int a
、__block int b
的賦值情況赏半。
__Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
10
};
__Block_byref_b_1 b = {
0,
&b,
0,
sizeof(__Block_byref_b_1),
20
};
還是拿第一個
__Block_byref_a_0 a
的賦值來說明贺归。可以看到
__isa
指針值傳空,__forwarding
指向了局部變量a
本身的地址断箫,__flags
分配了 0牧氮,__size
為結構體的大小,a
賦值為 10瑰枫。下圖用來說明__forwarding
指針的指向情況踱葛。
這下,我們知道
__forwarding
其實就是局部變量a
本身的地址光坝,那么我們就可以通過__forwarding
指針來訪問局部變量尸诽,同時也能對其進行修改了。來看一下 Block 主體部分對應的
__main_block_func_0
結構體來驗證一下盯另。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
__Block_byref_b_1 *b = __cself->b; // bound by ref
(a->__forwarding->a) = 20;
(b->__forwarding->b) = 30;
printf("a = %d, b = %d\n",(a->__forwarding->a), (b->__forwarding->b));
}
可以看到 (a->__forwarding->a) = 20;
和 (b->__forwarding->b) = 30;
是通過指針取值的方式來改變了局部變量的值性含。這也就解釋了通過 __block
來修飾的變量,在 Block 的主體部分中改變值的原理其實是:通過『指針傳遞』的方式鸳惯。
__block修飾對象
- (void)useBlockQualifierChangeLocalVariables {
__block Person *person = [[Person alloc] init];
void(^block)(void) = ^ {
person = [[Person alloc] init];//修改person創(chuàng)建的地址
NSLog(@"person is %@", person);
};
block();
}
- 把上述代碼轉(zhuǎn)換成C++
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *person;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref
(person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bp_sg6dyc5957s2j2v4l6z9k4dm0000gn_T_main_1f2ea2_mi_0, (person->__forwarding->person));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref)))
__Block_byref_person_0 person = {
(void*)0,
(__Block_byref_person_0 *)&person,
33554432,
sizeof(__Block_byref_person_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"),
sel_registerName("alloc")),
sel_registerName("init"))
};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
- 我們先從
__main_block_impl_0
開始說起:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 我們在
__main_block_impl_0
結構體中可以看到: 原OC
代碼中商蕴,被__block
修飾的person
變成了__Block_byref_person_0
類型結構體指針 -
__Block_byref_person_0
類型結構體聲明和該結構體初始化如下:
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *person;
};
__Block_byref_person_0 person = {
(void*)0,
(__Block_byref_person_0 *)&person,
33554432,
sizeof(__Block_byref_person_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"),
sel_registerName("alloc")),
sel_registerName("init"))
};
- 我們發(fā)現(xiàn),在
_Block_byref_person_0
中多了兩個函數(shù)芝发,通過其初始化可以知道這兩個函數(shù)分別是__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
這兩個函數(shù)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
- 這兩個函數(shù)其實和
_main_block_copy_0
和_main_block_dispose_0
一樣绪商,最終都是調(diào)用_Block_object_assign
和_Block_object_dispose
這兩個函數(shù)。那么這里為什么都加上了40呢辅鲸?我們分析一下_Block_byref_person_0
的結構:
struct __Block_byref_person_0 {
void *__isa; //指針格郁,8字節(jié)
__Block_byref_person_0 *__forwarding; //指針,8字節(jié)
int __flags; //int型,4字節(jié)
int __size; //int型例书,4字節(jié)
void (*__Block_byref_id_object_copy)(void*, void*);//指針型锣尉,8字節(jié)
void (*__Block_byref_id_object_dispose)(void*);//指針型,8字節(jié)
Person *person;
};
- 這樣一來决采,
_Block_byref_person_0
的地址和person
指針的地址就相差40字節(jié)自沧,所以+40的目的就是找到person
指針。
更改特殊區(qū)域變量值
- C語言中有幾種特殊區(qū)域的變量树瞭,允許 Block 改寫值拇厢,具體如下:
- 靜態(tài)局部變量
- 靜態(tài)全局變量
- 全局變量
下面我們通過代碼來看看這種情況
- OC代碼
int global_val = 10; // 全局變量
static int static_global_val = 20; // 靜態(tài)全局變量
int main() {
static int static_val = 30; // 靜態(tài)局部變量
void (^myLocalBlock)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, static_val);
};
myLocalBlock();
return 0;
}
- C++代碼
int global_val = 10;
static int static_global_val = 20;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, (*static_val));
}
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() {
static int static_val = 30;
void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
return 0;
}
- 從中可以看到:
- 在
__main_block_impl_0
結構體中,將靜態(tài)局部變量static_val
以指針的形式添加為成員變量移迫,而靜態(tài)全局變量static_global_val
旺嬉、全局變量global_val
并沒有添加為成員變量管行。
- 在
int global_val = 10;
static int static_global_val = 20;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 再來看一下 Block 主體部分對應的
__main_block_func_0
結構體部分厨埋。靜態(tài)全局變量static_global_val
、全局變量global_val
是直接訪問的捐顷,而靜態(tài)局部變量static_val
則是通過『指針傳遞』的方式進行訪問和賦值荡陷。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, (*static_val));
}
-
靜態(tài)變量的這種方法似乎也適用于自動變量的訪問,但我們?yōu)槭裁礇]有這么做呢迅涮?
實際上废赞,在由 Block 語法生成的值 Block 上,可以存有超過其變量域的被截獲對象的自動變量叮姑。變量作用域結束的同時唉地,原來的自動變量被廢棄,因此 Block 中超過變量作用域而存在的變量同靜態(tài)變量一樣猜欺,將不能通過指針訪問原來的自動變量婆廊。
最后我們用一張圖來總結一下這一節(jié)所學
__block
變量內(nèi)存管理
我們先回顧一下之前的一些捕獲變量或?qū)ο笫侨绾喂芾韮?nèi)存的庸追。
-
注:下面 “干預” 是指不用程序員手動管理,其實本質(zhì)還是要系統(tǒng)管理內(nèi)存的分配與釋放群嗤。
-
auto
局部基本類型變量,因為是值傳遞兵琳,內(nèi)存是跟隨 Block狂秘,不用干預; -
static
局部基本類型變量躯肌,指針傳遞者春,由于分配在靜態(tài)區(qū),故不用干預清女; - 全局變量碧查,存儲在數(shù)據(jù)區(qū),不用多說,不用干預忠售;
- 局部對象變量传惠,如果在棧上,不用干預稻扬。但
Block
在拷貝到堆的時候卦方,對其retain
,在Block
對象銷毀時泰佳,對其release
盼砍;
-
在這里,__block
變量呢逝她?
注意點就是:__block 變量在轉(zhuǎn)換后封裝成了一個新對象浇坐,內(nèi)存管理會多出一層。
基本類型的Desc
上述 age
是基本類型黔宛,其轉(zhuǎn)換后的結構體為:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
而 Block
中的 Desc
如下:
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
//下面兩個方法是Block內(nèi): 內(nèi)存管理相關函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}
針對基本類型近刘,以 age
類型為例:
-
__Block_byref_age_0
對象同樣是在Block對象
從棧上拷貝到堆上,進行retain
臀晃; - 當
Block對象
銷毀時觉渴,對__Block_byref_age_0
進行release
; -
__Block_byref_age_0
內(nèi)age
徽惋,由于是基本類型案淋,是不用進行內(nèi)存手動干預的。
對象類型的Desc
下面看__block
對象類型的轉(zhuǎn)換:
struct __Block_byref_person_1 {
void *__isa; //地址0---占用8字節(jié)
__Block_byref_person_1 *__forwarding; //地址8-16---占用8字節(jié)
int __flags; //地址16-20---占用4字節(jié)
int __size; //地址20-24---占用8字節(jié)
void (*__Block_byref_id_object_copy)(void*, void*); //地址24-32---占用8字節(jié)
void (*__Block_byref_id_object_dispose)(void*); //地址32-40---占用8字節(jié)
BFPerson *person; //地址40-48---占用8字節(jié)
};
因為捕獲的本身是一個對象類型险绘,所以該對象類型還需要進行內(nèi)存的干預踢京。
這里有兩個熟悉的函數(shù),即用于管理對象 auto 變量時宦棺,我們見過瓣距,用于管理對象 auto 的內(nèi)存:
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
- 那么這兩個函數(shù)對應的實現(xiàn),我們也找出來:
初始化__block對象
下面針對轉(zhuǎn)換來轉(zhuǎn)換去的細節(jié)做了刪減渺氧,方便閱讀:
struct __Block_byref_person_1 person = {
0, //isa
&person, //__forwarding
33554432, //__flags
sizeof(__Block_byref_person_1), //__size
__Block_byref_id_object_copy_131, //(*__Block_byref_id_object_copy)
__Block_byref_id_object_dispose_131,// (*__Block_byref_id_object_dispose)
(objc_msgSend)((objc_msgSend)(objc_getClass("BFPerson"), sel_registerName("alloc")), sel_registerName("init"))
};
//注意此處+40字節(jié)
static void __Block_byref_id_object_copy_131(void *dst, void *src
{
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src)
{
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
- 我們注意觀察旨涝,在
__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
函數(shù)中,都會偏移 40 字節(jié)侣背,我們再看__block BFPerson
對象轉(zhuǎn)換后的__Block_byref_person_1
結構體發(fā)現(xiàn)白华,其 40 字節(jié)偏移處就是原本的BFPerson *person
對象。
對象類型的內(nèi)存管理
以 BFPerson *person
贩耐,在__block
修飾后弧腥,轉(zhuǎn)換為:__Block_byref_person_1
對象:
-
__Block_byref_person_1
對象同樣是在Block對象
從棧上拷貝到堆上,進行retain
潮太;- 當
__Block_byref_person_1
進行retain
同時管搪,會將person
對象進行 retain
- 當
- 當
Block對象
銷毀時虾攻,對__Block_byref_person_1
進行release
- 當
__Block_byref_person_1
對象release
時,會將person
對象release
- 當
與auto對象變量的區(qū)別
從棧到堆
Block 從棧復制到堆時更鲁,__block 變量產(chǎn)生的影響如下:
__block 變量的配置存儲域 | Block 從棧復制到堆的影響 |
---|---|
棧 | 從棧復制到堆霎箍,并被 Block 持有 |
堆 | 被 Block 持有 |
Block從棧拷貝到堆
當有多個 Block 對象澡为,持有同一個__block 變量漂坏。
- 當其中任何 Block 對象復制到堆上,__block 變量就會復制到堆上媒至。
- 后續(xù)顶别,其他 Block 對象復制到堆上,__block 對象引用計數(shù)會增加拒啰。
- Block 復制到堆上的對象驯绎,持有__block 對象。
Block銷毀
總結
更多細節(jié)
__block捕獲變量存放在哪谋旦?
__block int age = 20;
__block BFPerson *person = [[BFPerson alloc] init];
void(^block)(void) = ^ {
age = 30;
person = [[BFPerson alloc] init];
NSLog(@"malloc address: %p %p", &age, person);
NSLog(@"malloc age is %d", age);
NSLog(@"person is %@", person);
};
block();
NSLog(@"stack address: %p %p", &age, person);
NSLog(@"stack age is %d", age);
//輸出結果
Block-test[12866:2303749] malloc address: 0x100610bf8 0x100612ff0
Block-test[12866:2303749] malloc age is 30
Block-test[12866:2303749] person is <Person: 0x100612ff0>
Block-test[12866:2303749] stack address: 0x100610bf8 0x100612ff0
Block-test[12866:2303749] stack age is 30
可以看到剩失,不管是 age
還是 person
,均在堆空間蛤织。
其實赴叹,本質(zhì)上鸿染,將 Block
從椫秆粒拷貝到堆,也會將__block
對象一并拷貝到堆涨椒,如下圖:
對象與__block變量的區(qū)別
__block BFPerson *blockPerson = [[BFPerson alloc] init];
BFPerson *objectPerson = [[BFPerson alloc] init];
void(^block)(void) = ^ {
NSLog(@"person is %@ %@", blockPerson, objectPerson);
};
轉(zhuǎn)換后:
//Block對象
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
BFPerson *objectPerson; //objectPerson對象摊鸡,捕獲
__Block_byref_blockPerson_0 *blockPerson; // blockPerson封裝后的對象,內(nèi)部捕獲blockPerson
};
//__block blockPerson封裝后的對象
struct __Block_byref_blockPerson_0 {
void *__isa;
__Block_byref_blockPerson_0 *__forwarding;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
BFPerson *blockPerson;
};
//兩種對象不同的處理方式
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->blockPerson, (void*)src->blockPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->objectPerson, (void*)src->objectPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//__Block_byref_blockPerson_0內(nèi)部對__block blockPerson的處理
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
從上面可以得出