block的本質(zhì)
block本質(zhì)上也是一個(gè)oc對(duì)象唯卖,他內(nèi)部也有一個(gè)isa指針椿访。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象垮媒。block其實(shí)也是NSObject的子類
block的類型
共有三種類型的block分別是:全局的,棧上的术浪,堆上的
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )//直到程序結(jié)束才會(huì)被回收
__NSStackBlock__ ( _NSConcreteStackBlock )//棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放,作用域執(zhí)行完畢之后就會(huì)被立即釋放
__NSMallocBlock__ ( _NSConcreteMallocBlock )//平時(shí)編碼過(guò)程中最常使用到的寿酌。存放在堆中需要我們自己進(jìn)行內(nèi)存管理胰苏。
block是如何定義其類型
三種block的創(chuàng)建
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1. 內(nèi)部沒(méi)有調(diào)用外部變量的block
void (^block1)(void) = ^{
NSLog(@"Hello");
};
// 2. 內(nèi)部調(diào)用外部變量的block
int a = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d",a);
};
// 3. 直接調(diào)用的block的class
NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
NSLog(@"%d",a);
} class]);
}
return 0;
}
打印結(jié)果:
2021-09-28 11:30:11.568128+0800 OC-Review[44456:6774536] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
block的變量捕獲
局部變量
- auto變量 - 值傳遞
- static變量 - 指針傳遞
全局變量 - 直接訪問(wèn)
- 一旦block中捕獲的變量為對(duì)象類型,block結(jié)構(gòu)體中的__main_block_desc_0會(huì)出兩個(gè)參數(shù)copy和dispose醇疼。因?yàn)樵L問(wèn)的是個(gè)對(duì)象硕并,block希望擁有這個(gè)對(duì)象,就需要對(duì)對(duì)象進(jìn)行引用秧荆,也就是進(jìn)行內(nèi)存管理的操作倔毙。比如說(shuō)對(duì)對(duì)象進(jìn)行retarn操作,因此一旦block捕獲的變量是對(duì)象類型就會(huì)會(huì)自動(dòng)生成copy和dispose來(lái)對(duì)內(nèi)部引用的對(duì)象進(jìn)行內(nèi)存管理乙濒。
- 當(dāng)block內(nèi)部訪問(wèn)了對(duì)象類型的auto變量時(shí)陕赃,如果block是在棧上卵蛉,block內(nèi)部不會(huì)對(duì)person產(chǎn)生強(qiáng)引用。不論block結(jié)構(gòu)體內(nèi)部的變量是__strong修飾還是__weak修飾么库,都不會(huì)對(duì)變量產(chǎn)生強(qiáng)引用傻丝。
- 如果block被拷貝到堆上。copy函數(shù)會(huì)調(diào)用_Block_object_assign函數(shù)诉儒,根據(jù)auto變量的修飾符(__strong葡缰,__weak,unsafe_unretained)做出相應(yīng)的操作忱反,形成強(qiáng)引用或者弱引用
- 如果block從堆中移除泛释,dispose函數(shù)會(huì)調(diào)用_Block_object_dispose函數(shù),自動(dòng)釋放引用的auto變量缭受。
block內(nèi)修改變量的值
局部變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 5;
void(^blk)(void) = ^{
a = 10;
};
}
return 0;
}
block不能修改外部的局部變量,age是在main函數(shù)內(nèi)部聲明的胁澳,說(shuō)明age的內(nèi)存存在于main函數(shù)的椄没ィ空間內(nèi)部米者,但是block內(nèi)部的代碼在__main_block_func_0函數(shù)內(nèi)部。__main_block_func_0函數(shù)內(nèi)部無(wú)法訪問(wèn)age變量的內(nèi)存空間宇智,兩個(gè)函數(shù)的椔悖空間不一樣,__main_block_func_0內(nèi)部拿到的age是block結(jié)構(gòu)體內(nèi)部的age随橘,因此無(wú)法在__main_block_func_0函數(shù)內(nèi)部去修改main函數(shù)內(nèi)部的變量喂分。
在最新的xcode中,已經(jīng)對(duì)這種情況進(jìn)行了錯(cuò)誤代碼提示
修改方式
方式一:a使用static修飾机蔗。
int main(int argc, char * argv[]) {
@autoreleasepool {
static int a = 5;
void(^blk)(void) = ^{
a = 10;
};
blk();
}
return 0;
}
-----
2021-09-29 14:40:10.580041+0800 OC-Review[71142:7289047] 修改前5
2021-09-29 14:40:10.581801+0800 OC-Review[71142:7289047] 修改后10
方式二:__block
int main(int argc, char * argv[]) {
@autoreleasepool {
__block int a = 5;
void(^blk)(void) = ^{
a = 10;
};
NSLog(@"修改前%d",a);
blk();
NSLog(@"修改后%d",a);
}
return 0;
}
2021-09-29 14:42:12.904192+0800 OC-Review[71207:7291575] 修改前5
2021-09-29 14:42:12.905207+0800 OC-Review[71207:7291575] 修改后10
查看源碼:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
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;
}
};
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) = 10;
}
首先被__block修飾的a變量聲明變?yōu)槊麨閍ge的__Block_byref_a_0結(jié)構(gòu)體蒲祈,也就是說(shuō)加上__block修飾的話捕獲到的block內(nèi)的變量為__Block_byref_a_0類型的結(jié)構(gòu)體。
__isa指針 :__Block_byref_age_0中也有isa指針也就是說(shuō)__Block_byref_age_0本質(zhì)也一個(gè)對(duì)象萝嘁。
__forwarding :__forwarding是__Block_byref_age_0結(jié)構(gòu)體類型的梆掸,并且__forwarding存儲(chǔ)的值為(__Block_byref_age_0 *)&age,即結(jié)構(gòu)體自己的內(nèi)存地址牙言。
__flags :0
__size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的內(nèi)存空間酸钦。
a :真正存儲(chǔ)變量的地方,這里存儲(chǔ)局部變量10咱枉。
__block將變量包裝成對(duì)象卑硫,然后在把a(bǔ)ge封裝在結(jié)構(gòu)體里面,block內(nèi)部存儲(chǔ)的變量為結(jié)構(gòu)體指針蚕断,也就可以通過(guò)指針找到內(nèi)存地址進(jìn)而修改變量的值欢伏。
循環(huán)引用
- 情景一
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
};
}
NSLog(@"大括號(hào)結(jié)束啦");
return 0;
}
可以發(fā)現(xiàn)大括號(hào)結(jié)束之后,person依然沒(méi)有被釋放亿乳,產(chǎn)生了循環(huán)引用
Person對(duì)象和block對(duì)象相互之間產(chǎn)生了強(qiáng)引用硝拧,導(dǎo)致雙方都不會(huì)被釋放,進(jìn)而造成內(nèi)存泄漏
- 情景二
#import "Person.h"
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
self.block = ^{
NSLog(@"%@",[self class]);
};
}
return self;
}
這是開發(fā)中經(jīng)常遇到的一個(gè)場(chǎng)景。之前我們說(shuō)過(guò)block會(huì)捕獲局部河爹,上面的OC函數(shù)調(diào)用轉(zhuǎn)化為runtime代碼為
objc_msgSend(self,@selector(init)) 在OC的方法中 有2個(gè)隱藏參數(shù) self和_cmd 這2個(gè)參數(shù)作為函數(shù)的形參,在方法作用域中屬于局部變量 匠璧, 所以在block中使用self就滿足之前提到的 block會(huì)捕獲局部變量
查看源碼
struct __Person__init_block_impl_0 {
struct __block_impl impl;
struct __Person__init_block_desc_0* Desc;
Person *self;
__Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sr_m_cfkwyx2h56vh4_kf65_vw40000gn_T_Person_3f840b_mi_0,((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
}
這里可以看到 __Person__init_block_impl_0結(jié)構(gòu)體中 創(chuàng)建了一個(gè)Person *self的強(qiáng)指針 指向init方法中self
指針?biāo)赶虻膒erson對(duì)象,使person引用計(jì)數(shù)+1 而person對(duì)block也有一個(gè)強(qiáng)引用咸这。這里就造成了循環(huán)引用夷恍。
解決方法
首先為了能隨時(shí)執(zhí)行block,我們肯定希望person對(duì)block對(duì)強(qiáng)引用媳维,而block內(nèi)部對(duì)person的引用為弱引用最好酿雪。
使用__weak 和 __unsafe_unretained修飾符可以解決循環(huán)引用的問(wèn)題。
- __weak 和 __unsafe_unretained的區(qū)別
a __weak不會(huì)產(chǎn)生強(qiáng)引用侄刽,指向的對(duì)象銷毀時(shí)指黎,會(huì)自動(dòng)將指針置為nil。因此一般通過(guò)__weak來(lái)解決問(wèn)題州丹。
b __unsafe_unretained不會(huì)產(chǎn)生強(qiáng)引用醋安,不安全,指向的對(duì)象銷毀時(shí)墓毒,指針存儲(chǔ)的地址值不變吓揪。會(huì)造成野指針問(wèn)題
- 情景三
在block中調(diào)用super也會(huì)造成循環(huán)引用 :
#import "Person.h"
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
self.block = ^{
[super init];
};
}
return self;
}
查看源碼
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"));
}
當(dāng)使用[self class]時(shí),會(huì)調(diào)用objc_msgSend函數(shù)所计,第一個(gè)參數(shù)receiver就是self柠辞,而第二個(gè)參數(shù),要先找到self所在的這個(gè)class的方法列表
當(dāng)使用[super class]時(shí)主胧,會(huì)調(diào)用objc_msgSendSuper函數(shù)叭首,此時(shí)會(huì)先構(gòu)造一個(gè)__rw_objc_super的結(jié)構(gòu)體作為objc_msgSendSuper的第一個(gè)參數(shù)。 該結(jié)構(gòu)體第一個(gè)成員變量receiver仍然是self踪栋,而第二個(gè)成員變量super_class即是所在類的父類
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
runtime對(duì)外暴露的類型為:
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
結(jié)構(gòu)體第一個(gè)成員receiver 就代表方法的接受者 第二個(gè)成員代表方法接受者的父類
所以
self.block = ^{
[super init];
};
轉(zhuǎn)化后是:
self.block = ^{
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(objc_getClass("Person")),
};
((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(init));
};
可以很明顯的看到問(wèn)題焙格,block強(qiáng)引用了self,而self也強(qiáng)持有了這個(gè)block
解決方法:
#import "Person.h"
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
__weak __typeof(self) weakSelf = self;
self.block = ^{
[super init];
};
}
return self;
}