一個簡單示例:ios
int main(int argc, const char * argv[]) {
void (^block)(void) = ^{
NSLog(@"hey");
};
block();
return 0;
}
複製代碼
將以上 Objective-C 源碼轉換成 c++ 相關源碼,使用命令行 : xcrun -sdk iphoneos xclang -arch arm64 -rewrite-objc 文件名
c++
c++ 的結構體與通常的類類似。api
int main(int argc, const char * argv[]) {
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;
}
複製代碼
其中 Block 的數據結構爲:bash
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
複製代碼
impl 變量數據結構:數據結構
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
複製代碼
FuncPtr:函數實際調用的地址,由於 Block 可看做是捕獲自動變量的匿名函數。iphone
Desc 變量數據結構:函數
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
複製代碼
Objective-C 中 Block 有三種類型,其最終類型都是 NSBlock 。ui
Block 類型的不一樣,主要根據捕獲變量的不一樣行爲產生:this
Block 類型 | 行爲 |
---|---|
NSGlobalBlock | 沒有訪問 auto 變量 |
NSStackBlock | 訪問 auto 變量 |
NSMallocBlock | NSStackBlock 調用 copy |
內存五大區:棧、堆、靜態區(BSS 段)、常量區(數據段)、代碼段spa
不一樣類型的 Block 調用 copy 操做,也會產生不一樣的複製效果:
Block 類型 | 副本源的配置存儲域 | 複製效果 |
---|---|---|
__NSConcreteStackBlock | 棧 | 從棧複製到堆 |
__NSConcreteGlobalBlock | 數據段(常量區) | 什麼也不作 |
__NSConcreteMallocBlock | 堆 | 引用計數增長 |
在 ARC 環境下,聲明的 block 屬性用 copy 或 strong 修飾的效果是同樣的,但在 MRC 環境下,則用 copy 修飾。
爲了保證在 Block 內部可以正常訪問外部變量,Block 有一套變量捕獲機制:
變量類型 | 是否捕獲到 Block 內部 | 訪問方式 |
---|---|---|
局部 auto 變量 | 是 | 值傳遞 |
局部 static 變量 | 是 | 指針傳遞 |
全局變量 | 否 | 直接訪問 |
若局部 static 變量是基礎類型
int val
,則訪問方式爲int *val
若局部 static 變量是對象類型JAObject *obj
,則訪問方式爲JAObject **obj
一個簡單示例:
int age = 10;
// static int age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", age);
};
block();
複製代碼
struct __main_block_impl_0 {
···
int age; // 傳遞值
}
複製代碼
struct __main_block_impl_0 {
···
int *age; // 傳遞指針
}
複製代碼
一個簡單示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", person.age);
};
block();
複製代碼
struct __main_block_impl_0 {
···
JAPerson *person;
}
複製代碼
struct __main_block_impl_0 {
···
JAPerson **person;
}
複製代碼
當捕獲的變量是對象類型或者使用 __Block 將變量包裝成一個 __Block_byref_變量名_0 類型的 Objective-C 對象時,會產生 copy
和 dispose
函數。
一個簡單示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", person.age);
};
block();
複製代碼
其中生成的 Block 的數據結構中多了 JAPerson 類型指針變量 person :
struct __main_block_impl_0 {
···
JAPerson *person;
}
複製代碼
Desc 變量數據結構多了內存管理相關的函數:
static struct __main_block_desc_0 {
···
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}
複製代碼
這兩個函數的調用時機:
函數 | 調用時機 |
---|---|
copy | 棧上的 Block 複製到堆時 |
dispose | 堆上的 Block 被廢棄時 |
copy 和 dispose 底層相關源碼
// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
複製代碼
當 Block 內部訪問了對象類型的 auto 變量時:
copy
函數,copy
函數內部會調用 _Block_object_assign
函數,_Block_object_assign
函數會根據 auto 變量的修飾符(__strong、__weak、__unsafe_unretain)做出相應的內存管理操做。注意:若此時變量類型爲對象類型,這裏僅限於 ARC 時會 retain ,MRC 時不會 retain 。
dispose
函數,dispose
函數內部會調用 _Block_object_dispose
函數,_Block_object_dispose
函數會自動 release 引用的 auto 變量。使用 __weak 修飾的 OC 代碼轉換對應的 c++ 代碼會報錯:
error: cannot create __weak reference because the current deployment target does not support weak references
此時終端命令需支持 ARC 並指定 Runtime 版本:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
局部 static 變量(指針訪問)、全局變量(直接訪問)均可以在 Block 內部直接修改捕獲的變量,而局部 auto 變量則主要經過使用 __block 存儲域修飾符來修改捕獲的變量。
編譯器會將 __block 修飾的變量包裝成一個 Objective-C 對象。
一個簡單示例:
__block int age = 10;
void (^block)(void) = ^{
NSLog(@"age is %d", age);
};
block();
複製代碼
其中 Block 的數據結構多了一個 __Block_byref_age_0 類型的指針:
struct __main_block_impl_0 {
···
__Block_byref_age_0 *age; // by ref
}
複製代碼
__Block_byref_age_0 結構體:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age; // age 真正存儲的地方
};
複製代碼
兩個注意點:
age->__forwarding->age
的方式去取值,是由於這兩個 age 均可能仍在棧上,此時直接 age->age
訪問會有問題,而 copy 操做時 __forwarding 會指向堆上的 __Block_byref_age_0 ,此時就算第一個 age 仍在棧上,經過 age->__forwarding
會從新指向堆上的 __Block_byref_age_0 ,此時再訪問 age 便不會有問題 age->__forwarding->age
。使用 __block 修飾符時的內存管理狀況:
copy
函數,copy
函數會調用 __main_block_copy_0
函數對 __block 變量產生一個強引用。以下圖dispose
函數,dispose
函數會調用 _Block_object_dispose
函數自動 release
__block 變量。以下圖一個簡單的示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
__weak typeof(person) weakPerson = person;
void (^block)(void) = ^{
NSLog(@"person‘s age is %d", weakPerson.age);
};
複製代碼
一個簡單的示例:
JAPerson *person = [[JAPerson alloc] init];
person.age = 10;
__block __weak typeof(person) weakPerson = person;
void (^block)(void) = ^{
NSLog(@"person‘s age is %d", weakPerson.age);
};
block();
return 0;
複製代碼
常見的循環引用問題:
_Block_object_assign
函數在 MRC 環境下對 block 內部的對象不會進行 retain 操做。