先貼一段代碼,接下來根據這段代碼逐步分析__weak、__strong、__block
這些關鍵字的作用
一. 查看__weak、__strong、__block在這些修飾符下的源碼
main函數實現
typedef void (^Block) (void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int no = 20;
__block int age = 10;
NSObject *object = [[NSObject alloc] init];
__weak NSObject *weakObject = object;
Block block = ^{
age = 20;
NSLog(@"%d", no);
NSLog(@"%d", age);
NSLog(@"%p", weakObject);
};
struct __main_block_impl_0* blockImpl = (__bridge struct __main_block_impl_0*)block;
block();
age = 30;
}
return 0;
}
以下代碼是將OC代碼轉換為C/C++代碼的結果
1. 使用__block修飾以后age
變成了__Block_byref_age_0
這種數據結構,是一個OC對象,含有isa
指針,__forwarding
指針指向自己,結構體大小__size
,真正用來存儲age
數值的變量。
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
2. __main_block_impl_0這種數據結構比最簡單的block
多了三個參數,分別是int no;
和NSObject *__weak weakObject;
和__Block_byref_age_0 *age;
,no
值傳遞,age
默認使用強引用。weakObject
使用__weak
修飾是因為外部使用了弱引用。如果外部不使用__weak
修飾,默認會使用__strong
修飾,也就是強引用。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int no;
NSObject *__weak weakObject;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3. __main_block_func_0這個函數主要就是調用block
需要執行的具體函數。可以看到訪問age
的時候使用了(age->__forwarding->age) = 20;
這種方式,而不是直接使用age->age
,這是為了保證block復制到堆上以后無論是在棧上還是在堆上的block
都可以訪問到同一份block
捕獲的堆上的變量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
int no = __cself->no; // bound by copy
NSObject *__weak weakObject = __cself->weakObject; // bound by copy
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_0, no);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_1, (age->__forwarding->age));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d47e2d_mi_2, weakObject);
}
4. __main_block_copy_0、_Block_object_assign在捕獲對象的時候進行對象的內存管理。如果是__block
修飾的類型就是強引用,如果是對象類型根據外部的修飾類型而定,如果外部使用__strong
修飾就是強引用,如果是使用__weak
這里邊就使用弱引用。可以看到這里BLOCK_FIELD_IS_BYREF
和BLOCK_FIELD_IS_OBJECT
修飾類型是不同的。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
5. __main_block_desc_0 這里比最簡單的block
多了兩個函數指針,分別是copy
和dispose
。函數實現就是上邊4的兩個函數。
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
6. main函數刪除了多余的部分如下所示。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int no = 20;
__Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
__attribute__((objc_ownership(weak))) NSObject *weakObject = object;
Block block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, no, weakObject, &age, 570425344);
block->FuncPtr(block);
(age.__forwarding->age) = 30;
}
return 0;
}
如上可以看到__block int age = 10;
變成了 __Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
age = 30;
變成了(age.__forwarding->age) = 30;
這里就是在棧上訪問age
最終存儲的地方,為了保持和block
內部訪問到的是同一個地方所以使用了age.__forwarding
先找到堆上的age
這個結構體的地址,然后在訪問堆上該結構體的變量age
。
二. 設置斷點查看age
具體存放的內存地址
將以下這些結構體放在main函數上邊,用來獲取成員變量的內存地址的時候使用。
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age;
};
由下邊可知直接獲取age
的地址和使用&(blockImpl->age->__forwarding->age)
獲取的地址是一樣的,也就是說直接使用age
就是訪問的copy到堆上的__Block_byref_age_0
結構體里邊的age
。
(lldb) p/x &age
(int *) $0 = 0x000000010065a598
(lldb) p/x &(blockImpl->age)
(__Block_byref_age_0 **) $1 = 0x000000010065a560
(lldb) p/x &(blockImpl->age->age)
(int *) $2 = 0x000000010065a598
(lldb) p/x &(blockImpl->age->__forwarding->age)
(int *) $3 = 0x000000010065a598
三. __block修飾符
- __block可以用于解決block內部無法修改auto變量值的問題
- __block不能修飾全局變量、靜態變量(static)
- 編譯器會將__block變量包裝成一個對象
四. __block的內存管理
當block在棧上時,并不會對__block變量產生強引用
-
當block被copy到堆時
a) 會調用block內部的copy函數
b) copy函數內部會調用_Block_object_assign函數
c) _Block_object_assign函數會對__block變量形成強引用(retain)
__block的內存管理-引用 當block從堆中移除時
a) 會調用block內部的dispose函數
b) dispose函數內部會調用_Block_object_dispose函數
c) _Block_object_dispose函數會自動釋放引用的__block變量(release)
四. __block的__forwarding指針
五. 對象類型的auto變量、__block變量
當block在棧上時,對它們都不會產生強引用
當block拷貝到堆上時,都會通過copy函數來處理它們
__block變量(假設變量名叫做a)
a) _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);對象類型的auto變量(假設變量名叫做p)
a) _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);當block從堆上移除時,都會通過dispose函數來釋放它們
__block變量(假設變量名叫做a)
a) _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);對象類型的auto變量(假設變量名叫做p)
a) _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
六. 被__block修飾的對象類型
當__block變量在棧上時,不會對指向的對象產生強引用
當__block變量被copy到堆時
a) 會調用__block變量內部的copy函數
b) copy函數內部會調用_Block_object_assign函數
c) _Block_object_assign函數會根據所指向對象的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain)如果__block變量從堆上移除
a) 會調用__block變量內部的dispose函數
b) dispose函數內部會調用_Block_object_dispose函數
c) _Block_object_dispose函數會自動釋放指向的對象(release)