寫一個簡單的block demo驗證分析一下block的本質:bash
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^myBlock)(void) = ^{
NSLog(@"this is a block");
};
myBlock();
NSLog(@"%@",myBlock);
}
return 0;
}
複製代碼
使用命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
編譯成C++代碼,找到C++代碼最後咱們能夠看到block被編譯成一個結構體iphone
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;
}
};
複製代碼
第一個屬性__block_impl
又是一個結構體函數
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製代碼
這裏isa
指針是指向了block的類型
,這個後面再說,在後面貼的C++代碼中能夠注意一下,都是block的類型。
咱們這個簡單的myBlock內部只有一句NSLog的打印,被封裝在了__main_block_func_0
這個函數中測試
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_26cca1_mi_0);
}
複製代碼
__main_block_func_0
的函數地址又做爲__main_block_impl_0
構造函數的第一個參數fp,被賦值給impl
的FuncPtr
ui
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
調用FuncPtr,也就是調用myBlock中封裝的函數。
根據以上對源碼的分析 ,也驗證了咱們最開始說的兩點block的本質。this
上面探究block本質的demo代碼特別簡單,下面咱們由深到淺的繼續探究block是如何獲取外部變量的。spa
所謂auto
變量也叫自動變量,就是離開做用域(他所在的{}
)就會自動銷燬,也就是局部變量,由於auto
能夠省略,因此咱們平時均可以直接定義局部變量,不須要在前面加上auto修飾符。
先看一段示例代碼:指針
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int age = 10;
void(^myBlock)(void) = ^{
NSLog(@"this is a block - %d ",age);
};
age = 20;
myBlock();
}
return 0;
}
複製代碼
那麼請問這段代碼的打印age的值是多少呢?有人以爲是20,有人以爲是10,這裏確定的告訴你們,或者你們手動寫一寫這段代碼測試一下打印結果,實際上是10.仍是用命令編譯成C++代碼一看究竟code
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_edbb92_mi_0,age);
}
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 age = 10;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
複製代碼
一樣的結構體,一樣的調用方式,上面說過的內容就不重複了,主要看局部變量age是如何被block獲取到的:cdn
__main_block_impl_0
結構體中添加了一個age
屬性,構造函數中也添加了age
,: age(_age)
是C++語法,表示默認執行age = _age
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
直接把age的值獲取到,而且保存在結構體內部,並且是值傳遞
age = 20;
雖然修改了age的值,可是結構體內部的age的值並不會發生改變與auto
相對的,還有static
修飾的變量,那麼block
對static
變量捕獲的方式和auto
變量有什麼不一樣呢?看下面的測試代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
auto int age = 10;
static int height = 50;
void(^myBlock)(void) = ^{
NSLog(@"this is a block - %d ,height is %d",age, height);
};
age = 20;
height = 100;
myBlock();
}
return 0;
}
複製代碼
輸出
block[87871:3465064] this is a block - 10 ,height is 100
複製代碼
很明顯咱們在block定義以後修改static變量的值,block內部是能夠知道的,再看C++代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_716be0_mi_0,age, (*height));
}
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;
auto int age = 10;
static int height = 50;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 20;
height = 100;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
複製代碼
根據上面編譯完的代碼能夠很明顯看到變量age和height被block獲取時的區別,height被結構體__main_block_impl_0
存儲的是地址。
繼續根據測試代碼和C++代碼,分析block對變量的捕獲
#import <Foundation/Foundation.h>
int age = 10;
static int height = 50;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^myBlock)(void) = ^{
NSLog(@"this is a block - %d ,height is %d",age, height);
};
age = 20;
height = 100;
myBlock();
}
return 0;
}
複製代碼
C++代碼:
#pragma clang assume_nonnull end
int age = 10;
static int height = 50;
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_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_1de4db_mi_0,age, height);
}
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;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
age = 20;
height = 100;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
複製代碼
能夠得出結論:
其實self
的捕獲,纔是開發中常常遇到的狀況,也是引發block循環引用的罪魁禍首
。固然self的捕獲能夠歸到上面兩類中,但仍是想拿出來單獨分析一下。也算是對上面兩類變量分析結果的實踐吧。
#import "Person.h"
@implementation Person
- (void)test_block {
void(^block)(void) = ^{
NSLog(@"this is a block - %p",self);
};
}
@end
複製代碼
先思考一下,這裏
self
會被捕獲到block中嘛,self在這裏是一個auto變量仍是static變量仍是全局變量呢?先分析出是什麼變量,下一步咱們才能知道self是以什麼形式被捕獲到block中或者會不會被捕獲到block中。
你知道答案了嗎?
看看下面的C++代碼:
struct __Person__test_block_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_block_desc_0* Desc;
Person *self;
__Person__test_block_block_impl_0(void *fp, struct __Person__test_block_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
能夠獲得結論,block是會捕獲self
的,但是self是個局部變量嘛?這裏又要提到method
的兩個默認參數了self
和_cmd
,代碼中的test方法被編譯成_I_Person_test_block
,他有兩個參數是self
和_cmd
,函數的參數就是局部變量
,而後被捕獲到block中賦值給struct中的self
static void _I_Person_test_block(Person * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__Person__test_block_block_impl_0((void *)__Person__test_block_block_func_0, &__Person__test_block_block_desc_0_DATA, self, 570425344));
}
複製代碼
同理,若是是block中訪問self的成員變量(self.name)
或者經過self調用別的方法([self method])
,也一樣會捕獲self到block中。