本文主要介紹:
1讹语、block的本質(zhì)
2旅急、block捕獲變量
3趋惨、block的類型
4氢伟、__block原理
本質(zhì)
通過clang分析Block底層
step1:
定義block.c
文件
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("lbh");
};
return 0;
}
step2:
通過xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
,將block.c
編譯成 block.cpp
或粮,其中block在底層被編譯成了以下的形式
int main(){
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;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("lbh");
}
//******簡化******
//__main_block_impl_0 是構(gòu)造函數(shù)导饲,在結(jié)構(gòu)體中
//參數(shù)__main_block_func_0 是閉包中具體實(shí)現(xiàn)
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));//構(gòu)造函數(shù)
block->FuncPtr(block);//block調(diào)用執(zhí)行
相當(dāng)于block等于__main_block_impl_0
,是一個(gè)函數(shù)氯材,第一個(gè)參數(shù)是__main_block_func_0
帜消,它是個(gè)函數(shù),代碼塊的實(shí)現(xiàn)函數(shù)浓体。
step3:
查看__main_block_impl_0
泡挺,是一個(gè)結(jié)構(gòu)體
//**block代碼塊的結(jié)構(gòu)體類型**
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;
}
};
//**block的結(jié)構(gòu)體類型**
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)};
struct __main_block_impl_0
包含一個(gè)struct __block_impl
類型的impl
和一個(gè)struct __main_block_desc_0*
類型的Desc
。
struct __block_impl
包含一個(gè)isa
指針命浴,說明block
其實(shí)是一個(gè)對(duì)象娄猫。
static struct __main_block_desc_0
中包含一個(gè)Block_size
表示block占用內(nèi)存空間
構(gòu)造函數(shù)__main_block_impl_0
將第一個(gè)參數(shù)__main_block_func_0
傳給了FuncPtr
,所以FuncPtr
指向block具體實(shí)現(xiàn)函數(shù)的地址生闲。
block通過clang編譯后的源碼間的關(guān)系如下所示媳溺,以__block
修飾的變量為例
總結(jié):block
本質(zhì)上也是一個(gè)oc對(duì)象,他內(nèi)部也有一個(gè)isa指針
碍讯。block
是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象悬蔽。
捕獲基本數(shù)據(jù)類型
捕獲局部變量--auto
定義一個(gè)變量,并在block中調(diào)用
int main(){
int a = 10;
void(^block)(void) = ^{
printf("lbh--%d", a);
};
a = 20;
block();
return 0;
}
輸出結(jié)果
lbh--10
底層編譯成如下
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) {
// : a(_a) c++語法 會(huì)自動(dòng)將_a賦值給a a = _a
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//代碼塊放在函數(shù)中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷貝
printf("lbh--%d", a);
}
int main(){
int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
在構(gòu)造函數(shù)創(chuàng)建block
時(shí)捉兴,傳入的第三個(gè)參數(shù)是a
蝎困,然后將a
存入到結(jié)構(gòu)體__main_block_impl_0
里的成員變量a
中,調(diào)用block
時(shí)倍啥,直接從結(jié)構(gòu)體中取出變量a
的值 所以局部自動(dòng)變量是值拷貝
禾乘, 如果此時(shí)在block內(nèi)部實(shí)現(xiàn)中作 a++操作,是有問題的虽缕,會(huì)造成編譯器的代碼歧義始藕,即此時(shí)的a是只讀的
捕獲局部變量--static
int main(int argc, char * argv[]) {
static int a = 10;
void(^block)(void) = ^{
printf("lbh--%d", a);
};
a = 20;
block();
return 0;
}
輸出結(jié)果
lbh--20
底層編譯如下
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *a = __cself->a; // bound by copy
printf("lbh--%d", (*a));
}
int main(){
static int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
在通過構(gòu)造函數(shù)創(chuàng)建block
時(shí),第三個(gè)參數(shù)傳的是&a
,即變量a的地址
伍派,然后將&a
存入結(jié)構(gòu)體__main_block_impl_0
中的成員變量*a
中江耀,調(diào)用block
時(shí),從結(jié)構(gòu)體中取出a
诉植,因?yàn)榇嫒氲氖?code>a的地址所以是指針拷貝祥国。
全局變量
static int a= 10;
int main(int argc, char * argv[]) {
void(^block)(void) = ^{
printf("lbh--%d", a);
};
a = 20;
block();
return 0;
}
輸出結(jié)果
lbh--20
底層編譯
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("lbh--%d", a);
}
int main(){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
a = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
在通過構(gòu)造函數(shù)創(chuàng)建block
時(shí),并沒有以參數(shù)的形式將a
傳進(jìn)去倍踪,在結(jié)構(gòu)體__main_block_impl_0
中也沒有生成新的成員變量,在調(diào)用時(shí)直接訪問變量a
索昂。
總結(jié):
局部變量都會(huì)被block捕獲建车,自動(dòng)變量是值捕獲,靜態(tài)變量為地址捕獲椒惨。全局變量則不會(huì)被block捕獲
問題:
為什么全局變量不需要捕獲缤至,而局部變量需要捕獲?解答: 因?yàn)槿肿兞慷伎梢栽L問康谆,捕獲是多此一舉领斥,局部變量因?yàn)樽饔糜虻膯栴}需要捕獲,請(qǐng)看下面的例子
void (^block) (void);
void test()
{
auto int a = 10;
static int b = 20;
block = ^{
NSLog(@" a = %d b = %d", a,b);
};
}
//static int a= 10;
int main(int argc, char * argv[]) {
test();
block();
return 0;
}
a
沃暗、b
的作用域是在test
函數(shù)中月洛,而block
代碼塊封裝在另一個(gè)函數(shù)中,block調(diào)用時(shí)調(diào)用的是這個(gè)封裝的函數(shù)孽锥,超出了a
嚼黔、b
的作用域,如果不進(jìn)行變量捕獲惜辑,會(huì)出現(xiàn)訪問異常唬涧。
問題
:下面代碼中self
會(huì)不會(huì)被捕獲? 為什么盛撑?
- (void)test
{
void(^block)(void) = ^{
NSLog(@"----%p",self);
};
block();
}
看底層編譯
struct __LBHPerson__test_block_impl_0 {
struct __block_impl impl;
struct __LBHPerson__test_block_desc_0* Desc;
LBHPerson *self;
__LBHPerson__test_block_impl_0(void *fp, struct __LBHPerson__test_block_desc_0 *desc, LBHPerson *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void _I_LBHPerson_test(LBHPerson * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__LBHPerson__test_block_impl_0((void *)__LBHPerson__test_block_func_0, &__LBHPerson__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__LBHPerson__test_block_impl_0
結(jié)構(gòu)體中增加了一個(gè)成員變量LBHPerson *self;
碎节,所以self
是會(huì)被捕獲的,因?yàn)閛c函數(shù)默認(rèn)是會(huì)有兩個(gè)參數(shù)(LBHPerson * self, SEL _cmd)
抵卫,而參數(shù)是局部變量
狮荔,局部變量是會(huì)被捕獲的。
問題:
如果在block中使用成員變量或者調(diào)用實(shí)例的屬性會(huì)有什么不同的結(jié)果
@interface LBHPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LBHPerson
- (void)test
{
void(^block)(void) = ^{
NSLog(@"%@",self.name);
NSLog(@"%@",_name);
};
block();
}
@end
通過xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc LBHPerson.m
編譯成底層代碼
struct __LBHPerson__test_block_impl_0 {
struct __block_impl impl;
struct __LBHPerson__test_block_desc_0* Desc;
LBHPerson *self;
__LBHPerson__test_block_impl_0(void *fp, struct __LBHPerson__test_block_desc_0 *desc, LBHPerson *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __LBHPerson__test_block_func_0(struct __LBHPerson__test_block_impl_0 *__cself) {
LBHPerson *self = __cself->self; // bound by copy
//通過調(diào)用self的getter方法獲取屬性值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9___44hp612k7gxwp82fznnkpm0000gn_T_LBHPerson_03f602_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));
//通過self從其ivars中找到成員變量
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f9___44hp612k7gxwp82fznnkpm0000gn_T_LBHPerson_03f602_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_LBHPerson$_name)));
}
在__LBHPerson__test_block_impl_0
結(jié)構(gòu)體中只捕獲了LBHPerson *self;
介粘,在由代碼塊中封裝的函數(shù)中轴合,是通過self
間接獲取的。
block 類型
int main(int argc, const char * argv[]) {
@autoreleasepool {
// __NSGlobalBlock__ : NSBlock : NSObject
void (^block)(void) = ^{
NSLog(@"Hello");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
}
return 0;
}
注意
:在不同版本的xcode上可能會(huì)有差異碗短,但最終都繼承于NSObjcet
從上述打印內(nèi)容可以看出block最終都是繼承自NSBlock類型受葛,而NSBlock繼承于NSObjcet。那么block其中的isa指針其實(shí)是來自NSObject中的。這也更加印證了block的本質(zhì)其實(shí)就是OC對(duì)象
总滩。
block的3種類型
通過代碼查看一下block在什么情況下其類型會(huì)各不相同
int c = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1. 內(nèi)部沒有調(diào)用外部變量的block
void (^block1)(void) = ^{
NSLog(@"Hello");
};
// 2. 內(nèi)部調(diào)用auto變量的block
int a = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d",a);
};
// 3. 內(nèi)部調(diào)用局部static變量的block
//static int b = 10;
//void (^block3)(void) = ^{
// NSLog(@"Hello - %d",b);
//};
// 4. 內(nèi)部調(diào)用全局變量的block
//void (^block4)(void) = ^{
// NSLog(@"Hello - %d",c);
//};
// 5. 直接調(diào)用的block的class
NSLog(@"%@ %@ %@ %@ %@", [block1 class], [block2 class],[^{
NSLog(@"%d",a);
} class]);
}
return 0;
}
輸出結(jié)果
__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
block可以分為三種各類型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock ) //全局block
__NSStackBlock__ ( _NSConcreteStackBlock ) //棧區(qū)block
__NSMallocBlock__ ( _NSConcreteMallocBlock ) //堆區(qū)block
看下通過clang
編譯結(jié)果
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 __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int a;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __main_block_impl_2 {
struct __block_impl impl;
struct __main_block_desc_2* Desc;
int a;
__main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在底層C++代碼中纲堵,block
的impl.isa = &_NSConcreteStackBlock;
,三個(gè)block的isa指針全部都指向_NSConcreteStackBlock類型地址闰渔。
問題:
為什么底層編譯結(jié)果和打印結(jié)果不一致runtime運(yùn)行時(shí)過程中也許對(duì)類型進(jìn)行了轉(zhuǎn)變席函。最終類型當(dāng)然以runtime運(yùn)行時(shí)類型也就是我們打印出的類型為準(zhǔn)。
block在內(nèi)存中的存儲(chǔ)
通過下面一張圖看一下不同block的存放區(qū)域
數(shù)據(jù)段中的NSGlobalBlock直到程序結(jié)束才會(huì)被回收冈涧,不過我們很少使用到NSGlobalBlock類型的block茂附,因?yàn)檫@樣使用block并沒有什么意義。
NSStackBlock類型的block存放在棧中督弓,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放营曼,作用域執(zhí)行完畢之后就會(huì)被立即釋放,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉愚隧。
NSMallocBlock是在平時(shí)編碼過程中最常使用到的蒂阱。存放在堆中需要我們自己進(jìn)行內(nèi)存管理。
block是如何定義其類型
block是如何定義其類型狂塘,依據(jù)什么來為block定義不同的類型并分配在不同的空間呢录煤?首先看下面一張圖
1、沒有訪問
auto
變量是全局block
2荞胡、訪問了auto
變量是棧區(qū)block
3妈踊、棧區(qū)block
調(diào)用copy
變成堆區(qū)block
注意
:這是對(duì)MRC環(huán)境得出的結(jié)論,ARC打印結(jié)果會(huì)有差別泪漂,因?yàn)锳RC內(nèi)部會(huì)做很多事情
先關(guān)閉ARC環(huán)境响委,看下這個(gè)結(jié)論是否正確
// MRC環(huán)境!=蚜骸赘风!
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Global:沒有訪問auto變量:__NSGlobalBlock__
void (^block1)(void) = ^{
NSLog(@"block1---------");
};
// Stack:訪問了auto變量: __NSStackBlock__
int a = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", a);
};
// Global:訪問局部static變量:__NSGlobalBlock__
static int b = 10;
void (^block3)(void) = ^{
NSLog(@"block2---------%d", b);
};
NSLog(@"%@ %@ %@ %@", [block1 class], [block2 class],[block3 class],[[block2 copy] class]);
}
return 0;
}
輸出結(jié)果
-
沒有訪問auto變量
的block是__NSGlobalBlock__
類型的,存放在數(shù)據(jù)段中纵刘。 -
訪問了auto變量
的block是__NSStackBlock__
類型的邀窃,存放在棧中。 -
__NSStackBlock__
類型的block調(diào)用copy
成為__NSMallocBlock__
類型并被復(fù)制存放在堆中假哎。
NSGlobalBlock類型的我們很少使用到瞬捕,因?yàn)槿绻恍枰L問外界的變量,直接通過函數(shù)實(shí)現(xiàn)就可以了舵抹,不需要使用block肪虎。
但是__NSStackBlock__訪問了auto變量
,并且是存放在棧中
的惧蛹,上面提到過扇救,棧中的代碼在作用域結(jié)束之后內(nèi)存就會(huì)被銷毀
刑枝,那么我們很有可能block內(nèi)存銷毀之后才去調(diào)用他,那樣就會(huì)發(fā)生問題迅腔,通過下面代碼可以證實(shí)這個(gè)問題装畅。
//MRC
void (^block)(void);
void test()
{
// __NSStackBlock__
int a = 10;
block = ^{
NSLog(@"block---------%d", a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
輸出結(jié)果
問題:
可以發(fā)現(xiàn)a的值變?yōu)榱瞬豢煽氐囊粋€(gè)數(shù)字。為什么會(huì)發(fā)生這種情況呢沧烈?因?yàn)樯鲜龃a中創(chuàng)建的
block是__NSStackBlock__類型
的掠兄,因此block是存儲(chǔ)在棧中的,那么當(dāng)test函數(shù)執(zhí)行完畢之后锌雀,棧內(nèi)存中block所占用的內(nèi)存已經(jīng)被系統(tǒng)回收
蚂夕,因此就有可能出現(xiàn)亂得數(shù)據(jù)。查看其c++代碼可以更清楚的理解腋逆。
為了避免這種情況發(fā)生婿牍,可以通過copy將NSStackBlock類型的block轉(zhuǎn)化為NSMallocBlock類型的block,將block存儲(chǔ)在堆中闲礼,以下是修改后的代碼
//MRC
void (^block)(void);
void test()
{
// __NSStackBlock__ 調(diào)用copy 轉(zhuǎn)化為__NSMallocBlock__
int age = 10;
block = [^{
NSLog(@"block---------%d", age);
} copy];
[block release];
}
看下此時(shí)的輸出結(jié)果
那么其他類型的block調(diào)用copy會(huì)改變block類型嗎牍汹?
所以在平時(shí)開發(fā)過程中MRC環(huán)境下經(jīng)常需要使用copy來保存block铐维,將棧上的block拷貝到堆中柬泽,即使棧上的block被銷毀,堆上的block也不會(huì)被銷毀
嫁蛇,需要我們自己調(diào)用release操作來銷毀锨并。而在ARC環(huán)境下系統(tǒng)會(huì)自動(dòng)調(diào)用copy操作,使block不會(huì)被銷毀睬棚。
ARC幫我們做了什么
在ARC環(huán)境下第煮,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block進(jìn)行一次copy
操作,將block復(fù)制到堆上抑党。
將上面的例子放在ARC環(huán)境中包警,看下是否會(huì)被copy到堆區(qū)
//ARC
void (^block)(void);
void test()
{
// __NSStackBlock__
int a = 10;
block = ^{
NSLog(@"block---------%d", a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
NSLog(@"== %@",[block class]);
}
return 0;
}
輸出結(jié)果
在ARC下block被copy到堆區(qū)
block對(duì)對(duì)象變量的捕獲
block一般使用過程中都是對(duì)對(duì)象變量的捕獲,那么對(duì)象變量的捕獲同基本數(shù)據(jù)類型變量相同嗎底靠?
//ARC
@interface LBHPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LBHPerson
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
LBHPerson *person = [[LBHPerson alloc] init];
person.name = @"liu";
block = ^{
NSLog(@"------block內(nèi)部%@",person.name);
};
} // 執(zhí)行完畢害晦,person沒有被釋放
NSLog(@"--------");
} // person 釋放
return 0;
}
運(yùn)行程序
person
是小括號(hào)(116-123行)中的一個(gè)auto變量
,按理說它的生命周期超出這個(gè)括號(hào)時(shí)就已經(jīng)結(jié)束暑中,但是在124行的斷點(diǎn)處壹瘟,person的dealloc
方法并沒有執(zhí)行,繼續(xù)運(yùn)行
運(yùn)行到斷點(diǎn)126行鳄逾,發(fā)現(xiàn)person調(diào)用了dealloc
方法稻轨,此時(shí)person才被銷毀
丽惶,由于person
是auto變量
团驱,block代碼塊中有使用了person引镊,所以block會(huì)捕獲person
,即block對(duì)person有一個(gè)強(qiáng)引用
的止,所以block不被銷毀的話,peroson也不會(huì)銷毀层坠。
查看源代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LBHPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LBHPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
將上述代碼轉(zhuǎn)移到MRC環(huán)境下辐宾,在MRC環(huán)境下即使block還在,person卻被釋放掉了询筏。因?yàn)镸RC環(huán)境下block在楅叛撸空間,椣犹祝空間對(duì)外面的person不會(huì)進(jìn)行強(qiáng)引用逆屡。
//MRC環(huán)境下代碼
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
LBHPerson *person = [[LBHPerson alloc] init];
person.name = @"liu";
block = ^{
NSLog(@"------block內(nèi)部%@",person.name);
};
[person release];
} // person被釋放
NSLog(@"--------");
}
return 0;
}
運(yùn)行結(jié)果
在126行的斷點(diǎn)處person調(diào)用了dealloc
,被銷毀了踱讨。
block調(diào)用copy操作之后魏蔗,person不會(huì)被釋放。
block = [^{
NSLog(@"------block內(nèi)部%d",person.age);
} copy];
前面提到過痹筛,只需要對(duì)椵褐危空間的block進(jìn)行一次copy
操作,將椫愠恚空間的block拷貝到堆中
谣旁,person就不會(huì)被釋放
,說明堆空間的block可能會(huì)對(duì)person進(jìn)行一次retain操作滋早,以保證person不會(huì)被銷毀榄审。堆空間的block自己銷毀之后也會(huì)對(duì)持有的對(duì)象進(jìn)行release操作。
椄唆铮空間上的block不會(huì)對(duì)對(duì)象強(qiáng)引用搁进,堆空間的block有能力持有外部調(diào)用的對(duì)象,即對(duì)對(duì)象進(jìn)行強(qiáng)引用或去除強(qiáng)引用的操作
__weak
__weak添加之后昔头,person在作用域執(zhí)行完畢之后就被銷毀了
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
LBHPerson *person = [[LBHPerson alloc] init];
person.name = @"liu";
__weak LBHPerson *waekPerson = person;
block = ^{
NSLog(@"------block內(nèi)部%@",waekPerson.name);
};
}
NSLog(@"--------");
}
return 0;
}
通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
編譯成底層源碼
注意
: __weak修飾變量饼问,需要告知編譯器使用ARC環(huán)境及版本號(hào)否則會(huì)報(bào)錯(cuò)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LBHPerson *__weak waekPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LBHPerson *__weak _waekPerson, int flags=0) : waekPerson(_waekPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在__main_block_impl_0
中也是用__weak
去修飾的
__main_block_copy_0 和 __main_block_dispose_0
當(dāng)block中捕獲對(duì)象類型的變量時(shí),我們發(fā)現(xiàn)block結(jié)構(gòu)體__main_block_impl_0
的描述結(jié)構(gòu)體__main_block_desc_0
中多了兩個(gè)參數(shù)copy
和dispose
函數(shù)揭斧,查看源碼
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};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->waekPerson, (void*)src->waekPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->waekPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
copy
和dispose
函數(shù)中傳入的都是__main_block_impl_0
結(jié)構(gòu)體本身
copy本質(zhì)就是__main_block_copy_0函數(shù)
莱革,__main_block_copy_0
函數(shù)內(nèi)部調(diào)用_Block_object_assign
函數(shù)
dispose本質(zhì)就是__main_block_dispose_0函數(shù)
,__main_block_dispose_0
函數(shù)內(nèi)部調(diào)用_Block_object_dispose
函數(shù)
_Block_object_assign函數(shù)調(diào)用時(shí)機(jī)及作用
當(dāng)block進(jìn)行copy操作
的時(shí)候就會(huì)自動(dòng)調(diào)用__main_block_desc_0
內(nèi)部的__main_block_copy_0
函數(shù)未蝌,__main_block_copy_0
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)驮吱。
_Block_object_assign
函數(shù)會(huì)自動(dòng)根據(jù)__main_block_impl_0結(jié)構(gòu)體內(nèi)部的person是什么類型的指針
,對(duì)person對(duì)象產(chǎn)生強(qiáng)引用或者弱引用
萧吠。可以理解為_Block_object_assign函數(shù)內(nèi)部會(huì)對(duì)person進(jìn)行引用計(jì)數(shù)器的操作纸型,如果__main_block_impl_0結(jié)構(gòu)體內(nèi)person指針是__strong類型,則為強(qiáng)引用牧氮,引用計(jì)數(shù)+1踱葛,如果__main_block_impl_0結(jié)構(gòu)體內(nèi)person指針是__weak類型,則為弱引用光坝,引用計(jì)數(shù)不變尸诽。
_Block_object_dispose函數(shù)調(diào)用時(shí)機(jī)及作用
當(dāng)block從堆中移除
時(shí)就會(huì)自動(dòng)調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù)
,__main_block_dispose_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)盯另。
_Block_object_dispose會(huì)對(duì)person對(duì)象做釋放操作
性含,類似于release,也就是斷開對(duì)person對(duì)象的引用鸳惯,而person究竟是否被釋放還是取決于person對(duì)象自己的引用計(jì)數(shù)商蕴。
后續(xù)繼續(xù)補(bǔ)充
Block循環(huán)引用
正常釋放
:是指A持有B的引用,當(dāng)A調(diào)用dealloc方法芝发,給B發(fā)送release信號(hào)绪商,B收到release信號(hào),如果此時(shí)B的retainCount(即引用計(jì)數(shù))為0時(shí)后德,則調(diào)用B的dealloc方法循環(huán)引用
:A部宿、B相互持有,所以導(dǎo)致A無法調(diào)用dealloc方法給B發(fā)送release信號(hào),而B也無法接收到release信號(hào)落蝙。所以A、B此時(shí)都無法釋放
如下圖所示:
相關(guān)例子
例1
//代碼一
NSString *name = @"LBH";
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();
//代碼二
UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
};
問題:
上述兩段代碼是否出現(xiàn)循環(huán)引用?
解答:
代碼一種發(fā)生了循環(huán)引用
,因?yàn)樵?code>block內(nèi)部使用了外部變量name捐顷,導(dǎo)致block持有了self
,而self原本是持有block的
,所以導(dǎo)致了self和block的相互持有
渣蜗。
代碼二中無循環(huán)引用托享,雖然也使用了外部變量,但是self并沒有持有animation的block
碧查,僅僅只有animation持有self迄沫,不構(gòu)成相互持有
例2
新建一個(gè)頁面B,它是從頁面A push過來的
//類擴(kuò)展
typedef void(^LBHBlock)(void);
@interface ViewController ()
@property (nonatomic, copy) LBHBlock lbhblock;
@property (nonatomic, copy) NSString *name;
@end
//實(shí)現(xiàn)
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 循環(huán)引用
self.name = @"lbh";
[self test1];
}
- (void)test1
{
// __weak typeof(self) weakSelf = self;
self.lbhblock = ^{
NSLog(@"%@",self.name);
};
}
- (void)dealloc{
NSLog(@"dealloc 來了");
}
@end
從當(dāng)前的頁面B 返回到頁面A 逝她,頁面B的dealloc
并沒有執(zhí)行宁昭,由于循環(huán)引用導(dǎo)致頁面無法釋放
解決循環(huán)引用
解決循環(huán)引用常見的方式有以下幾種:
1疆拘、weak-strong-dance
2、__block
修飾對(duì)象(需要注意的是在block內(nèi)部需要置空
對(duì)象,而且block必須調(diào)用
)
3、傳遞對(duì)象self作為block的參數(shù)
白华,提供給block內(nèi)部使用
4潮太、使用NSProxy
weak-strong-dance
- 如果block內(nèi)部并
未嵌套block
,直接使用__weak修飾self
即可
- (void)test1
{
__weak typeof(self) weakSelf = self;
self.lbhblock = ^{
NSLog(@"%@",weakSelf.name);
};
}
此時(shí)的weakSelf 和 self 指向同一片內(nèi)存空間
朋沮,且使用__weak不會(huì)導(dǎo)致self的引用計(jì)數(shù)發(fā)生變化
纠亚,可以通過打印weakSelf和self的指針地址条篷,以及self的引用計(jì)數(shù)來驗(yàn)證
- (void)test2
{
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
__weak typeof(self) weakSelf = self;
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
self.lbhblock = ^{
NSLog(@"%@",weakSelf.name);
};
NSLog(@"%p %p",weakSelf, self);
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self.lbhblock)));
NSLog(@"%ld",(long)CFGetRetainCount((__bridge CFTypeRef)(self)));
}
- 如果block內(nèi)部
嵌套block
,需要同時(shí)使用__weak 和 __strong
- (void)test3
{
__weak typeof(self) weakSelf = self;
self.lbhblock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%s %@",__func__,strongSelf.name);
});
};
self.lbhblock();
}
運(yùn)行結(jié)果
【注意】
:實(shí)際上嵌套的block內(nèi)部使用weakSelf
并不一定會(huì)出現(xiàn)問題蚕冬,不過為了程序的嚴(yán)謹(jǐn)通常還是會(huì)使用strongSelf
__block修飾變量
這種方式同樣依賴于中介者模式
,屬于手動(dòng)釋放
忧侧,是通過__block修飾對(duì)象躺屁,主要是因?yàn)開_block修飾的對(duì)象是可以改變的
- (void)test4
{
__block ViewController *vc = self;
self.lbhblock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%s %@",__func__,vc.name);
vc = nil;
});
};
self.lbhblock();
}
運(yùn)行結(jié)果
【注意】
這里的block必須調(diào)用
耐亏,如果不調(diào)用block,vc就不會(huì)置空李根,那么依舊是循環(huán)引用囱持,self和block都不會(huì)被釋放
對(duì)象self作為參數(shù)
主要是將對(duì)象self作為參數(shù),提供給block內(nèi)部使用,不會(huì)有引用計(jì)數(shù)問題
//聲明一個(gè)新的block
typedef void(^LBH2Block)(ViewController *);
@property (nonatomic, copy) LBH2Block lbh2block;
- (void)test5
{
self.lbh2block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%s %@",__func__,vc.name);
});
};
self.lbh2block(self);
}
運(yùn)行結(jié)果
NSProxy 虛擬類
OC是只能
單繼承
的語言枯怖,但是它是基于運(yùn)行時(shí)的機(jī)制
,所以可以通過NSProxy
來實(shí)現(xiàn)偽多繼承
椒袍,填補(bǔ)了多繼承的空白NSProxy
和NSObject
是同級(jí)的一個(gè)類辨赐,也可以說是一個(gè)虛擬類
,只是實(shí)現(xiàn)了NSObject的協(xié)議NSProxy
其實(shí)是一個(gè)消息重定向封裝的一個(gè)抽象類,類似一個(gè)代理人式散,中間件,可以通過繼承它响驴,并重寫下面兩個(gè)方法來實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
使用場(chǎng)景
NSProxy的使用場(chǎng)景主要有兩種:
- 實(shí)現(xiàn)多繼承功能
- 解決了
NSTimer&CADisplayLink
創(chuàng)建時(shí)對(duì)self強(qiáng)引用問題,參考YYKit
的YYWeakProxy
撕蔼。
循環(huán)引用解決原理
主要是通過自定義的NSProxy
類的對(duì)象來代替self
豁鲤,并使用方法實(shí)現(xiàn)消息轉(zhuǎn)發(fā)
下面是NSProxy子類的實(shí)現(xiàn)以及使用的場(chǎng)景
step1
自定義一個(gè)NSProxy
的子類LBHProxy
@interface LBHProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
@interface LBHProxy ()
@property(nonatomic, weak, readonly) NSObject *objc;
@end
@implementation LBHProxy
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
//2.有了方法簽名之后就會(huì)調(diào)用方法實(shí)現(xiàn)
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
//1、查詢?cè)摲椒ǖ姆椒ê灻?- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
NSMethodSignature *signature;
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
}else{
signature = [super methodSignatureForSelector:sel];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.objc respondsToSelector:aSelector];
}
@end
step2:
自定義Cat類和Dog類
//********Cat類********
@interface Cat : NSObject
@end
@implementation Cat
- (void)eat{
NSLog(@"貓吃魚");
}
@end
//********Dog類********
@interface Dog : NSObject
@end
@implementation Dog
- (void)shut{
NSLog(@"狗叫");
}
@end
step3:
通過LBHProxy實(shí)現(xiàn)多繼承功能
- (void)lbh_proxyTest{
Dog *dog = [[Dog alloc] init];
Cat *cat = [[Cat alloc] init];
LBHProxy *proxy = [LBHProxy alloc];
[proxy transformObjc:cat];
[proxy performSelector:@selector(eat)];
[proxy transformObjc:dog];
[proxy performSelector:@selector(shut)];
}
通過LBHProxy解決定時(shí)器中self的強(qiáng)引用問題
self.timer = [NSTimer timerWithTimeInterval:1 target:[LBHProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
總結(jié)
循環(huán)應(yīng)用的解決方式從根本上來說就兩種,以self -> block -> self
為例
打破
self 對(duì) block
的強(qiáng)引用视译,可以block屬性修飾符使用weak,但是這樣會(huì)導(dǎo)致block還沒創(chuàng)建完就釋放了
省古,所以從這里打破強(qiáng)引用行不通-
打破
block對(duì)self
的強(qiáng)引用,主要就是self的作用域和block作用域的通訊,通訊有代理蔼两、傳值抑胎、通知歧蒋、傳參等幾種方式,用于解決循環(huán)鲫咽,常見的解決方式如下:weak-strong-dance
__block
(block內(nèi)對(duì)象置空,且調(diào)用block)將對(duì)象
self
作為block的參數(shù)通過
NSProxy
的子類代替self
問題:
block為什么需要調(diào)用?
解答:
block在底層是類型為__main_block_impl_0
結(jié)構(gòu)體河绽,通過其同名構(gòu)造函數(shù)創(chuàng)建,第一個(gè)傳入的block的內(nèi)部實(shí)現(xiàn)代碼塊鉴未,即__main_block_func_0惰聂,用fp表示,然后賦值給impl的FuncPtr屬性换棚,然后在main中進(jìn)行了調(diào)用扮叨,這也是block為什么需要調(diào)用的原因戴质。如果不調(diào)用漾稀,block內(nèi)部實(shí)現(xiàn)的代碼塊將無法執(zhí)行,可以總結(jié)為以下兩點(diǎn)
- 函數(shù)聲明:即block內(nèi)部實(shí)現(xiàn)聲明成了一個(gè)函數(shù)
__main_block_func_0
- 執(zhí)行具體的函數(shù)實(shí)現(xiàn):通過調(diào)用block的FuncPtr指針建瘫,調(diào)用block執(zhí)行
4.3 __block的原理
對(duì)a加一個(gè)__block
崭捍,然后在block中對(duì)a進(jìn)行++操作
int main(){
__block int a = 11;
void(^block)(void) = ^{
a++;
printf("%d", a);
};
block();
return 0;
}
底層編譯成如下
struct __Block_byref_a_0 {//__block修飾的外界變量的結(jié)構(gòu)體
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {//block的結(jié)構(gòu)體類型
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) {//構(gòu)造方法
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//block內(nèi)部實(shí)現(xiàn)
__Block_byref_a_0 *a = __cself->a; // bound by ref 指針拷貝,此時(shí)的對(duì)象a 與 __cself對(duì)象的a 指向同一片地址空間
//等同于 外界的 a++
(a->__forwarding->a)++;
printf("%d", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
int main(){
//__Block_byref_a_0 是結(jié)構(gòu)體啰脚,a 等于 結(jié)構(gòu)體的賦值殷蛇,即將外界變量a 封裝成對(duì)象
//&a 是外界變量a的地址
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11};
//__main_block_impl_0中的第三個(gè)參數(shù)&a,是封裝的對(duì)象a的地址
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
main中的a是通過外界變量封裝的對(duì)象
__main_block_impl_0
中橄浓,將對(duì)象a的地址&a
給構(gòu)造函數(shù)__main_block_func_0
內(nèi)部對(duì)a的處理是指針拷貝
粒梦,此時(shí)創(chuàng)建的對(duì)象a與傳入對(duì)象的a指向同一片內(nèi)存空間
總結(jié):
外界變量會(huì)生成
__Block_byref_a_0
結(jié)構(gòu)體,結(jié)構(gòu)體用來保存原始變量的指針和值將變量生成的結(jié)構(gòu)體對(duì)象的
指針地址傳遞給block
荸实,然后在block內(nèi)部就可以對(duì)外界變量進(jìn)行操作了
兩種拷貝對(duì)比如下
值拷貝 - 深拷貝匀们,只是拷貝數(shù)值,且拷貝的值不可更改准给,指向不同的內(nèi)存空間泄朴,案例中
普通變量a就是值拷貝
指針拷貝 - 淺拷貝,生成的對(duì)象指向同一片內(nèi)存空間露氮,案例中經(jīng)過
__block修飾的變量a就是指針拷貝