__block 可以用來解決block内部無法修改auto變量值的問題
__blcok不能修飾全局變量、靜态變量(static)
編譯器會将__block變量包裝成一個對象
代碼示例
============================== 原始代碼 ==============================
@interface TBPerson : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation TBPerson
@end
@interface ViewController ()
@end
@implementation ViewController
typedef void (^TBBlock) (void);
- (void)viewDidLoad {
[super viewDidLoad];
// __block修飾的基礎資料類型
__block int age = 10;
// __block修飾的對象類型
__block TBPerson *person = [[TBPerson alloc] init];
person.name = @"111";
// 對象類型的auto變量
TBPerson *person1 = [[TBPerson alloc] init];
person1.name = @"000";
TBBlock block = ^{
age = 20;
person.name = @"222";
NSLog(@"age is %d; person -- name is %@; person1 -- name is %@",age,person.name,person1.name);
};
block();
}
============================== 使用clang轉換為C++猜測底層實作 ==============================
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
// __block int age = 10;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
// __block TBPerson *person = [[TBPerson alloc] init];
__attribute__((__blocks__(byref))) __Block_byref_person_1 person = {(void*)0,(__Block_byref_person_1 *)&person, 33554432, sizeof(__Block_byref_person_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((TBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((TBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TBPerson"), sel_registerName("alloc")), sel_registerName("init"))};
// person.name = @"111";
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_a6f2e3_mi_0);
// TBPerson *person1 = [[TBPerson alloc] init];
TBPerson *person1 = ((TBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((TBPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TBPerson"), sel_registerName("alloc")), sel_registerName("init"));
// person1.name = @"000";
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person1, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_a6f2e3_mi_1);
// block
TBBlock block = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, person1, (__Block_byref_age_0 *)&age, (__Block_byref_person_1 *)&person, 570425344));
// 調用block方法
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
// block内部要執行的函數
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
// __block變量包裝成一個對象__Block_byref_age_0
__Block_byref_age_0 *age = __cself->age; // bound by ref
__Block_byref_person_1 *person = __cself->person; // bound by ref
TBPerson *person1 = __cself->person1; // bound by copy
/*
為什麼要使用age->__forwarding->age、person->__forwarding->person方式來通路
當變量從棧上拷貝到堆上的時候,可以保證不管是從棧上進行的通路,還是從堆上進行的通路,最後都一定是通路的堆上的變量
*/
(age->__forwarding->age) = 20;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_a6f2e3_mi_2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_lx_q2n5w01149j15zhtyt93nzpr0000gn_T_ViewController_a6f2e3_mi_3,(age->__forwarding->age),((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("name")),((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person1, sel_registerName("name")));
}
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
TBPerson *person1; // block捕獲的外部變量
__Block_byref_age_0 *age; // 指向結構體對象 struct __Block_byref_age_0 (就是個強引用,不存在弱引用)
__Block_byref_person_1 *person; // 指向結構體對象 struct __Block_byref_person_1 (就是個強引用,不存在弱引用)
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, TBPerson *_person1, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, int flags=0) : person1(_person1), age(_age->__forwarding), person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = {
0,
sizeof(struct __ViewController__viewDidLoad_block_impl_0),
__ViewController__viewDidLoad_block_copy_0,
__ViewController__viewDidLoad_block_dispose_0
};
/*
當block在棧上的時候,并不會對__block變量産生強引用
當block被copy到堆上時
會調用block内部的copy函數
copy函數内部會調用_Block_object_assign函數
_Block_object_assign函數會對 __block變量形成強引用(retain)
*/
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->person1, (void*)src->person1, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
/*
當block從堆上移除時
會調用block内部的dispose函數
dispose函數内部會調用_Block_object_dispose函數
_Block_object_dispose函數會自動釋放引用的 __block變量(release)
*/
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->person1, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
// 基本資料類型轉C++(__block int age 包裝後的對象)
struct __Block_byref_age_0 {
void *__isa; // 指向結構體的類型
__Block_byref_age_0 *__forwarding; // 指向自己
int __flags; // 暫時未用到
int __size; // 結構體的大小
int age; // 變量的值
};
// 對象類型轉C++(__block TBPerson *person 包裝後的對象)
struct __Block_byref_person_1 {
void *__isa; // 指向結構體的類型
__Block_byref_person_1 *__forwarding; // 指向自己
int __flags; // 暫時未用到
int __size; // 結構體的大小
void (*__Block_byref_id_object_copy)(void*, void*); // 對象執行個體的記憶體管理:copy函數的位址
void (*__Block_byref_id_object_dispose)(void*); // 對象執行個體的記憶體管理:dispose函數的位址
/*
在ARC下:
可以是強引用(__block TBPerson *person),也可以是弱引用(__block __weak TBPerson *person),主要看外部修飾詞
在MRC下:
不會對TBPerson *person進行retain(也就是說一直是弱引用)
*/
TBPerson *person; // 指向對象執行個體
};
/*
當__block被copy到堆上時
會調用__block内部的copy函數
copy函數内部會調用_Block_object_assign函數
_Block_object_assign函數會根據指向對象的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用
(注意:這裡僅限于ARC時才會retain,在MRC環境時不會retain)
*/
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// (char*)dst + 40 就是TBPerson *person(8+8+4+4+8+8)
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
/*
當__block從堆上移除時
會調用__block内部的dispose函數
dispose函數内部會調用_Block_object_dispose函數
_Block_object_dispose函數會自動釋放指向的對象(release)
*/
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
============================== 總結 ==============================
一、對象類型的auto變量、__block變量 的記憶體管理
當block在棧上時,對它們都不會産生強引用
當block拷貝到推上時,都會通過copy函數來處理它們
__block變量: _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
對象類型的auto變量: _Block_object_assign((void*)&dst->person1, (void*)src->person1, 3/*BLOCK_FIELD_IS_OBJECT*/);
當block從堆上移除時,都會通過dispose函數來釋放它們
__block變量: _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
對象類型的auto變量: _Block_object_dispose((void*)src->person1, 3/*BLOCK_FIELD_IS_OBJECT*/);
注意:
__block變量 : BLOCK_FIELD_IS_BYREF
對象類型auto變量 : BLOCK_FIELD_IS_OBJECT
二、針對的是__block包裝的結構體的記憶體管理(如:__Block_byref_age_0 *age; __Block_byref_person_1 *person;)
當block在棧上的時候,并不會對__block變量産生強引用
當block被copy到堆上時
會調用block内部的copy函數
copy函數内部會調用_Block_object_assign函數
_Block_object_assign函數會對 __block變量形成強引用(retain)
當block從堆上移除時
會調用block内部的dispose函數
dispose函數内部會調用_Block_object_dispose函數
_Block_object_dispose函數會自動釋放引用的 __block變量(release
三、針對的是__block包裝的結構體内部指向的對象(__block修飾的對象類型)的記憶體管理
當__block變量在棧上的時候,并不會對指向的對象産生強引用
當__block被copy到堆上時
會調用__block内部的copy函數
copy函數内部會調用_Block_object_assign函數
_Block_object_assign函數會根據指向對象的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用
(注意:這裡僅限于ARC時才會retain,在MRC環境時不會retain)
當__block從堆上移除時
會調用__block内部的dispose函數
dispose函數内部會調用_Block_object_dispose函數
_Block_object_dispose函數會自動釋放指向的對象(release)
注意:
1、block内部需要修改auto變量的值時才需要加__block
2、如果是使用 則不用加__block 如數組的添加、删除元素等操作
3、如果block外面通路__block修飾的變量時,通路的是 包裝後的對象裡面的變量 如:
struct __Block_byref_age_0 --> int age (通路的是age)
struct __Block_byref_person_1 --> TBPerson *person (通路的是person)
__block變量的copy示意圖(截取自課程内容)
__block變量的dispose示意圖(截取自課程内容)
__block變量的__forwarding指針示意圖(截取自課程内容)
PS 此文為學習 李明傑 老師的 iOS底層原理課程 所寫筆記