前言
block的類型
從一段代碼開始
// 全局變量 n,name
static int n = 100;
static NSString *name = @"zezefamily";
- (void)demo10
{
//0.沒有引用外部變量的 testBlock0
void (^testBlock0)(BOOL ok);
testBlock0 = ^(BOOL ok){
};
NSLog(@"testBlock0 == %@",testBlock0);
//1.引用全局靜態變量的 testBlock1
void (^testBlock1)(BOOL ok);
testBlock1 = ^(BOOL ok){
n;
name;
};
NSLog(@"testBlock1 == %@",testBlock1);
//2.引用了局部變量的 testBlock2
int a = 10;
void (^testBlock2)(BOOL ok);
testBlock2 = ^(BOOL ok){
a;
};
NSLog(@"testBlock2 == %@",testBlock2);
//3.引用了局部靜態變量的 testBlock3
static int c = 15;
void (^testBlock3)(BOOL ok);
testBlock3 = ^(BOOL ok){
c;
};
NSLog(@"testBlock2 == %@",testBlock3);
//4.沒有引用外部變量且弱引用修飾的 testBlock4
void (^__weak testBlock4)(void) = ^{
};
NSLog(@"testBlock4 == %@",testBlock4);
//5.引用了局部變量的且弱引用修飾的 testBlock5
NSString *b = @"string";
void (^__weak testBlock5)(void) = ^{
b;
};
NSLog(@"testBlock5 == %@",testBlock5);
//6.引用了全局靜態變量且弱引用修飾的 testBlock6
void (^__weak testBlock6)(void) = ^{
n;
name;
};
NSLog(@"testBlock6 == %@",testBlock6);
}
看下打印信息:
TestApp[69115:8949095] testBlock0 == <__NSGlobalBlock__: 0x10ef24230>
TestApp[69115:8949095] testBlock1 == <__NSGlobalBlock__: 0x10ef24250>
TestApp[69115:8949095] testBlock2 == <__NSMallocBlock__: 0x6000004d4930>
TestApp[69115:8949095] testBlock2 == <__NSGlobalBlock__: 0x10ef24290>
TestApp[69115:8949095] testBlock4 == <__NSGlobalBlock__: 0x10ef242d0>
TestApp[69115:8949095] testBlock5 == <__NSStackBlock__: 0x7ffee0cdd100>
TestApp[69115:8949095] testBlock6 == <__NSGlobalBlock__: 0x10ef242f0>
首先我們可以看到,block
有3中類型,分別為:NSGlobalBlock
,NSMallocBlock
,NSStackBlock
。
1.當沒有引用任何外部變量的,或引用了全局靜態變量的,或引用了局部靜態變量的block
為NSGlobalBlock
(全局block
);
2.當引用了局部變量且沒有__weak
修飾的block
,為NSMallocBlock
(堆block
);
3.當引用了局部變量且__weak
修飾的block
,為NSStackBlock
(棧block
);
block的本質
我們在main.m
中注入幾行簡單的block
代碼片段,然后通過Clang
看一下編譯后的情況,下面是簡單的block
應用:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 測試代碼
void (^testBlock)(void) = ^{
NSLog(@"hello block");
};
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通過Clang
來看下編譯后的樣子:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
上面代碼看著比較凌亂,我們稍微的整理一下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
void (*testBlock)(void) = _main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
testBlock->FuncPtr(testBlock);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
簡化之后我們可以看到,我們的block
代碼部分被轉化成了一個__main_block_impl_0 ()
的函數,通過傳入了__main_block_func_0
,__main_block_desc_0_DATA
2個參數。
testBlock();
調用部分被轉化為了testBlock->FuncPrt(testBlock)
,調用FuncPrt
函數,并將testBlock
自己傳入。
首先我們先看下__main_block_func_0
:
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;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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
是一個結構體,包含impl
,Desc
,和一個同名的構造函數,而我們的block
最終訪問的就是這個__main_block_impl_0()
構造函數,內部對impl
和Desc
進行了賦值操作,這里重點看下impl.FuncPtr = fp
, 這里的fp
就是外界出入的__main_block_func_0
接下來我們看下構造函數里的參數:
參數1:__main_block_func_0
也就是這個fp
,同樣是impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yy_htpy_x9s09v1zf7ms0jgytwr0000gn_T_main_9dacc5_mi_0);
}
這里就是block
代碼塊。
__main_block_func_0
是什么時候被調用的吶?這里看一下main
函數
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
這里將testBlock
強轉為__block_impl*
類型,并調用了FuncPtr()
,也就是__main_block_func_0 ()
函數,之后就執行了整個block
;
到這里,我們也就知道了,我們的block
,在編譯時被包裝成了一個xx_block_impl_0
的結構體,并執行了同名的構造函數,將block
塊轉換成__xx_block_func_0
函數作為參數傳入,同時還傳入了一些描述信息。執行block
時,被轉為調用FuncPtr()
函數,即__xx_block_func_0
函數。
block變量捕獲
我們在來看下block
的變量捕獲,首先給我們的代碼添加一個變量:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 測試代碼
int a = 1024;
void (^testBlock)(void) = ^{
NSLog(@"a = %d",a);
};
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Clang
看下編譯后有什么不同,直接將main
函數中簡化的代碼片段看一下:
int main(int argc, char * argv[]) {
{
int a = 1024;
void (*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
testBlock->FuncPtr(testBlock);
}
}
這里可以看到我們定義的變量a
,同時可以看到__main_block_impl_0
函數末尾也多了一個參數a
; 再看下__main_block_impl_0
結構體:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這個結構體中也明顯的看到了多一個成員int a;
,同時構造函數也多了一個參數,并將傳入的_a
賦值給a
;
這就意味著,在程序編譯時,自動將外部變量捕獲并添加到了__main_block_impl_0
結構體內;
再看下__main_block_func_0()
代碼塊:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&"a = %d",a);
}
這里可以注意到,NSLog
輸出的這個變量a
,并不是block
外界的那個int a
,而是來自于__cself->a
,即__main_block_impl_0
結構體中的int a
變量,這里我的理解是發生了一次深拷貝的a
, 這樣也就解釋了為什么非__block
修飾的變量無法在block
內部修改,因為他們完全不是同一樣變量(值和指針都不是同一個)。
接下來使用__block
修飾一下int a
,看編譯結果:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 測試代碼
__block int a = 1024;
void (^testBlock)(void) = ^{
a++;
NSLog(@"a = %d",a);
};
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
編譯后:
int main() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
1024
};
void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
}
這里很明顯的看到使用__block
修飾的變量a
被包裝成了一個__Block_byref_a_0
結構體對象
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
并保存了變量a
的指針地址和值,然后將__Block_byref_a_0
對象傳入__main_block_impl_0()
函數:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這個可以看到a = _a->__forwarding
,即外界變量a
的指針地址,再看下__main_block_func_0
代碼塊:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
NSLog((NSString *)&"a = %d",(a->__forwarding->a));
}
這里的a++
操作,是對__forwarding->a
的操作,即外界的那個變量a。所以通過__block
修飾后的變量為指針拷貝(淺拷貝),都指向同一片地址空間。
我們可以用更直接的方式驗證一下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 測試代碼
__block int a = 1024;
int b = 100;
NSString *c = @"我是c";
__block NSString *d = @"我是d";
NSLog(@"a = %d",a);
NSLog(@"b = %d",b);
NSLog(@"c = %@, c = %p, &c = %p",c,c,&c);
NSLog(@"d = %@, d = %p, &d = %p",d,d,&d);
void (^testBlock)(void) = ^{
NSLog(@"block內,c = %@, c = %p, &c = %p",c,c,&c);
NSLog(@"block內,d = %@, d = %p, &d = %p",d,d,&d);
NSLog(@"block內,a = %d, a = %d, &a = %p",a,a,&a);
NSLog(@"block內,b = %d, b = %d, &b = %p",b,b,&b);
};
a++;
b++;
c = @"我修改了c";
d = @"我修改了d";
NSLog(@"block外,c = %@, c = %p, &c = %p",c,c,&c);
NSLog(@"block外,d = %@, d = %p, &d = %p",d,d,&d);
NSLog(@"block外,a = %d",a);
NSLog(@"block外,b = %d",b);
//執行block
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
打印結果:
TestApp[6068:9618080] c = 我是c, c = 0x10f1cb640, &c = 0x7ffee0a38d08
TestApp[6068:9618080] d = 我是d, d = 0x10f1cb660, &d = 0x7ffee0a38d00
TestApp[6068:9618080] block外,c = 我修改了c, c = 0x10f1cb740, &c = 0x7ffee0a38d08
TestApp[6068:9618080] block外,d = 我修改了d, d = 0x10f1cb760, &d = 0x6000030b3268
TestApp[6068:9618080] block外,a = 1025
TestApp[6068:9618080] block外,b = 101
TestApp[6068:9618080] block內,c = 我是c, c = 0x10f1cb640, &c = 0x600002ba9be0
TestApp[6068:9618080] block內,d = 我修改了d, d = 0x10f1cb760, &d = 0x6000030b3268
TestApp[6068:9618080] block內,a = 1025, a = 1025, &a = 0x600003e925f8
TestApp[6068:9618080] block內,b = 100, b = 100, &b = 0x600002ba9bf8
再結合剛才的源碼是不是瞬間清晰了很多。而且在block
內部訪問的參數其實是有一個__cself
的隱藏參數間接訪問的,盡管說直觀看到的block
內外變量的形式并無差別。
底層原理
上面我們都是編譯時的探索,接下我們通過匯編來看下block
運行時的情況。
先來一個簡單的代碼段:
- (void)viewDidLoad {
[super viewDidLoad];
int a = 10;
void (^block)(void) = ^{
a;
NSLog(@"hello block");
};
block();
}
文章一開始就介紹,當一個沒有引用任何外部變量時,是一個__NSGlobalBlock__
類型的block
,引用局部變量時,是一個__NSMallocBlock__
,但在Clang
編譯后發現無論怎么引用外部變量,其中的isa
都指向_NSConcreteStackBlock
類型,這也就說明其他的類型是由運行時動態決定的,但它們是在什么時候發生的改變吶?接下來詳細看下:
下一個符號斷點
objc_retainBlock
,看到其內部跳轉了_Block_copy
指令:看下
_Block_copy
內部實現:可以看到這個指令來自于
libsystem_blocks.dylib
庫。從
libobjc.A.dylib
的objc_retainBlock
指令,進入了libsystem_blocks.dylib
的_Block_copy
指令;首先看下我們的代碼片段,根據之前我們知道的當
block
引用了局部變量為__NSMallocBlock__
,這里我們觀察一下objc_retainBlock
的block
變化情況:(lldb) register read x0
x0 = 0x000000016b11b850
(lldb) po 0x000000016b11b850
<__NSStackBlock__: 0x16b11b850>
signature: "v8@?0"
invoke : 0x104cedbc0 (/private/var/containers/Bundle/Application/92CCF1EA-8000-48E0-A52B-4B8ECDDFC227/TestApp.app/TestApp`__main_block_invoke)
(lldb)
通過寄存器輸出,我們看到當前block
為__NSStackBlock__
類型
向下執行一步,然后再次查看寄存器的情況:
(lldb) register read x0
x0 = 0x00000002833a02d0
(lldb) po 0x00000002833a02d0
<__NSMallocBlock__: 0x2833a02d0>
signature: "v8@?0"
invoke : 0x104cedbc0 (/private/var/containers/Bundle/Application/92CCF1EA-8000-48E0-A52B-4B8ECDDFC227/TestApp.app/TestApp`__main_block_invoke)
(lldb)
此時其實觀察x0
,地址已經發生了變化,同時block
類型由原來的__NSStackBlock__
變為__NSMallocBlock__
,也就是我們NSLog
輸出的類型。
下一步我們看下objc_retainBlock
指令內部操作:
此時打印寄存器輸出:
(lldb) register read x0
x0 = 0x000000016ae13850
(lldb) po 0x000000016ae13850
<__NSStackBlock__: 0x16ae13850>
signature: "v8@?0"
invoke : 0x104ff5bc0 (/private/var/containers/Bundle/Application/BC10EC5A-B715-4785-843E-50181B210D15/TestApp.app/TestApp`__main_block_invoke)
(lldb)
這里說明真正發生改變的還在_Block_copy
指令:
這里我們忽略掉
_Block_copy
中間的執行部分,直接在retab
指令時下斷點,并查看寄存器信息:
(lldb) register read x0
x0 = 0x0000000281d68000
(lldb) po 0x0000000281d68000
<__NSMallocBlock__: 0x281d68000>
signature: "v8@?0"
invoke : 0x104ff5bc0 (/private/var/containers/Bundle/Application/BC10EC5A-B715-4785-843E-50181B210D15/TestApp.app/TestApp`__main_block_invoke)
(lldb)
此時的block
類型已經變為了__NSMallocBlock__
類型,也就是說是在_Block_copy
時發生了改變。即:__NSStackBlock__
-> _Block_copy
-> __NSMallocBlock__
那我們就看一下_Block_copy
的源碼具體做了哪些處理:
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) { //是否為Global Block
return aBlock;
}
else { // ARC 下,StackBlock ,copy to MallocBlock ,
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
比較醒目的注釋
// Its a stack block. Make a copy.
如果是stack block
執行一次拷貝
- 通過
malloc()
開辟一個Block_layout
類型的內存空間result
; - 將
aBlock
的內容拷貝給result
- 給
result
賦值,將result->isa
指向_NSConcreteMallocBlock
; - 返回
result
;
從源碼可以看到block
是一個Block_layout
的結構體:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
typedef void(*BlockInvokeFunction)(void *, ...);
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
結構體中的isa
指向了block
類型; flags
存放了很多的標識符信息,類似于isa_t
; invoke
是block
代碼塊,編譯時看到的那個fp->FuncPtr->_block_func_0
,descriptor
指附加屬性,這里可以看到他是一個Block_descriptor_1
的結構體,往下可以看到Block_descriptor_2
,Block_descriptor_3
, 這些都是由flags
內部標識動態決定的。
下面是flags
的標識定義:
// 注釋: flag 標識
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
BLOCK_HAS_COPY_DISPOSE
決定了是否包含Block_descriptor_2
;
BLOCK_HAS_SIGNATURE
決定了是否包含Block_descriptor_3
;
對應的實現:
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
// 注釋:Block 的描述 : copy 和 dispose 函數
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
// 注釋: Block 的描述 : 簽名相關
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
通過flags
+指針平移向下取值;
我們從編譯時的源碼看到,使用__block
修飾的變量,會被包裝成一個Block_byref
的結構體對象:
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
// 注釋: __Block 修飾的結構體 byref_keep 和 byref_destroy 函數 - 來處理里面持有對象的保持和銷毀
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
這里也會根據flags
標識,動態的插入Block_byref_2
和Block_byref_3
的屬性
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
block
捕獲外部變量的核心操作->_Block_object_assign
// 注釋: Block 捕獲外界變量的操作
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
該方法會通過傳入的不同類型(id,NSObject
,__block
,__weak
)的數據做響應的處理;
++++++++++++++++++++++start++++++++++++++++++++++++++
以下內容是我對Block
的總結:
block 在編譯時會 被轉換為一個 Block_layout 結構體
同時會將變量捕獲到該結構體內部 指針捕獲,值捕獲
__block 修飾的變量 會被包裝為 Block_byref 的結構體
Block_byref 內部的flags 也會根據類型 來決定是否包含 Block_byref1 2
同時動態生成的 flags 值 來確定是否包含 Block_descriptor_1 2 3
運行時,runtime 會調用 Block_copy()
根據類型來判斷生成什么類型的Block_layout
判斷是否為BLOCK_NEEDS_FREE類型 如果是 直接返回
判斷是否為BLOCK_IS_GLOBAL類型 如果是 直接返回
否則 從 棧空間 copy 到 堆空間
1. 通過malloc()開辟一個Block_layout類型的result
2. memmove 將編譯時的Block_layout 映射給 result
3. 對 result 做一些初始化操作
4. _Block_call_copy_helper(result, aBlock);
4.1 判斷是否包含Block_descriptor_2,如果存在,執行desc2->copy(result, aBlock)->
_Block_object_assign(void *destArg, const void *object, const int flags)
4.1.1 判斷當前捕獲的參數類型
BLOCK_FIELD_IS_OBJECT 普通對象類型
_Block_retain_object(object);
*dest = object;
BLOCK_FIELD_IS_BLOCK block對象類型
*dest = _Block_copy(object);
BLOCK_FIELD_IS_BYREF __block修飾類型
*dest = _Block_byref_copy(object); //對Block_byref類型進行處理
1.拿到編譯期生成的Block_byref類型的src
2.通過malloc創建Block_byref類型的copy
3.對copy進行初始化賦值操作
4.判斷src 是否包含Block_byref1 Block_byref2
4.1如果包含
4.1.1 對copy進行Block_byref1賦值
4.1.2 調用Block_byref1->byref_keep->_Block_object_assign()
4.2 否則 memmove()
BLOCK_FIELD_IS_WEAK __weak修飾類型
*dest = _Block_byref_copy(object);
BLOCK_BYREF_CALLER called from __block (byref)
*dest = object;
5. result.isa = _NSConcreteMallocBlock
6. 返回 result
響應block:通過reuslt->invoke()->_block_func_0()
Block即將銷毀時,runtime調用_Block_release()
1.獲取當前Block_layout aBlock;
2.判斷是否為空,是否為GlobalBlock 是否不需要Free 直接return
3._Block_call_dispose_helper(aBlock);
3.1 獲取Block_descriptor_2 數據 desc
3.2 desc->dispose()->__main_block_dispose_0->_Block_object_dispose()
3.2.1 是否為BLOCK_FIELD_IS_BYREF __block 修飾
_Block_byref_release(object)
1.判斷是否存在 Block_byref2 存在 調用byref2->byref_destroy()->__Block_byref_id_object_dispose()->
_Block_object_dispose()
2.free(byref)
3.2.2 是否為BLOCK_FIELD_IS_BLOCK block對象
_Block_release(object);
3.2.3 其他類型不處理
4._Block_destructInstance(aBlock); //這個里面其實什么都沒做
5.free(aBlock)
==============>以下編譯時Block生成的數據結構<====================
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
Block_layout {
//基本結構
&_NSConcreteStackBlock, // isa
570425344, // flags
0, // reserved
__main_block_func_0, // invoke
//根據flags 動態添加 [Block_descriptor_1]
0, // Block_descriptor_1 -> reserved
sizeof(struct __main_block_impl_0), // Block_descriptor_1 -> size
//根據flags 動態添加 [Block_descriptor_2]
__main_block_copy_0, // Block_descriptor_2 -> copy
__main_block_dispose_0 // Block_descriptor_2 -> dispose
//變量捕獲
NSString *age; // 對象類型 age
int *b; // 基本數據類型 b
__Block_byref *a; // __block修飾類型
__Block_byref *string; // __block修飾類型
}
Block_byref a = {
//基本結構
(void*)0, //isa
(Block_byref *)&a, //forwarding
0, //flags
sizeof(__Block_byref_a_0), //size
//變量捕獲
10 //變量a的值
};
Block_byref string = {
(void*)0, //isa
(__Block_byref *)&string,, //forwarding
33554432, //flags
sizeof(__Block_byref), //size
//根據flag動態添加
__Block_byref_id_object_copy, //Block_byref_2 -> byref_keep
__Block_byref_id_object_dispose //Block_byref_2 -> byref_destroy
};
*/
在Block
源碼中重要就是有以下幾個重要的結構體類型 和 函數:
//創建
_Block_copy()
_Block_call_copy_helper()
_Block_object_assign()
//銷毀
_Block_release()
_Block_call_dispose_helper()
_Block_object_dispose()
//結構體
Block_layout
Block_descriptor_1
Block_descriptor_2
Block_descriptor_3
Block_byref
Block_byref_2
Block_byref_3
++++++++++++++++++++++end+++++++++++++++++++++++++++
block循環引用問題
循環引用的產生
先上一段代碼:
typedef void(^TestBock2)(void);
@interface SecondViewController ()
@property (nonatomic,copy) TestBock2 block;
@property (nonatomic,copy) NSString *name;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
NSLog(@"self.name == %@",self.name);
};
self.block();
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
當我們這樣寫完之后其實編譯器已經提示警告了:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
這個寫法發生了一個循環引用,即當前self
持有block
,block
內部又持有了self
(self -> block -> self
),從而導致該頁面無法正常釋放而導致內存泄露問題。
解決循環引用
解決這個問題,我們需要做的就是掐斷這個循環引用鏈,要嘛處理掉self
對block
的強引用,要嘛處理掉block
對self
的強引用。
方式一: 使用__weak
來處理掉block
對self
的強引用
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"self.name == %@",weakSelf.name);
};
self.block();
}
這里利用了__weak
的特性,當__weak
修飾的變量,當指向的對象釋放時,該變量自動置nil
。
此時循環引用問題得到了解決,但是這里還是存在一個問題就是,當block
內部如果執行的了一個耗時異步任務,比如下面這樣:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
__weak typeof(self) weakSelf = self;
self.block = ^{
//模擬延時任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",weakSelf.name);
});
};
self.block();
}
當我們進入頁面,退出時,耗時任務還沒執行完,但此時我們已經頁面已經dealloc
了,這就導致了這個耗時任務是去了意義。
TestApp[154:9509667] -[SecondViewController dealloc]
TestApp[154:9509667] self.name == (null)
這里就需要我們來延長一下weakSelf
的生命周期到延時任務的block
執行完。通過__strong
來延長生命周期:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//模擬延時任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",strongSelf.name);
});
};
self.block();
}
此時delloc
會在延時任務執行完之后才觸發
TestApp[281:9511903] self.name == zezefamily
TestApp[281:9511903] -[SecondViewController dealloc]
方式二: 上面我們利用了__weak
自動置nil
的特性,我們這里是不是也可以使用手動的方式去置空吶,比如下面這樣:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
__block SecondViewController *vc = self;
self.block = ^{
//模擬延時任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",vc.name);
vc = nil;
});
};
self.block();
}
看打印結果:
TestApp[453:9514212] self.name == zezefamily
TestApp[453:9514212] -[SecondViewController dealloc]
其實這個方式的本質跟__weak
是一樣的,只不過這里需要我們手動置nil
。
方式三: 以參數的方式傳遞給block
,作為block
的局部變量,在block
執行完之后釋放
typedef void(^TestBock2)(SecondViewController *);
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
self.block = ^(SecondViewController *vc){
//模擬延時任務
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",vc.name);
});
};
self.block(self);
}
再看輸出:
TestApp[619:9516826] self.name == zezefamily
TestApp[619:9516826] -[SecondViewController dealloc]
同樣完美執行了delloc
。
方式四:NSProxy
(待完善)