iOS開發(fā)---Block詳解
Block的基礎(chǔ)
什么是Blocks恋日?
- 用一句話來描述:帶有自動(dòng)變量的匿名函數(shù)(是不是一臉懵逼彩郊,不要擔(dān)心讥耗,整篇博客都會(huì)圍繞這句話展開)顧名思義:
Block
沒有函數(shù)名毒坛,另外Block
帶有"^
"標(biāo)記,插入記號(hào)便于查找到Block
-
Blocks
也被稱作閉包望伦、代碼塊。展開來講煎殷,Blocks
就是一個(gè)代碼塊屯伞,把你想要執(zhí)行的代碼封裝在這個(gè)代碼塊里,等到需要的時(shí)候再去調(diào)用豪直。 -
Block
共享局部作用域的數(shù)據(jù)劣摇。Block
的這項(xiàng)特征非常有用,因?yàn)槿绻鷮?shí)現(xiàn)一個(gè)方法弓乙,并且該方法定義一個(gè)塊末融,則該塊可以訪問該方法的局部變量和參數(shù)(包括堆棧變量),以及函數(shù)和全局變量(包括實(shí)例變量)暇韧。這種訪問是只讀的勾习,但如果使用__block
修飾符聲明變量,則可在Block
內(nèi)更改其值懈玻。即使包含有塊的方法或函數(shù)已返回巧婶,并且其局部作用范圍已銷毀,但是只要存在對(duì)該塊的引用涂乌,局部變量仍作為塊對(duì)象的一部分繼續(xù)存在艺栈。
Blocks的語法
-
Block
的完整語法格式如下:
^ returnType (argument list) {
expressions
}
用一張圖來表示
- 也可以寫省略格式的
Block
,比如省略返回值類型:
^ (int x) {
return x;
}
Block
省略返回值類型時(shí),如果表達(dá)式中有return語句就使用該返回值的類型骂倘,沒有return語句就使用void
類型眼滤。
- 如果沒有參數(shù)列表,也可以省略參數(shù)列表:
^ {
NSLog(@"hello world");
}
- 與c語言的區(qū)別
- 沒有函數(shù)名
- 帶有"^"符號(hào)
Block類型變量
-
Block
類型變量與一般的C
語言變量完全相同历涝,可以作為自動(dòng)變量诅需,函數(shù)參數(shù),靜態(tài)變量荧库,全局變量使用 - C語言函數(shù)將是如何將所定義的函數(shù)的地址賦值給函數(shù)指針類型變量中
int func (int count)
{
return count + 1;
}
int (*funcptr) (int) = &func;
- 使用
Block
語法就相當(dāng)于生成了可賦值給Block
類型變量的值堰塌。
//Blocks 變量聲明與賦值
int (^blk) (int) = ^int (int count) {
return count + 1;
};
//把Block語法生成的值賦值給Block類型變量
int (^myBlock)(int) = blk;
與前面的使用函數(shù)指針的源代碼對(duì)比可知,聲明
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ù)列表) { 表達(dá)式 };
typedef void (^callBlock)(NSSting *);
callBlock block = ^(NSString *str) {
NSLog(@"%@", str);
}
與上一個(gè)知識(shí)點(diǎn)中指定Block為函數(shù)返回值對(duì)比
//原來的記述方式
void func(void (^blk)(NSString 8))
//用了 typedef 定義后的記述方式
void func(callBlock blk)
//原來的記述方式
void (^func()(NSString *))
//用了 typedef 定義后的記述方式
callBlock func()
Block截取變量
截獲自動(dòng)變量的值
- 我們先看一個(gè)??
// 使用 Blocks 截獲局部變量值
- (void)useBlockInterceptLocalVariables {
int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
printf("a = %d, b = %d\n",a, b);
};
myLocalBlock(); // 打印結(jié)果:a = 10, b = 20
a = 20;
b = 30;
myLocalBlock(); // 打印結(jié)果:a = 10, b = 20
}
為什么兩次打印結(jié)果都是 a = 10, b = 20
?
明明在第一次調(diào)用 myLocalBlock();
之后已經(jīng)重新給變量 a蚪战、變量 b 賦值了牵现,為什么第二次調(diào)用 myLocalBlock();
的時(shí)候铐懊,使用的還是之前對(duì)應(yīng)變量的值?
因?yàn)?Block
語法的表達(dá)式使用的是它之前聲明的局部變量a
瞎疼、變量 b
科乎。Blocks
中,Block
表達(dá)式截獲所使用的局部變量的值贼急,保存了該變量的瞬時(shí)值茅茂。所以在第二次執(zhí)行 Block
表達(dá)式時(shí),即使已經(jīng)改變了局部變量 a
和 b
的值太抓,也不會(huì)影響 Block
表達(dá)式在執(zhí)行時(shí)所保存的局部變量的瞬時(shí)值空闲。
這就是 Blocks
變量截獲局部變量值的特性。
通過__block說明符賦值
- 上面我們想修改變量
a
走敌,變量b
的值碴倾,但是有沒有成功,那么我們難道就沒有方法來修改了么悔常?別急影斑,__block
來也,只要用這個(gè)說明符修飾變量机打,就可以在塊中修改矫户。
// 使用 __block 說明符修飾,更改局部變量值
- (void)useBlockQualifierChangeLocalVariables {
__block int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
a = 20;
b = 30;
printf("a = %d, b = %d\n",a, b); // 打印結(jié)果:a = 20, b = 30
};
myLocalBlock();
}
可以看到残邀,使用__block
說明符修飾之后皆辽,我們?cè)?Block
表達(dá)式中,成功的修改了局部變量值芥挣。
使用__block
修飾符的自動(dòng)變量被稱為__blcok
變量
Block的實(shí)現(xiàn)
在上一部分我們知道了
Blocks
是 帶有局部變量的匿名函數(shù)驱闷。但是 Block 的實(shí)質(zhì)究竟是什么呢?類型空免?變量空另?要想了解 Block 的本質(zhì),就需要從 Block 對(duì)應(yīng)的 C++ 源碼來入手蹋砚。
下面我們通過一步步的源碼剖析來了解 Block 的本質(zhì)扼菠。
Block的實(shí)質(zhì)是什么?
準(zhǔn)備工作
- 在項(xiàng)目中添加
blocks.m
文件坝咐,并寫好block
的相關(guān)代碼析命。 - 打開『終端』粥帚,執(zhí)行
cd XXX/XXX
命令铐达,其中XXX/XXX
為block.m
所在的目錄凤类。 - 繼續(xù)執(zhí)行
clang -rewrite-objc block.m
- 執(zhí)行完命令之后,
block.m
所在目錄下就會(huì)生成一個(gè)block.cpp
文件,這就是我們需要的block
相關(guān)的C++
源碼盗尸。
Block源碼預(yù)覽
- 轉(zhuǎn)換前OC代碼:
int main () {
void (^myBlock)(void) = ^{
printf("myBlock\n");
};
myBlock();
return 0;
}
- 轉(zhuǎn)換后c++代碼:
/* 包含 Block 實(shí)際函數(shù)指針的結(jié)構(gòu)體 */
struct __block_impl {
void *isa;
int Flags;
int Reserved; // 今后版本升級(jí)所需的區(qū)域大小
void *FuncPtr; // 函數(shù)指針
};
/* Block 結(jié)構(gòu)體 */
struct __main_block_impl_0 {
// impl:Block 的實(shí)際函數(shù)指針柑船,指向包含 Block 主體部分的 __main_block_func_0 結(jié)構(gòu)體
struct __block_impl impl;
// Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結(jié)構(gòu)體
struct __main_block_desc_0* Desc;
// __main_block_impl_0:Block 構(gòu)造函數(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 主體部分結(jié)構(gòu)體 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("myBlock\n");
}
/* Block 附加信息結(jié)構(gòu)體:包含今后版本升級(jí)所需區(qū)域大小振劳,Block 的大小*/
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升級(jí)所需區(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結(jié)構(gòu)體
- 我們先來看看
__main_block_impl_0
結(jié)構(gòu)體( Block 結(jié)構(gòu)體)
/* Block 結(jié)構(gòu)體 */
struct __main_block_impl_0 {
// impl:Block 的實(shí)際函數(shù)指針椎组,指向包含 Block 主體部分的 __main_block_func_0 結(jié)構(gòu)體
struct __block_impl impl;
// Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結(jié)構(gòu)體
struct __main_block_desc_0* Desc;
// __main_block_impl_0:Block 構(gòu)造函數(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
結(jié)構(gòu)體(Block 結(jié)構(gòu)體)包含了三個(gè)部分:
- 成員變量
impl
; - 成員變量
Desc
指針; -
__main_block_impl_0
構(gòu)造函數(shù)。
struct __block_impl
結(jié)構(gòu)
我們先看看第一部分
impl
是__block_impl
結(jié)構(gòu)體類型的成員變量
/* 包含 Block 實(shí)際函數(shù)指針的結(jié)構(gòu)體 */
struct __block_impl {
void *isa; // 用于保存 Block 結(jié)構(gòu)體的實(shí)例指針
int Flags; // 標(biāo)志位
int Reserved; // 今后版本升級(jí)所需的區(qū)域大小
void *FuncPtr; // 函數(shù)指針
};
-
__block_impl
包含了 Block 實(shí)際函數(shù)指針FuncPtr
专筷,FuncPtr
指針指向 Block 的主體部分弱贼,也就是 Block 對(duì)應(yīng) OC 代碼中的^{ printf("myBlock\n"); };
部分。 - 還包含了標(biāo)志位
Flags
磷蛹,在實(shí)現(xiàn)block
的內(nèi)部操作時(shí)會(huì)用到 - 今后版本升級(jí)所需的區(qū)域大小
Reserved
-
__block_impl
結(jié)構(gòu)體的實(shí)例指針isa
吮旅。
static struct __main_block_desc_0
結(jié)構(gòu)
第二部分 Desc 是指向的是
__main_block_desc_0
類型的結(jié)構(gòu)體的指針型成員變量,__main_block_desc_0
結(jié)構(gòu)體用來描述該 Block 的相關(guān)附加信息:
/* Block 附加信息結(jié)構(gòu)體:包含今后版本升級(jí)所需區(qū)域大小味咳,Block 的大小*/
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升級(jí)所需區(qū)域大小
size_t Block_size; // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_impl_0
構(gòu)造函數(shù)
第三部分是
__main_block_impl_0
結(jié)構(gòu)體(Block 結(jié)構(gòu)體) 的構(gòu)造函數(shù)庇勃,負(fù)責(zé)初始化__main_block_impl_0
結(jié)構(gòu)體(Block 結(jié)構(gòu)體) 的成員變量。
__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;
}
- 關(guān)于結(jié)構(gòu)體構(gòu)造函數(shù)中對(duì)各個(gè)成員變量的賦值槽驶,我們需要先來看看
main()
函數(shù)中责嚷,對(duì)該構(gòu)造函數(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)換罕拂,使之簡潔一點(diǎ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
構(gòu)造函數(shù)爆班,生成的__main_block_impl_0
結(jié)構(gòu)體(Block 結(jié)構(gòu)體)類型實(shí)例的指針,賦值給__main_block_impl_0
結(jié)構(gòu)體(Block 結(jié)構(gòu)體)類型的指針變量myBlock
辱姨。
可以看到柿菩, 調(diào)用 __main_block_impl_0
構(gòu)造函數(shù)的時(shí)候,傳入了兩個(gè)參數(shù)雨涛。
-
第一個(gè)參數(shù):
__main_block_func_0
枢舶。- 其實(shí)就是
Block
對(duì)應(yīng)的主體部分,可以看到下面關(guān)于__main_block_func_0
結(jié)構(gòu)體的定義 镜悉,和OC
代碼中^{ printf("myBlock\n"); };
部分具有相同的表達(dá)式祟辟。 - 這里參數(shù)中的
__cself
是指向 Block 的值的指針變量,相當(dāng)于OC
中的self
侣肄。
/* Block 主體部分結(jié)構(gòu)體 */ static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("myBlock\n"); }
- 其實(shí)就是
-
第二個(gè)參數(shù):
__main_block_desc_0_DATA
:__main_block_desc_0_DATA
包含該 Block 的相關(guān)信息旧困。
我們?cè)賮斫Y(jié)合之前的__main_block_impl_0
結(jié)構(gòu)體定義。-
__main_block_impl_0
結(jié)構(gòu)體(Block 結(jié)構(gòu)體)可以表述為:
struct __main_block_impl_0 { void *isa; // 用于保存 Block 結(jié)構(gòu)體的實(shí)例指針 int Flags; // 標(biāo)志位 int Reserved; // 今后版本升級(jí)所需的區(qū)域大小 void *FuncPtr; // 函數(shù)指針 struct __main_block_desc_0* Desc; // Desc:Desc 指針 };
-
__main_block_impl_0
構(gòu)造函數(shù)可以表述為:
impl.isa = &_NSConcreteStackBlock; // isa 保存 Block 結(jié)構(gòu)體實(shí)例 impl.Flags = 0; // 標(biāo)志位賦值 impl.FuncPtr = __main_block_func_0; // FuncPtr 保存 Block 結(jié)構(gòu)體的主體部分 Desc = &__main_block_desc_0_DATA; // Desc 保存 Block 結(jié)構(gòu)體的附加信息
-
調(diào)用
在分析了Block結(jié)構(gòu)體和成員變量,現(xiàn)在我們看看main函數(shù)中是如何調(diào)用block的
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
-
myBlock
結(jié)構(gòu)體的第一個(gè)成員變量為__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的實(shí)質(zhì)總結(jié)
用一句話來說拗盒,Block是個(gè)對(duì)象
- 在C語言的底層實(shí)現(xiàn)里怖竭,它是一個(gè)結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體相當(dāng)于
objc_class
的類對(duì)象結(jié)構(gòu)體陡蝇,用_NSConcreteStackBlock
對(duì)其中成員變量isa
初始化痊臭,_NSConcreteStackBlock
相當(dāng)于class_t
結(jié)構(gòu)體實(shí)例(在我的理解中就是該block
實(shí)例的元類)。在將Block
作為OC
對(duì)象處理時(shí)登夫,關(guān)于該類的信息放置于_NSConcreteStackBlock
中广匙。
- 是對(duì)象:其內(nèi)部第一個(gè)成員為
isa
指針;- 封裝了函數(shù)調(diào)用:
Block
內(nèi)代碼塊恼策,封裝了函數(shù)調(diào)用鸦致,調(diào)用Block
,就是調(diào)用該封裝的函數(shù)涣楷;- 執(zhí)行上下文:
Block
還有一個(gè)描述Desc
分唾,該描述對(duì)象包含了Block
的信息以及捕獲變量的內(nèi)存相關(guān)函數(shù),及Block
所在的環(huán)境上下文狮斗;
Block的類型
前面已經(jīng)說過了绽乔,Block的本質(zhì)就是一個(gè)OC對(duì)象,既然它是OC對(duì)象情龄,那么它就有類型迄汛。
準(zhǔn)備工作:
- 先把ARC關(guān)掉,因?yàn)锳RC幫我們做了太多的事骤视,不方便我們觀察結(jié)果鞍爱。關(guān)掉ARC的方法在Build Settings里面搜索Objective-C Automatic Reference Counting,把這一項(xiàng)置為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;
}
}
//打印結(jié)果
__NSStackBlock__
__NSStackBlock
NSBlock
NSObject
- 這說明上面定義的這個(gè)
block
的類型是NSStackBlock
睹逃,并且它最終繼承自NSObject
也說明Block
的本質(zhì)是OC
對(duì)象。
Block的三種類型
-
Block有三種類型祷肯,分別是NSGlobalBlock,MallocBlock,NSStackBlock沉填。
這三種類型的Block
對(duì)象的存儲(chǔ)區(qū)域如下:
類 | 對(duì)象的存儲(chǔ)域 |
---|---|
NSStackBlock(_NSConcreteStackBlock) | 棧 |
NSGlobalBlock(_NSConcreteGlobalBlock) | 程序的數(shù)據(jù)區(qū)域(.data區(qū)) |
NSMallocBlock(_NSConcreteMallocBlock) | 堆 |
截獲了自動(dòng)變量的Block是NSStackBlock類型,沒有截獲自動(dòng)變量的Block則是NSGlobalStack類型,NSStackBlock類型的Block進(jìn)行copy操作之后其類型變成了NSMallocBlock類型佑笋。
- 下面我們用一張圖來看一下不同block的存儲(chǔ)區(qū)域
- 從上圖可以發(fā)現(xiàn)翼闹,根據(jù)
block
的類型不同,block存放在不同的區(qū)域中蒋纬。- 數(shù)據(jù)段中的
__NSGlobalBlock__
直到程序結(jié)束才會(huì)被回收猎荠,不過我們很少使用到__NSGlobalBlock__
類型的block
坚弱,因?yàn)檫@樣使用block
并沒有什么意義。 -
__NSStackBlock__
類型的block
存放在棧中关摇,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放荒叶,作用域執(zhí)行完畢之后就會(huì)被立即釋放,而在相同的作用域中定義block
并且調(diào)用block
似乎也多此一舉输虱。 -
__NSMallocBlock__
是在平時(shí)編碼過程中最常使用到的些楣。存放在堆中需要我們自己進(jìn)行內(nèi)存管理。
- 數(shù)據(jù)段中的
-
_NSConcreteGlobalBlock
- 在以下兩種情況下使用
Block
的時(shí)候宪睹,Block
為NSConcreteGlobalBlock
類對(duì)象愁茁。
- 記述全局變量的地方,使用
Block
語法時(shí)亭病; -
Block
語法的表達(dá)式中沒有截獲的自動(dòng)變量時(shí)埋市。
NSConcreteGlobalBlock
類的Block
存儲(chǔ)在『程序的數(shù)據(jù)區(qū)域』。因?yàn)榇娣旁诔绦虻臄?shù)據(jù)區(qū)域命贴,所以即使在變量的作用域外,也可以通過指針安全的使用食听。記述全局變量的地方胸蛛,使用 Block 語法示例代碼:
void (^myGlobalBlock)(void) = ^{ printf("GlobalBlock\n"); }; int main() { myGlobalBlock(); return 0; }
通過對(duì)應(yīng)
C++
源碼,我們可以發(fā)現(xiàn):Block
結(jié)構(gòu)體的成員變量isa
賦值為:impl.isa = &_NSConcreteGlobalBlock;
樱报,說明該Block
為NSConcreteGlobalBlock
類對(duì)象葬项。 - 在以下兩種情況下使用
_NSConcreteStackBlock
除了_NSConcreteGlobalBlock中提到的兩種情形,其他情形下創(chuàng)建的 Block
都是 NSConcreteStackBlock
對(duì)象迹蛤,平常接觸的 Block
大多屬于 NSConcreteStackBlock
對(duì)象民珍。
NSConcreteStackBlock
類的 Block
存儲(chǔ)在『棧區(qū)』的。如果其所屬的變量作用域結(jié)束盗飒,則該 Block 就會(huì)被廢棄嚷量。如果 Block 使用了 __block
變量,則當(dāng) __block
變量的作用域結(jié)束逆趣,則 __block
變量同樣被廢棄蝶溶。
- _NSConcreteMallocBlock
為了解決棧區(qū)上的 Block
在變量作用域結(jié)束被廢棄這一問題,Block
提供了 『復(fù)制』功能宣渗《端可以將 Block 對(duì)象和 __block
變量從棧區(qū)復(fù)制到堆區(qū)上。當(dāng) Block
從棧區(qū)復(fù)制到堆區(qū)后痕囱,即使棧區(qū)上的變量作用域結(jié)束時(shí)田轧,堆區(qū)上的 Block
和 __block
變量仍然可以繼續(xù)存在,也可以繼續(xù)使用鞍恢。
此時(shí)傻粘,『堆區(qū)』上的 Block 為 NSConcreteMallocBlock
對(duì)象每窖,Block 結(jié)構(gòu)體的成員變量 isa 賦值為:impl.isa = &_NSConcreteMallocBlock;
那么,什么時(shí)候才會(huì)將 Block 從棧區(qū)復(fù)制到堆區(qū)呢抹腿?
這就涉及到了 Block 的自動(dòng)拷貝和手動(dòng)拷貝岛请。
Block的自動(dòng)拷貝和手動(dòng)拷貝
Block的自動(dòng)拷貝
在使用ARC
時(shí),大多數(shù)情形下編譯器會(huì)自動(dòng)進(jìn)行判斷警绩,自動(dòng)生成將 Block
從棧上復(fù)制到堆上的代碼:
- 將
Block
作為函數(shù)返回值返回時(shí)崇败,會(huì)自動(dòng)拷貝; - 向方法或函數(shù)的參數(shù)中傳遞
Block
時(shí)肩祥,使用以下兩種方法的情況下后室,會(huì)進(jìn)行自動(dòng)拷貝,否則就需要手動(dòng)拷貝:-
Cocoa
框架的方法且方法名中含有usingBlock
等時(shí)混狠; -
Grand Central Dispatch(GCD)
的 API岸霹。
-
- 將
Block
賦值給類的附有__strong
修飾符的id
類型或 Block 類型成員變量時(shí)
Block的手動(dòng)拷貝
我們可以通過『copy 實(shí)例方法(即 alloc / new / copy / mutableCopy
)』來對(duì) Block
進(jìn)行手動(dòng)拷貝。當(dāng)我們不確定 Block
是否會(huì)被遺棄将饺,需不需要拷貝的時(shí)候贡避,直接使用 copy
實(shí)例方法即可,不會(huì)引起任何的問題予弧。
關(guān)于 Block 不同類的拷貝效果總結(jié)如下:
Block 類 | 存儲(chǔ)區(qū)域 | 拷貝效果 |
---|---|---|
_NSConcreteStackBlock | 棧區(qū) | 從椆伟桑拷貝到堆 |
_NSConcreteGlobalBlock | 程序的數(shù)據(jù)區(qū)域 | 不做改變 |
_NSConcreteMallocBlock | 堆區(qū) | 引用計(jì)數(shù)增加 |
__block變量的拷貝
在使用 __block
變量的 Block
從棧復(fù)制到堆上時(shí),__block
變量也會(huì)受到如下影響:
__block 變量的配置存儲(chǔ)區(qū)域 | Block 從棧復(fù)制到堆時(shí)的影響 |
---|---|
堆區(qū) | 從棧復(fù)制到堆掖蛤,并被 Block 所持有 |
棧區(qū) | 被 Block 所持有 |
當(dāng)然杀捻,如果不再有 Block
引用該 __block
變量,那么 __block
變量也會(huì)被廢除蚓庭。
Block截獲變量實(shí)質(zhì)
我們下面見根據(jù)變量修飾符致讥,來探查 Block 如何捕獲不同修飾符的類型變量。
- auto:自動(dòng)變量修飾符
- static:靜態(tài)修飾符
- const:常量修飾符
在這三種修飾符器赞,我們又細(xì)分為全局變量和局部變量垢袱。
Block截獲自動(dòng)局部變量的實(shí)質(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(); // 輸出結(jié)果:a = 10, b = 20, c = 30
a = 20;
b = 30;
myLocalBlock(); // 輸出結(jié)果:a = 10, b = 20, c = 30
Block(a, b, c); // 輸出結(jié)果:a = 20, b = 30, c = 30
}
Block塊中直接調(diào)用局部變量
- 從中我們可以看出拳魁,我們?cè)诘谝淮握{(diào)用
myLocalBlock();
之后已經(jīng)重新給變量a
惶桐、變量b
賦值了,但是第二次調(diào)用myLocalBlock();
的時(shí)候潘懊,使用的還是之前對(duì)應(yīng)變量的值姚糊。
這是因?yàn)锽lock 語法的表達(dá)式使用的是它之前申明的局部變量
a
、變量b
授舟。Blocks中救恨,Block表達(dá)式截獲所使用的局部變量的值,保存了該變量的瞬時(shí)值释树。所以再第二次執(zhí)行Block表達(dá)式的時(shí)候肠槽,即使已經(jīng)改變了局部變量a
和b
的值擎淤,也不會(huì)影響B(tài)lock表達(dá)式在執(zhí)行時(shí)所保存的局部變量的瞬時(shí)值。這就是Block變量截獲局部變量值的特性
??:
Block
語法表達(dá)式中沒有使用的自動(dòng)變量不會(huì)被追加到結(jié)構(gòu)體中秸仙,Blocks
的自動(dòng)變量截獲只針對(duì)Block
中使用的自動(dòng)變量嘴拢。可是,為什么 Blocks 變量使用的是局部變量的瞬時(shí)值寂纪,而不是局部變量的當(dāng)前值呢席吴?讓我們看一下對(duì)應(yīng)的
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
結(jié)構(gòu)體(Block 結(jié)構(gòu)體)中多了兩個(gè)成員變量a
和b
,這兩個(gè)變量就是 Block 截獲的局部變量捞蛋。a
和b
的值來自與__main_block_impl_0
構(gòu)造函數(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
主體部分的結(jié)構(gòu)體)中,變量a拟杉、b
的值使用的__cself
獲取的值庄涡。
而__cself->a
、__cself->b
是通過值傳遞的方式傳入進(jìn)來的搬设,而不是通過指針傳遞穴店。這也就說明了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);
}
- 我們可以看出全局變量并沒有存儲(chǔ)在Block的結(jié)構(gòu)體中贞言,而是在調(diào)用的時(shí)候通過直接訪問的方式來調(diào)用。
- 下面用一張圖我們把上面所說的全局作用域和局部作用域做一個(gè)總結(jié)
變量類型 | 抓獲到Block對(duì)象內(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); // 輸出結(jié)果: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
結(jié)構(gòu)體中沒有變量a
阀蒂、變量b
,說明通過直接傳值的方式该窗,變量并沒有存進(jìn)Block
的結(jié)構(gòu)體中。 - 在
__main_block_func_1
函數(shù)中蚤霞,發(fā)現(xiàn)參數(shù)列表中多了int a, int b
這兩個(gè)參數(shù)酗失,還有調(diào)用Block
的時(shí)候,直接把變量a
昧绣、b
的值傳入進(jìn)去了规肴。
Block截獲static修飾變量的實(shí)質(zhì)
下面我們定義了三個(gè)變量:
- 全局
- 變量: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(); // 輸出結(jié)果:a = 10, b = 50, c = 60
}
- 從以上測試結(jié)果我們可以得出:
-
Block
對(duì)象能獲取最新的靜態(tài)全局變量和靜態(tài)局部變量; - 靜態(tài)局部常量由于值不會(huì)更改夜畴,故沒有變化拖刃;
-
- 我們來看看
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對(duì)象獲取到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地址進(jìn)行Blcok對(duì)象的初始化
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
結(jié)構(gòu)體中税灌,靜態(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
結(jié)構(gòu)體部分洛勉,靜態(tài)全局變量static int c
是直接訪問的,靜態(tài)局部變量static int b
是通過『指針傳遞』的方式進(jìn)行訪問如迟,靜態(tài)局部常量static const int a
也是通過『指針傳遞』的方式進(jìn)行訪問收毫,但是它是通過const
修飾的無法進(jìn)行賦值操作。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//2.通過Block對(duì)象獲取到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ū)域不管是全局還是局部劳吠,不管是常量還是變量引润,均存儲(chǔ)在全局存儲(chǔ)區(qū)中,存在全局存儲(chǔ)區(qū)痒玩,該地址在程序運(yùn)行過程中一直不會(huì)改變淳附,所以能訪問最新值。 -
static
修飾后:
- 全局變量蠢古,直接訪問奴曙。
- 局部變量,指針訪問草讶。其中在局部變量中洽糟,又有局部靜態(tài)常量,即被
const
修飾的堕战。-
const
存放在text
段中坤溃,即使被static
同時(shí)修飾,也存放text
中的常量區(qū)嘱丢;
-
-
Block截獲const修飾變量的實(shí)質(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(); // 輸出結(jié)果: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
局部變量,其實(shí)仍然是auto
修飾越驻,值傳遞汁政;
-
最后我們用一張圖來總結(jié)一下這一節(jié)所學(xué)的內(nèi)容
Block截獲對(duì)象實(shí)質(zhì)
- 在前一節(jié)我們學(xué)習(xí)了
Block
如何截獲基本類型,這一節(jié)我們主要學(xué)習(xí)截獲對(duì)象類型的auto
變量
Person *person = [[Person alloc] init];
person.age = 20;
void (^block)(void) = ^{
NSLog(@"age = %d", person.age);
};
block();
- 根據(jù)
Block
捕獲基本變量的規(guī)律缀旁,針對(duì)對(duì)象记劈,仍然適用-
auto
變量捕獲后,Block
中變量的類型和變量原類型一致并巍; -
static
變量捕獲后抠蚣,Block
對(duì)應(yīng)的變量是對(duì)應(yīng)變量的指針類型;
-
那么履澳,auto
對(duì)象與基本類型在 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
結(jié)構(gòu)體中多了一個(gè)一個(gè)成員變量Person *person
怀跛,因?yàn)?code>person是自動(dòng)變量,所以這里捕獲了自動(dòng)變量person
作為_main_block_impl_0
結(jié)構(gòu)體的成員變量柄冲。而且還要注意的是吻谋,由于是自動(dòng)變量,所以在block外面现横,自動(dòng)變量是什么類型漓拾,在結(jié)構(gòu)體里面作為成員變量就是什么類型。person在結(jié)構(gòu)體外面作為自動(dòng)變量是指針類型戒祠,所以作為結(jié)構(gòu)體里面的成員變量也是指針類型骇两。
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
結(jié)構(gòu)中多了兩個(gè)函數(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*/);
}
針對(duì)這兩個(gè)函數(shù),它們的作用就是:
函數(shù) | 作用 | 調(diào)用時(shí)機(jī) |
---|---|---|
__main_block_copy_0 | 調(diào)用 _Block_object_assign 姜盈,相當(dāng)于 retain低千,將對(duì)象賦值在對(duì)象類型的結(jié)構(gòu)體變量 __main_block_impl_0 中。 |
棧上的 Block 復(fù)制到堆時(shí) |
__main_block_dispose_0 | 調(diào)用 _Block_object_dispose 馏颂,相當(dāng)于 release示血,釋放賦值在對(duì)象類型的結(jié)構(gòu)體變量中的對(duì)象。 |
堆上 Block 被廢棄時(shí) |
Blocks內(nèi)改寫被截獲變量的值的方式
在Block中修飾被截獲變量的值只有一下兩種情況救拉,我們先分析通過
__block
修飾符來修改截獲變量的方式
__block修飾符
-
__block 說明符
類似于static
难审、auto
、register
說明符亿絮,它們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中告喊。例如auto
表示作為自動(dòng)變量存儲(chǔ)在棧中,static
表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中派昧。
__block
修飾符又分為修飾局部變量和修飾對(duì)象
__blcok修飾局部變量
- (void)useBlockQualifierChangeLocalVariables {
__block int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
a = 20;
b = 30;
printf("a = %d, b = %d\n",a, b); // 輸出結(jié)果: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;
}
可以看到,只是加上了一個(gè)
__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;
}
};
我們?cè)?
__main_block_impl_0
結(jié)構(gòu)體中可以看到: 原 OC 代碼中,被__block
修飾的局部變量__block int a
实苞、__block int b
分別變成了__Block_byref_a_0
豺撑、__Block_byref_b_1
類型的結(jié)構(gòu)體指針a
、結(jié)構(gòu)體指針b
黔牵。這里使用結(jié)構(gòu)體指針a
聪轿、結(jié)構(gòu)體指針b
說明_Block_byref_a_0
、__Block_byref_b_1
類型的結(jié)構(gòu)體并不在__main_block_impl_0
結(jié)構(gòu)體中猾浦,而只是通過指針的形式引用陆错,這是為了可以在多個(gè)不同的 Block 中使用__block
修飾的變量灯抛。__Block_byref_a_0
、__Block_byref_b_1
類型的結(jié)構(gòu)體聲明如下:
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;
};
-
拿第一個(gè)
__Block_byref_a_0
結(jié)構(gòu)體定義來說明音瓷,__Block_byref_a_0
有 5 個(gè)部分:__isa
:標(biāo)識(shí)對(duì)象類的isa
實(shí)例變量__forwarding
:傳入變量的地址__flags
:標(biāo)志位__size
:結(jié)構(gòu)體大小a
:存放實(shí)變量a
實(shí)際的值对嚼,相當(dāng)于原局部變量的成員變量(和之前不加__block修飾符的時(shí)候一致)。
再來看一下
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
};
還是拿第一個(gè)
__Block_byref_a_0 a
的賦值來說明杏愤。可以看到
__isa
指針值傳空靡砌,__forwarding
指向了局部變量a
本身的地址,__flags
分配了 0珊楼,__size
為結(jié)構(gòu)體的大小通殃,a
賦值為 10。下圖用來說明__forwarding
指針的指向情況亥曹。
這下邓了,我們知道
__forwarding
其實(shí)就是局部變量a
本身的地址,那么我們就可以通過__forwarding
指針來訪問局部變量媳瞪,同時(shí)也能對(duì)其進(jìn)行修改了骗炉。來看一下 Block 主體部分對(duì)應(yīng)的
__main_block_func_0
結(jié)構(gòu)體來驗(yàn)證一下。
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 的主體部分中改變值的原理其實(shí)是:通過『指針傳遞』的方式。
__block修飾對(duì)象
- (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;
}
};
- 我們?cè)?
__main_block_impl_0
結(jié)構(gòu)體中可以看到: 原OC
代碼中兢仰,被__block
修飾的person
變成了__Block_byref_person_0
類型結(jié)構(gòu)體指針 -
__Block_byref_person_0
類型結(jié)構(gòu)體聲明和該結(jié)構(gòu)體初始化如下:
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
中多了兩個(gè)函數(shù),通過其初始化可以知道這兩個(gè)函數(shù)分別是__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
這兩個(gè)函數(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);
}
- 這兩個(gè)函數(shù)其實(shí)和
_main_block_copy_0
和_main_block_dispose_0
一樣把将,最終都是調(diào)用_Block_object_assign
和_Block_object_dispose
這兩個(gè)函數(shù)轻专。那么這里為什么都加上了40呢俏讹?我們分析一下_Block_byref_person_0
的結(jié)構(gòu):
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
結(jié)構(gòu)體中礼旅,將靜態(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 主體部分對(duì)應(yīng)的
__main_block_func_0
結(jié)構(gòu)體部分。靜態(tài)全局變量static_global_val
诡挂、全局變量global_val
是直接訪問的碎浇,而靜態(tài)局部變量static_val
則是通過『指針傳遞』的方式進(jìn)行訪問和賦值。
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)變量的這種方法似乎也適用于自動(dòng)變量的訪問璃俗,但我們?yōu)槭裁礇]有這么做呢奴璃?
實(shí)際上,在由 Block 語法生成的值 Block 上城豁,可以存有超過其變量域的被截獲對(duì)象的自動(dòng)變量苟穆。變量作用域結(jié)束的同時(shí),原來的自動(dòng)變量被廢棄唱星,因此 Block 中超過變量作用域而存在的變量同靜態(tài)變量一樣雳旅,將不能通過指針訪問原來的自動(dòng)變量。
最后我們用一張圖來總結(jié)一下這一節(jié)所學(xué)
__block
變量內(nèi)存管理
我們先回顧一下之前的一些捕獲變量或?qū)ο笫侨绾喂芾韮?nèi)存的间聊。
-
注:下面 “干預(yù)” 是指不用程序員手動(dòng)管理攒盈,其實(shí)本質(zhì)還是要系統(tǒng)管理內(nèi)存的分配與釋放。
-
auto
局部基本類型變量哎榴,因?yàn)槭侵祩鬟f型豁,內(nèi)存是跟隨 Block,不用干預(yù)尚蝌; -
static
局部基本類型變量迎变,指針傳遞,由于分配在靜態(tài)區(qū)飘言,故不用干預(yù)衣形; - 全局變量,存儲(chǔ)在數(shù)據(jù)區(qū)姿鸿,不用多說谆吴,不用干預(yù);
- 局部對(duì)象變量般妙,如果在棧上,不用干預(yù)相速。但
Block
在拷貝到堆的時(shí)候碟渺,對(duì)其retain
,在Block
對(duì)象銷毀時(shí),對(duì)其release
苫拍;
-
在這里芜繁,__block
變量呢?
注意點(diǎn)就是:__block 變量在轉(zhuǎn)換后封裝成了一個(gè)新對(duì)象绒极,內(nèi)存管理會(huì)多出一層骏令。
基本類型的Desc
上述 age
是基本類型,其轉(zhuǎn)換后的結(jié)構(gòu)體為:
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};
//下面兩個(gè)方法是Block內(nèi): 內(nèi)存管理相關(guān)函數(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*/);
}
針對(duì)基本類型垄提,以 age
類型為例:
-
__Block_byref_age_0
對(duì)象同樣是在Block對(duì)象
從棧上拷貝到堆上榔袋,進(jìn)行retain
; - 當(dāng)
Block對(duì)象
銷毀時(shí)铡俐,對(duì)__Block_byref_age_0
進(jìn)行release
凰兑; -
__Block_byref_age_0
內(nèi)age
,由于是基本類型审丘,是不用進(jìn)行內(nèi)存手動(dòng)干預(yù)的吏够。
對(duì)象類型的Desc
下面看__block
對(duì)象類型的轉(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é)
};
因?yàn)椴东@的本身是一個(gè)對(duì)象類型,所以該對(duì)象類型還需要進(jìn)行內(nèi)存的干預(yù)滩报。
這里有兩個(gè)熟悉的函數(shù)锅知,即用于管理對(duì)象 auto 變量時(shí),我們見過脓钾,用于管理對(duì)象 auto 的內(nèi)存:
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
- 那么這兩個(gè)函數(shù)對(duì)應(yīng)的實(shí)現(xiàn)售睹,我們也找出來:
初始化__block對(duì)象
下面針對(duì)轉(zhuǎn)換來轉(zhuǎn)換去的細(xì)節(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ù)中侣姆,都會(huì)偏移 40 字節(jié),我們?cè)倏?code>__block BFPerson 對(duì)象轉(zhuǎn)換后的__Block_byref_person_1
結(jié)構(gòu)體發(fā)現(xiàn)沉噩,其 40 字節(jié)偏移處就是原本的BFPerson *person
對(duì)象捺宗。
對(duì)象類型的內(nèi)存管理
以 BFPerson *person
,在__block
修飾后川蒙,轉(zhuǎn)換為:__Block_byref_person_1
對(duì)象:
-
__Block_byref_person_1
對(duì)象同樣是在Block對(duì)象
從棧上拷貝到堆上蚜厉,進(jìn)行retain
;- 當(dāng)
__Block_byref_person_1
進(jìn)行retain
同時(shí)畜眨,會(huì)將person
對(duì)象進(jìn)行 retain
- 當(dāng)
- 當(dāng)
Block對(duì)象
銷毀時(shí)昼牛,對(duì)__Block_byref_person_1
進(jìn)行release
- 當(dāng)
__Block_byref_person_1
對(duì)象release
時(shí),會(huì)將person
對(duì)象release
- 當(dāng)
與auto對(duì)象變量的區(qū)別
從棧到堆
Block 從棧復(fù)制到堆時(shí)康聂,__block 變量產(chǎn)生的影響如下:
__block 變量的配置存儲(chǔ)域 | Block 從棧復(fù)制到堆的影響 |
---|---|
棧 | 從棧復(fù)制到堆贰健,并被 Block 持有 |
堆 | 被 Block 持有 |
Block從棧拷貝到堆
當(dāng)有多個(gè) Block 對(duì)象伶椿,持有同一個(gè)__block 變量。
- 當(dāng)其中任何 Block 對(duì)象復(fù)制到堆上,__block 變量就會(huì)復(fù)制到堆上脊另。
- 后續(xù)导狡,其他 Block 對(duì)象復(fù)制到堆上,__block 對(duì)象引用計(jì)數(shù)會(huì)增加偎痛。
- Block 復(fù)制到堆上的對(duì)象旱捧,持有__block 對(duì)象。
Block銷毀
總結(jié)
更多細(xì)節(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);
//輸出結(jié)果
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
,均在堆空間靖榕。
其實(shí)标锄,本質(zhì)上,將 Block
從椬录疲拷貝到堆料皇,也會(huì)將__block
對(duì)象一并拷貝到堆,如下圖:
對(duì)象與__block變量的區(qū)別
__block BFPerson *blockPerson = [[BFPerson alloc] init];
BFPerson *objectPerson = [[BFPerson alloc] init];
void(^block)(void) = ^ {
NSLog(@"person is %@ %@", blockPerson, objectPerson);
};
轉(zhuǎn)換后:
//Block對(duì)象
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
BFPerson *objectPerson; //objectPerson對(duì)象星压,捕獲
__Block_byref_blockPerson_0 *blockPerson; // blockPerson封裝后的對(duì)象践剂,內(nèi)部捕獲blockPerson
};
//__block blockPerson封裝后的對(duì)象
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;
};
//兩種對(duì)象不同的處理方式
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)部對(duì)__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);
}
從上面可以得出