目錄
- 變量捕獲
- 變量
---- 變量的基本知識
---- 為什么要捕獲變量?
---- 為什么局部變量需要捕獲钝吮?
---- 為什么全局變量不用捕獲奇瘦?
---- self會被捕獲嗎?
---- 成員變量會被捕獲嗎巧鸭?- 局部變量捕獲和數(shù)值修改探究
---- 局部自動auto
變量- 靜態(tài)static變量捕獲和數(shù)值修改探究
- 全局變量捕獲和數(shù)值修改探究
- __block修飾符
變量捕獲
如果
Block
的執(zhí)行體使用了外界的局部變量纲仍,為了保證Block
內(nèi)部能夠正常訪問外部的變量郑叠,Block
有一個捕獲變量的機制明棍。
相當(dāng)于往Block
結(jié)構(gòu)體里增加一個成員變量摊腋,把值傳遞給這個成員變量兴蒸,分為值傳遞
和指針傳遞
。
如果執(zhí)行體使用了外界的全局變量蕾殴,則不需要捕獲钓觉,直接使用即可荡灾。
變量
-
變量的基本知識
首先變量可以分為兩種:局部變量和全局變量瞬铸。
局部變量分為:局部自動auto變量和局部靜態(tài)static變量赴捞。
全局變量分為:全局變量和全局靜態(tài)static變量赦政。
-
為什么要捕獲變量耀怜?
因為變量有作用域的限制财破,在Block里面使用Block外聲明的局部變量左痢,相當(dāng)于跨函數(shù)使用這個局部變量系洛。
如果不存一份到Block里面描扯,是無法使用的绽诚,會造成訪問無效內(nèi)存,因為外面的局部變量有可能過了作用域就會自動被銷毀卒落。
-
為什么局部變量需要捕獲儡毕?
因為局部變量只能在方法內(nèi)部訪問妥曲,離開作用域(大括號)就會自動銷毀钦购。
-
為什么全局變量不用捕獲押桃?
因為作用域是全局唱凯,無論方法內(nèi)外都可以隨時訪問磕昼。
-
self會被捕獲嗎节猿?
會,因為self也是局部變量浸间,我們來回想一下魁蒜,在OC里調(diào)用方法實際上會傳遞self指針的參數(shù),而且捕獲的是指針吩翻,所以屬于引用傳遞狭瞎。
objc_msgSend(id self, SEL _cmd, ...)
所以我們之所以能在每一個方法中使用self脚作,就是因為默認傳入self變量球涛。
-
成員變量會被捕獲嗎?
會捺典,因為訪問的成員變量也是局部變量襟己。
局部變量捕獲和數(shù)值修改探究
- 局部變量只能在方法內(nèi)部訪問擎浴,離開作用域(大括號)就會自動銷毀。
- Block內(nèi)訪無法修改局部(自動
auto
)變量科吭。
-
局部自動
auto
變量
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定義一個局部變量
int global = 100;
NSLog(@"global Memory address is :%p ",&global);
// 定義一個Block
void (^aBlock)(void);
// 把Block指向一個代碼塊
aBlock = ^{
NSLog(@"in block global memory address is :%p ",&global);
NSLog(@"global is :%d",global);
};
// 修改局部變量的值
global = 101;
//調(diào)用Block
aBlock();
}
return 0;
}
global Memory address is :0x16fdff2bc
in block global memory address is :0x1011040a0
global is :100
從結(jié)果來看到在Block中
不可以直接修改
局部變量,且兩個global內(nèi)存首地址
不同唤冈。
我們把OC代碼
編譯成C/C++
來看下底層實現(xiàn)你虹,僅截取部分如下:
#pragma clang assume_nonnull end
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// Block結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int global; // Block捕獲變量后相當(dāng)于往Block結(jié)構(gòu)體里增加一個成員變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block的具體代碼區(qū)執(zhí)行區(qū)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int global = __cself->global; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_1,&global);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_2,global);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int global = 100;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_2eb08c_mi_0,&global);
void (*aBlock)(void);
aBlock = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
global) );
global = 101;
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- 我們看到首先在
main函數(shù)
中聲明并初始化局部變量int global = 100;
看杭。- 初始化
Block
楼雹,給__main_block_impl_0
這個結(jié)構(gòu)體傳入局部變量global
也就是數(shù)值100
贮缅,屬于值傳遞
介却。Block
捕獲變量后齿坷,往Block
結(jié)構(gòu)體中增加一個局部變量int global
永淌,來接收這個數(shù)值100
遂蛀。- 因為是
值傳遞
所以在main
函數(shù)中int global
的變化不會影響Block
中int global
的值,不存在關(guān)聯(lián)關(guān)系螃宙。- 具體細節(jié)可以看我上面的注釋谆扎。
靜態(tài)static變量捕獲和數(shù)值修改探究
靜態(tài)變量:用static修飾的變量燕酷,特點是在程序運行過程中,一直在內(nèi)存中存在饵蒂,且只能在方法內(nèi)部訪問退盯。
Block內(nèi)可以直接訪問和修改
靜態(tài)變量。
// 定義一個靜態(tài)變量
static int global = 100;
global Memory address is :0x102c68b20
in block global memory address is :0x102c68b20
global is :101
編譯成C/C++
代碼進行查看:
#pragma clang assume_nonnull end
// Block結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *global;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_global, int flags=0) : global(_global) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block的具體代碼區(qū)執(zhí)行區(qū)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *global = __cself->global; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_1,&(*global));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_2,(*global));
}
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 int global = 100;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_cc8127_mi_0,&global);
void (*aBlock)(void);
aBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &global));
global = 101;
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- 我們看到首先在
main函數(shù)
中聲明并初始化靜態(tài)局部變量static int global = 100;
毒租。- 初始化
Block
給__main_block_impl_0
這個結(jié)構(gòu)體傳入變量global的指針
墅垮,屬于指針傳遞
算色。Block
捕獲變量后相當(dāng)于往Block結(jié)
構(gòu)體里增加一個局部變量int *global
,來接收這個指針
峡钓。- 因為是
指針傳遞
椒楣,所以后續(xù)以后續(xù)在__main_block_func_0
具體執(zhí)行方法區(qū)中首先拿到global
的指針地址牡肉。通過地址訪問和修改內(nèi)存上的內(nèi)容
统锤。- 具體細節(jié)可以看我上面的注釋。
補充:static變量
一直在內(nèi)存中存在煌寇,所以Block
無論什么時候執(zhí)行阀溶,都可以訪問到static
的指針鸦泳。
全局變量捕獲和數(shù)值修改探究
全局變量:在程序運行過程中做鹰,一只在內(nèi)存中存在,可以被所有方法訪問钾麸。
Block內(nèi)可以直接訪問和修改
全局變量更振。
#import <Foundation/Foundation.h>
// 定義一個全局變量
int global = 100;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"global Memory address is :%p ",&global);
// 定義一個Block
void (^aBlock)(void);
// 把Block指向一個代碼塊
aBlock = ^{
NSLog(@"in block global memory address is :%p ",&global);
NSLog(@"global is :%d",global);
};
// 修改全局變量的值
global = 101;
//調(diào)用Block
aBlock();
}
return 0;
}
global Memory address is :0x102c68b20
in block global memory address is :0x102c68b20
global is :101
編譯成C/C++
代碼進行查看:
#pragma clang assume_nonnull end
int global = 100;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_1,&global);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_2,global);
}
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;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_527828_mi_0,&global);
void (*aBlock)(void);
aBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
global = 101;
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- Block沒有對全局變量進行捕獲炕桨。
- 全局變量可以被程序內(nèi)所有方法
直接訪問
,也就不用block
捕獲肯腕。- 剩下的這里不過多贅述了献宫。
__block修飾符
__block
可以用于解決Block內(nèi)部無法修改auto
變量值的問題。__block
不能修飾全局變量实撒、靜態(tài)static
變量。
如果我們想在block中修改局部變量的值奈惑,那就需要在定義局部變量的時候增加修飾詞__block
// 定義一個局部變量
__block int global = 100;
NSLog(@"global內(nèi)存首地址:%p ",&global);
void(^dBlock)(void) = ^{
NSLog(@"在Block內(nèi)global內(nèi)存首地址:%p ",&global);
global = 101;
NSLog(@"在Block內(nèi)global的值為:%d",global);
};
//調(diào)用Block,觀察global的值是否會被修改
dBlock();
NSLog(@"在Block外global的值為:%d",global);
global內(nèi)存首地址:0x101006308
在Block中g(shù)lobal內(nèi)存首地址:0x101064bc8
在Block內(nèi)global的值為:101
在Block外global的值為:101
結(jié)果所示:在定義局部變量的時候增加修飾詞
__block
吭净,就可以在Block
中修改局部變量的值。
這是為什么呢肴甸?我們接下來進行探究寂殉。把OC代碼
編譯成C/C++
來看下底層實現(xiàn)。部分代碼如下:
#pragma clang assume_nonnull end
// __Block修飾之后變成了一個結(jié)構(gòu)體
struct __Block_byref_global_0 {
void *__isa; // isa指針代表該結(jié)構(gòu)體也是一個OC對象
__Block_byref_global_0 *__forwarding; // 指針變量,指向結(jié)構(gòu)體自己的內(nèi)存首地址原在。
int __flags;
int __size;
int global; // 我們聲明的int global最終存放的位置
};
// Block
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 一個__Block_byref_global_0結(jié)構(gòu)體的對象,可以通過指針變量找到
__Block_byref_global_0 *global; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_global_0 *_global, int flags=0) : global(_global->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block的代碼區(qū)執(zhí)行位置
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_global_0 *global = __cself->global; // bound by ref
// global->__forwarding->global
// 通過__forwarding存放的指針地址找到我們初始化的那個__Block_byref_global_0的結(jié)構(gòu)體
// 在找到__Block_byref_global_0結(jié)構(gòu)體下面的global變量
// 最后進行賦值
(global->__forwarding->global) = 101;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_2,(global->__forwarding->global));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->global, (void*)src->global, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->global, 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;
// __block int global = 100 <==> 轉(zhuǎn)化成了一個__Block_byref_global_0結(jié)構(gòu)體對象
// 并對該對象進行初始化友扰,
// 其中__forwarding指針指向自己(&global代表了該指針對象的內(nèi)存首地址)
__attribute__((__blocks__(byref))) __Block_byref_global_0 global = {
(void*)0,
(__Block_byref_global_0 *)&global,
0,
sizeof(__Block_byref_global_0),
100};
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_0,&(global.__forwarding->global));
// 初始化Block
// 給__main_block_impl_0這個結(jié)構(gòu)體傳入 &global也就是global對象的指針地址
void(*dBlock)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_global_0 *)&global,
570425344));
((void (*)(__block_impl *))((__block_impl *)dBlock)->FuncPtr)((__block_impl *)dBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2_bxd08__54lzbn226msl27gww0000gn_T_main_7a782e_mi_3,(global.__forwarding->global));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我們可以發(fā)現(xiàn):
- 通過
__block
修飾后的局部變量global
已經(jīng)不再是一個局部變量了,而是一個__Block_byref_global_0
的結(jié)構(gòu)體對象庶柿。- 系統(tǒng)會把局部變量作為一個成員變量包裝進它體內(nèi)村怪。
- 會把
__forwarding
指針指向自己。
所以不能再把它當(dāng)成一個局部變量來分析了浮庐。
Block
是不可以直接捕獲這個__block變量global
的甚负。Block
在初始化的時候會捕獲__block變量global
的的指針地址,屬于指針傳遞
审残。- 在
Block代碼塊
中我們看到(global->__forwarding->global) = 101;
代表著會通過內(nèi)存直接訪問
的形式找到int global
并修改值梭域。- 具體細節(jié)可以看我上面的注釋。
-
這里就思考了:
為什么不直接在Block中存儲
global
呢搅轿,就類似靜態(tài)static
變量那樣病涨,偏偏搞個結(jié)構(gòu)體來存放global
呢?
-
我的想法:
結(jié)合Block三種類型的知識我們知道:
- 當(dāng)了一個
Block
訪問了auto變量
璧坟,那Block
的類型就是是__NSStackBlock__
類型既穆,存放在棧中。內(nèi)存由系統(tǒng)控制雀鹃,如果超過變量作用域就會被系統(tǒng)自動銷毀幻工。- 在
ARC
環(huán)境下如果block
訪問了auto變量
,編譯器會根據(jù)情況自動執(zhí)行copy
褐澎,變成__NSMallocBlock__
類型会钝,然后將棧上的block
復(fù)制到堆上。
當(dāng)
Block
從__NSStackBlock__
類型轉(zhuǎn)換成__NSMallocBlock__
類型的時候:
Block
會從棧中copy
到堆中工三,那block
在棧中的變量也會跟著copy
到堆中迁酸,讓堆Block
持有它。
并且讓棧__block變量
的__forwarding
指針指向堆上面的__block變量
俭正。
這樣奸鬓,無論是在Block
語法內(nèi)外使用__block變量
,還是__block變量
配置在棧上或堆上掸读,都可以順利地訪問同一個__block變量
串远。