一毯盈、block 引用外部變量時,對外部變量的捕獲(capture)情況
1. 引用全局變量的block病袄,能簡單說下ta的底層結(jié)構(gòu)嗎搂赋?
#import <Foundation/Foundation.h>
static int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^lspBlock)(void) = ^{
NSLog(@"我是簡單的block:%d",age);
};
lspBlock();
}
return 0;
}
- 使用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
將代碼轉(zhuǎn)換成 C++代碼,有助于我們觀察代碼的本質(zhì)
- 轉(zhuǎn)換獲得如下代碼
- 從上圖益缠,我們可以獲得一些信息:
- ① block內(nèi)部有
isa
指針脑奠,說明 Block 是一個 OC類
- ② block 類中存儲著
需要運行的函數(shù)指針FuncPtr
,以及對 block的描述信息 __main_block_desc_0
- ③
__mian_block_impl_0
結(jié)構(gòu)體中沒有 age
的信息幅慌,說明block
沒有對全局變量 age
進行捕獲
2. 引用局部靜態(tài)變量的block捺信,能簡單說下ta的底層結(jié)構(gòu)嗎?
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
void (^lspBlock)(void) = ^{
NSLog(@"我是簡單的block:%d",age);
};
lspBlock();
}
return 0;
}
- 從轉(zhuǎn)換后的代碼欠痴,我們可以清晰的看到迄靠,對于靜態(tài)局部變量,1block
會采取捕獲指針的方式喇辽,拿到
&age的地址值掌挚,賦值給
__main_block_impl_0` 結(jié)構(gòu)體對象
3. 引用局部變量的block,能簡單說下ta的底層結(jié)構(gòu)嗎菩咨?
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^lspBlock)(void) = ^{
NSLog(@"我是簡單的block:%d",age);
};
lspBlock();
}
return 0;
}
- 我們可以發(fā)現(xiàn)吠式,
__main_block_impl_0
中傳遞的是 age
這個值,說明block捕獲局部變量時抽米,是值捕獲特占。
4. 接下來我們總結(jié)一下 block引用外部變量,在各種情況下的捕獲情況
5. 明白了 block 捕獲原理后云茸,我們來強化一下是目,請問下面代碼的輸出是多少?
#import <Foundation/Foundation.h>
int globalAge = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
static int staticAge = 30;
void (^lspBlock1)(void) = ^{
NSLog(@"我是簡單的block:%d",globalAge);
};
void (^lspBlock2)(void) = ^{
NSLog(@"我是簡單的block:%d",age);
};
void (^lspBlock3)(void) = ^{
NSLog(@"我是簡單的block:%d",staticAge);
};
globalAge++;
age++;
staticAge++;
lspBlock1();
lspBlock2();
lspBlock3();
}
return 0;
}
Demo[6041:138667] 我是簡單的block:11
Demo[6041:138667] 我是簡單的block:20
Demo[6041:138667] 我是簡單的block:31
6.請問下面的 block 有捕獲變量嗎揉抵?捕獲的是誰?
#import "Person.h"
@interface Person ()
@property(nonatomic, assign) int age;
@end
@implementation Person
- (void)lspTest {
void (^block)(void) = ^{
NSLog(@"%i", _age);
};
block();
}
@end
-
_age
實際上是 self->age
嗤疯,而 self
是一個局部實例變量冤今,對于局部變量,block 會采取值捕獲 self
- 還有一個疑惑茂缚,思考為什么我們在類中調(diào)用方法戏罢,可以直接使用
self
和 _cmd
這兩個變量
- 這是因為OC底層替我們在方法中傳遞了這兩個參數(shù),比如上述代碼轉(zhuǎn)換后
static void _I_Person_lspTest(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__lspTest_block_impl_0((void *)__Person__lspTest_block_func_0, &__Person__lspTest_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
二脚囊、block 的類型
1. 除了 block 有 isa
指針這個方面來說龟糕,我們還可以從哪方面證明 block 是一個 OC 對象?
- 我們還可以在
MRC
下通過打印 block 的 class 和 superclass來說明凑术;
#import <Foundation/Foundation.h>
int globalAge = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
static int staticAge = 30;
void (^lspBlock1)(void) = ^{
NSLog(@"我是簡單的block:%d",globalAge);
};
void (^lspBlock2)(void) = ^{
NSLog(@"我是簡單的block:%d",age);
};
void (^lspBlock3)(void) = ^{
NSLog(@"我是簡單的block:%d",staticAge);
};
void (^lspBlock4)(void) = [lspBlock2 copy];
NSLog(@"lspBlock1: %@, %@, %@, %@",[lspBlock1 class],
[lspBlock1 superclass],
[[lspBlock1 superclass] superclass],
[[[lspBlock1 superclass] superclass] superclass]);
NSLog(@"lspBlock2: %@, %@, %@, %@",[lspBlock2 class],
[lspBlock2 superclass],
[[lspBlock2 superclass] superclass],
[[[lspBlock2 superclass] superclass] superclass]);
NSLog(@"lspBlock3: %@, %@, %@, %@",[lspBlock3 class],
[lspBlock3 superclass],
[[lspBlock3 superclass] superclass],
[[[lspBlock3 superclass] superclass] superclass]);
NSLog(@"lspBlock4: %@, %@, %@, %@",[lspBlock4 class],
[lspBlock4 superclass],
[[lspBlock4 superclass] superclass],
[[[lspBlock4 superclass] superclass] superclass]);
}
return 0;
}
Demo[6201:154701] lspBlock1: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock2: __NSStackBlock__, __NSStackBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock3: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
Demo[6201:154701] lspBlock4: __NSMallocBlock__, __NSMallocBlock, NSBlock, NSObject
- 我們發(fā)現(xiàn)block最終繼承于
NSObject
,再次證明 block 底層就是OC對象
- 我們還發(fā)現(xiàn)所意,我們打印的block中主要有三種類型
__NSGlobalBlock
淮逊、__NSStackBlock
、__NSMallocBlock
- 還發(fā)現(xiàn)扶踊,無論哪種類型的block都繼承于
NSBlock
2. 既然 block 有三種類型泄鹏,你能從它們的名字,區(qū)分出運行時秧耗,它們分別放在內(nèi)存的哪一段嗎备籽?
-
__NSGlobalBlock
放在數(shù)據(jù)區(qū),特點是:由系統(tǒng)管理分井,運行過程不會釋放
-
__NSStackBlock
放在棧區(qū)车猬,特點是:系統(tǒng)自動分配內(nèi)存,也會自動釋放內(nèi)存
-
__NSMallocBlock
放在堆區(qū)尺锚,特點是:需要程序員自己管理內(nèi)存
3. 既然 block 有三種類型珠闰,那對它們進行 copy 操作會有什么效果呢?
4. 你可能會遇到的疑惑瘫辩,如果將 1
中的代碼伏嗜,轉(zhuǎn)換成 c++ 代碼
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int age;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 我們重點注意這句話
impl.isa = &_NSConcreteStackBlock;
,我們發(fā)現(xiàn)無論哪種 block伐厌,C++代碼的都會是這句代碼
- 這句話的含義就是將
_NSConcreteStackBlock
這個類對象賦值給 isa
指針
- 也就說明承绸,從C++代碼層面,這些block都是
_NSConcreteStackBlock
挣轨,怎么回事呢军熏?
- 當(dāng)我們遇到這種情況,
一切以運行時的情況為準
卷扮,因為運行時的結(jié)果羞迷,才是我們真正的最終結(jié)果