首發地址:block底層實現與變量捕獲objective-c
本文已經添加到專輯:《完全弄懂OC》。 歡迎加入個人QQ羣:661461410
,一塊兒探討iOS底層原理。shell
block又叫代碼塊,是OC語法中很是重要的一個概念,咱們先來看一下Block的簡單使用。bash
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{
NSLog(@"hello block");
}();
int d = 5;
void (^block)(int, int) = ^(int a, int b) {
int c = a + b + d;
NSLog(@"a + b + d = %d", c);
};
block(3, 4);
}
return 0;
}
複製代碼
上面的代碼中,咱們建立了兩個Block,一個直接執行,輸出Hello World
。 一個經過block變量進行調用,並引用了一個外部變量d。輸出12
。數據結構
咱們將以上代碼編譯成C代碼:iphone
# 在main.m所在目錄執行該命令。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
複製代碼
從main-arm64.cpp文件中,咱們能夠看到Block的結構以下:函數
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int d;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _d, int flags=0) : d(_d) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製代碼
咱們能夠看出Block的底層是結構體,__main_block_impl_1
包含一個變量impl
其結構和 Class
的結構相似,其包含一個isa
指針,可見Block本質上也是一個類,其中FuncPtr
表示要執行的代碼塊的函數地址。d
表示它引用的外部變量。佈局
下面,咱們一塊兒看一下Block的調用過程,首先咱們將下面代碼,編譯成C代碼。atom
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"hello block");
};
block();
}
return 0;
}
// 下面是編譯後的C代碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//能夠看得出來,Block的調用集中在這兩行。
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bv_k_7y193n6tvf34wjvqvnn3q40000gn_T_main_0422f2_mi_0);
}
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)};
複製代碼
針對,上面的兩行代碼,先調用__main_block_impl_0
的結構體構造函數,建立Block,並將地址賦值給我block。而__main_block_func_0
是對應block內部要執行的代碼,是一個靜態的方法,它會賦值給__block_impl
中的FuncPtr
。spa
__main_block_desc_0_DATA
是也是一個結構體變量,裏面的兩個參數reserved
爲 0, Block_size
爲 __main_block_impl_0
結構體的大小。指針
調用的時候,是從block裏面直接取出FuncPtr
。 咱們知道block
是__main_block_impl_0
類型,因爲結構體的特性,將block
強轉爲__block_impl
類型,是能夠直接取到FuncPtr
的。因此第二句的調用也是清晰的。
上面的兩句代碼去掉強制類型轉化,能夠精簡爲:
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
block->FuncPtr(block);
複製代碼
這樣,調用過程就清晰多了。
經過上面的分析,咱們能夠看出block的結構應該是以下圖所示:
auto自動變量是離開做用域,就會銷燬, 只存在局部變量裏面,不能修飾全局變量。
好比,下面例子中的age
和 weight
就是auto變量,他們離開本身所在的做用局就會銷燬。默認狀況下auto關鍵字會自動添加。
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
int age = 20;
auto int weight = 60;
}
// 在這裏訪問age, weight就報錯了。
}
return 0;
}
複製代碼
若是block中使用了auto變量,那麼block就會捕獲該變量,下面代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
int age = 20;
auto int weight = 60;
void (^block)(void) = ^{
NSLog(@"age = %d, weight = %d", age, weight); //age的結果是20
};
age = 40;
block();
}
}
return 0;
}
複製代碼
打印的結果中 age爲20 仍是 40? 編譯後,__main_block_impl_0
的結構以下,增長了兩個int 變量。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int weight;
//: age(_age), weight(_weight) 是C++語法,表示參數_age會賦值給變量age.
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int _weight, int flags=0) : age(_age), weight(_weight) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
咱們能夠看出,捕獲了auto變量,並且是值傳遞。
下面代碼輸入結果是什麼?
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
static int height = 40;
void (^block)(void) = ^{
NSLog(@"height = %d, ", height);
};
height = 80;
block();
}
}
return 0;
}
複製代碼
結果是80,爲何呢? 咱們依然經過編譯後的結果查看。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_height, int flags=0) : height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
{
static int height = 40;
void (*block)(void) = ((void (*)())&__ma.in_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &height));
height = 80;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
}
return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bv_k_7y193n6tvf34wjvqvnn3q40000gn_T_main_8d6bbf_mi_0, (*height));
}
複製代碼
咱們能夠看出__main_block_impl_0
中增長了一個變量height
,但須要注意的是它是int *
類型的,在給它賦值的時候傳入的是&height
。 在__main_block_func_0
中訪問的時候是經過*height
取值的。
所以咱們能夠得出結論,靜態變量也是會被Block捕獲的,但它捕獲的是指針。
下面代碼,輸出的結果是什麼?
int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
void (^block)(void) = ^{
NSLog(@"height = %d, ", age);
};
age = 20;
block();
}
}
return 0;
}
複製代碼
輸入結果是20,那block捕獲了age嗎?是經過指針訪問的嗎?咱們看一下編譯結果:
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並無捕獲全局變量。
經過上面的分析,咱們能夠得出結論:
下面代碼中,Person類中的test方法中block捕獲了變量了嗎?捕獲了那個變量?
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p.name = @"樂戈";
[p test];
}
return 0;
}
// Person.h
@interface Person : NSObject
@property (nonatomic, copy)NSString *name;
- (void)test;
@end
// Person.m
@implementation Person
- (void)test {
void (^block)(void) = ^{
NSLog(@"name == %@", self.name);
};
block();
}
@end
複製代碼
用上面的命令將Person.m編譯C++代碼,以下:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
能夠看出,這裏捕獲的並非name,而是Person對象,這涉及了block的循環引用,咱們將在下面的文章中講述。
下面各個代碼的輸出結果是什麼?
//問題1
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [@[@"abc"] mutableCopy];
void (^block)(void) = ^{
NSLog(@"hello block---%@", [array firstObject]);
};
array[0] = @"dgf";
block();
}
return 0;
}
//問題2
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [@[@"abc"] mutableCopy];
void (^block)(void) = ^{
NSLog(@"hello block---%@", [array firstObject]);
};
array = [@[@"dgf"] mutableCopy];
block();
}
return 0;
}
複製代碼