1. Block內(nèi)存管理

OC代碼轉(zhuǎn)換成C++代碼
void(*block)(void);
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_3fd458_mi_0,(age->__forwarding->age));
}
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*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
_block的內(nèi)部要調(diào)用外邊的變量,_block的desc0的結(jié)構(gòu)體里會(huì)多出
copy和dispose函數(shù)方法,copy的函數(shù)方法中會(huì)調(diào)用_Block_object_assign進(jìn)行內(nèi)存管理.
- 當(dāng)block在棧上時(shí),并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用.
- 當(dāng)block被copy到堆時(shí)
??? ①. 會(huì)調(diào)用block內(nèi)部的copy函數(shù).
??? ②. copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù).
??? ③._Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain).


當(dāng)block從堆中移除時(shí)
- 會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
- dispose函數(shù)內(nèi)部會(huì)調(diào)用
_Block_object_dispose函數(shù)_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的__block變量(release)


對(duì)象類(lèi)型的auto變量、__block變量
- 當(dāng)block在棧上時(shí),對(duì)它們都不會(huì)產(chǎn)生強(qiáng)引用.
- 當(dāng)block拷貝到堆上時(shí),都會(huì)通過(guò)copy函數(shù)來(lái)處理它們.
- __block變量(假設(shè)變量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a,8/*BLOCK_FIELD_IS_BYREF*/);- 對(duì)象類(lèi)型的auto變量(假設(shè)變量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p,3/*BLOCK_FIELD_IS_OBJECT*/);
- 當(dāng)block從堆上移除時(shí),都會(huì)通過(guò)dispose函數(shù)來(lái)釋放它們
- __block變量(假設(shè)變量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);- 對(duì)象類(lèi)型的auto變量(假設(shè)變量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

2. __block的__forwarding指針

用"__block"修飾auto變量xxx的時(shí)候,系統(tǒng)會(huì)將這個(gè)auto變量xxx轉(zhuǎn)換成一個(gè)__Block_byref_xxx_0結(jié)構(gòu)體類(lèi)型,結(jié)構(gòu)體中有個(gè)成員__forwarding。當(dāng)block在棧區(qū)的時(shí)候,__forwarding指向棧區(qū)的__Block_byref_xxx_0結(jié)構(gòu)體本身內(nèi)存地址;當(dāng)block被copy到堆區(qū)的時(shí)候,棧上block變量?jī)?nèi)的__forwarding將會(huì)指向堆上的block變量,從而進(jìn)一步訪問(wèn)block變量?jī)?nèi)部的成員。這樣,前文中訪問(wèn)age的時(shí)候通過(guò)" (age->__forwarding->age) = 20;"這種做法也就明白了。
3. __block修飾對(duì)象類(lèi)型
- block內(nèi)部的指針指向包裝好的結(jié)構(gòu)體就是強(qiáng)指針,沒(méi)有弱引用.結(jié)構(gòu)體內(nèi)部的對(duì)象指向外部的變量的指針是強(qiáng)指針還是弱指針是由外部的變量的修飾詞決定的.
- 如果
__block修飾的是對(duì)象類(lèi)型,block從棧區(qū)copy到堆區(qū)的時(shí)候,包裝好的對(duì)象會(huì)增加copy和dispose兩個(gè)函數(shù)對(duì)包裝好的對(duì)象做內(nèi)存管理.
- 當(dāng)
__block變量在棧上時(shí),不會(huì)對(duì)指向的對(duì)象產(chǎn)生強(qiáng)引用- 當(dāng)
__block變量被copy到堆時(shí)
???①. 會(huì)調(diào)用__block變量?jī)?nèi)部的copy函數(shù)
???②. copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
???③._Block_object_assign函數(shù)會(huì)根據(jù)所指向?qū)ο蟮男揎椃?code>__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時(shí)會(huì)retain,MRC時(shí)不會(huì)retain)- 如果
__block變量從堆上移除
???①. 會(huì)調(diào)用__block變量?jī)?nèi)部的dispose函數(shù)
???②. dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
???③._Block_object_dispose函數(shù)會(huì)自動(dòng)釋放指向的對(duì)象(release)
4. block循環(huán)引用
一. demo1

demo1中,TestClass有一個(gè)block實(shí)例對(duì)象,self對(duì)block的關(guān)系為強(qiáng)持有。block實(shí)現(xiàn)中,也引用了當(dāng)前實(shí)例self,并且也為強(qiáng)引用。這樣一來(lái),self持有block,block持有self,所以?xún)烧叨紵o(wú)法釋放,就造成內(nèi)存泄露。將該.m文件轉(zhuǎn)換為C++實(shí)現(xiàn),看看block結(jié)構(gòu)體__TestClass__test_block_impl_0和block代碼塊函數(shù)__TestClass__test_block_func_0:
struct __TestClass__test_block_impl_0 {
struct __block_impl impl;
struct __TestClass__test_block_desc_0* Desc;
TestClass *const __strong self;
__TestClass__test_block_impl_0(void *fp, struct __TestClass__test_block_desc_0 *desc, TestClass *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestClass__test_block_func_0(struct __TestClass__test_block_impl_0 *__cself) {
TestClass *const __strong self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_TestClass_e9b143_mi_0, ((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}
正如上面分析的一樣:由"TestClass *const __strong self;"可見(jiàn)block結(jié)構(gòu)體中成員變量self為當(dāng)前類(lèi)實(shí)例的強(qiáng)指針;并且block的代碼塊中TestClass *const __strong self = __cself->self; // bound by copy也強(qiáng)引用著當(dāng)前類(lèi)TestClass的實(shí)例.
block與self的互相持有:

二. demo2
#import <Foundation/Foundation.h>
typedef void(^CSBlock)(void);
@interface Person : NSObject
/** age*/
@property(nonatomic,assign)int age;
/** blokc*/
@property(nonatomic,copy) CSBlock block;
@end
@implementation Person
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"age is %d", person.age);
};
person.block();
}
return 0;
}
將上方的OC代碼轉(zhuǎn)換為C++:

person的循環(huán)引用形成

block內(nèi)部有個(gè)強(qiáng)指針person指向MJPerson,MJPerson有個(gè)成員變量_block指向block.

當(dāng)我們執(zhí)行完main函數(shù)中的20行代碼的時(shí)候,main中的person指向MJPerson的指針就銷(xiāo)毀了.

三. 解決block與持有對(duì)象間的強(qiáng)引用關(guān)系.
在ARC環(huán)境下有以下三種解決方案:
① 使用"__weak";
② 使用"__unsafe_unretained";
③ 使用"__block"(必須要調(diào)用block)。
方案①大家應(yīng)該都清楚,在開(kāi)發(fā)的過(guò)程中都應(yīng)該使用過(guò).
方案②:“__unsafe_unretained”字面理解就是不安全的、不會(huì)導(dǎo)致引用計(jì)數(shù)增加。簡(jiǎn)單說(shuō)就是:不安全的弱引用。
“__weak”與“ __unsafe_unretained”對(duì)比:
"__weak":不會(huì)產(chǎn)生強(qiáng)引用,當(dāng)指向的對(duì)象銷(xiāo)毀時(shí),會(huì)自動(dòng)讓指針置為nil;
“ __unsafe_unretained”:不會(huì)產(chǎn)生強(qiáng)引用,不安全。當(dāng)指向的對(duì)象銷(xiāo)毀時(shí),指針存儲(chǔ)的地址值不變,這個(gè)時(shí)候指向的是一塊已經(jīng)被系統(tǒng)回收的內(nèi)存,這個(gè)時(shí)候繼續(xù)訪問(wèn)會(huì)引發(fā)"野指針異常"。
對(duì)于方案③demo:
#import <Foundation/Foundation.h>
#import "TestClass.h"
@implementation TestClass
- (void)test{
self.age = 20;
// __unsafe_unretained TestClass *weakself = self;
// __weak TestClass *weakself = self;
__block TestClass* weakSelf = self;
self.block = ^{
NSLog(@"%ld", weakSelf.age);
weakSelf = nil;
};
self.block();
}
- (void)dealloc{
NSLog(@"TestClass - %@",NSStringFromSelector(_cmd));
}
當(dāng)testClass實(shí)例銷(xiāo)毀的時(shí)候,block也釋放了,不會(huì)循環(huán)引用。
我們分析一下轉(zhuǎn)換后的C++代碼:
struct __TestClass__test_block_impl_0 {
struct __block_impl impl;
struct __TestClass__test_block_desc_0* Desc;
__Block_byref_weakSelf_0 *weakSelf; // by ref
__TestClass__test_block_impl_0(void *fp, struct __TestClass__test_block_desc_0 *desc, __Block_byref_weakSelf_0 *_weakSelf, int flags=0) : weakSelf(_weakSelf->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_weakSelf_0 {
void *__isa;
__Block_byref_weakSelf_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
TestClass *__strong weakSelf;
};
首先block結(jié)構(gòu)體內(nèi)持有__block類(lèi)型的"__Block_byref_weakSelf_0"對(duì)象;
其次“__block”類(lèi)型對(duì)象中“TestClass *__strong weakSelf;”,即持有TestClass實(shí)例。
由于self持有了block,所以當(dāng)前對(duì)象self、block已經(jīng)__block變量三者的關(guān)系為:

如此一來(lái):又是一個(gè)循環(huán)引用問(wèn)題,我們嘗試在block代碼塊內(nèi)部去掉"weakSelf = nil",實(shí)際結(jié)果是TestClass實(shí)例不會(huì)釋放掉。針對(duì)這種狀況,打破三者之間的循環(huán)鏈即可消除循環(huán)引用,解釋如下:
首先a. 對(duì)象(也就是持有block的對(duì)象)對(duì)block的持有關(guān)系肯定是強(qiáng)持有;
其次b. block對(duì)__block變量也是強(qiáng)持有的關(guān)系,這兩條線無(wú)法改動(dòng)!如果突破__block變量持有對(duì)象這條線,就可以了,這樣就可以通過(guò)調(diào)用block后,手動(dòng)設(shè)置__block對(duì)象為nil。
在本demo中__block變量定義如下:
struct __Block_byref_weakSelf_0 {
void *__isa;
__Block_byref_weakSelf_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
TestClass *__strong weakSelf;
};
也就是將結(jié)構(gòu)體__Block_byref_weakSelf_0中的成員變量"TestClass *__strong weakSelf"置為nil,即weakSelf = nil,這樣__block就不會(huì)持有當(dāng)前類(lèi)的實(shí)例了,所以循環(huán)被打破。打破后三者關(guān)系見(jiàn)下圖:

由此針對(duì)方案③:該方案唯一的缺點(diǎn)就是需要執(zhí)行block。這么麻煩的關(guān)鍵在于:執(zhí)行完block之后,在block體內(nèi)設(shè)置引用對(duì)象為nil,從而達(dá)到手動(dòng)將__block變量?jī)?nèi)部的關(guān)鍵成員置為nil,這樣就可以打破循環(huán)關(guān)系.
在ARC環(huán)境下有以下三種解決方案:
① 用__unsafe_unretained解決;
② 用__block解決(block可以不調(diào)用)。
“ __unsafe_unretained”同ARC一致;
在使用"_block"時(shí),我們先總結(jié)一下block被copy到堆上時(shí),底層做了啥(⊙⊙)?
當(dāng)__block變量被copy到堆時(shí),會(huì)調(diào)用__block變量?jī)?nèi)部的copy函數(shù)。copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)。_Block_object_assign函數(shù)會(huì)根據(jù)所指向?qū)ο蟮男揎椃?code>__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時(shí)會(huì)retain,MRC時(shí)不會(huì)retain)。
如果__block變量從堆上移除,會(huì)調(diào)用__block變量?jī)?nèi)部的dispose函數(shù)。dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放指向的對(duì)象(release)。
正是因?yàn)镸RC環(huán)境下,__block變量對(duì)所引用的對(duì)象為弱引用關(guān)系,所以“對(duì)象”、“block”與"block變量"三種之間處于開(kāi)環(huán)狀態(tài),也就不存在循環(huán)引用問(wèn)題,因此在MRC下用__block修飾被引用對(duì)象,block可以不調(diào)用。正如下面demo:
// ATTENTION:MRC環(huán)境
#import <Foundation/Foundation.h>
#import "TestClass.h"
@implementation TestClass
- (void)test{
self.age = 20;
__block TestClass* weakSelf = self;
self.block = ^{
NSLog(@"%ld", weakSelf.age);
};
}
- (void)dealloc{
[super dealloc];
NSLog(@"TestClass - %@",NSStringFromSelector(_cmd));
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
{
TestClass *testClass = [[TestClass alloc] init];
[testClass test];
[testClass release];
}
NSLog(@"---------");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
//2018-08-31 11:25:27.800865+0800 Block[74957:7622148] TestClass - dealloc
//2018-08-31 11:25:27.803116+0800 Block[74957:7622148] ---------
結(jié)果是TestClass實(shí)例被銷(xiāo)毀的時(shí)候,block也一起銷(xiāo)毀了。
四. __weak搭配__strong使用
__weak TestClass* weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%ld", strongSelf.age);
};
在本demo中,在block內(nèi)部重新使用__strong修飾weakSelf(被引用)變量是為了在block內(nèi)部有一個(gè)強(qiáng)指針指向weakSelf(弱引用)避免在block調(diào)用的時(shí)候weakSelf已經(jīng)被銷(xiāo)毀。有些時(shí)候block內(nèi)部訪問(wèn)的對(duì)象并不是當(dāng)前類(lèi)的實(shí)例,考慮到block可能很久才會(huì)銷(xiāo)毀,因此被block引用的對(duì)象應(yīng)該是弱引用,否則可能造成被引用對(duì)象毫無(wú)意義地存在于內(nèi)存中。既然是弱引用,一旦該對(duì)象在其他地方被銷(xiāo)毀,則block內(nèi)部的弱引用對(duì)象也就銷(xiāo)毀了,繼續(xù)訪問(wèn)也就會(huì)返回null,還是用demo說(shuō)話吧:
// 1. 新建一個(gè)Dog類(lèi),并實(shí)現(xiàn)dealloc方法;
@interface Dog : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Dog
- (void)dealloc{
NSLog(@"Dog - %@",NSStringFromSelector(_cmd));
}
// 2. 在另一個(gè)類(lèi)的block函數(shù)體類(lèi)訪問(wèn)dog的成員屬性name;
#import <Foundation/Foundation.h>
#import "TestClass.h"
#import "Dog.h"
@implementation TestClass
- (void)test{
Dog *dog = [[Dog alloc] init];
dog.name = @"小黑";
__weak Dog *weakDog = dog;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"開(kāi)始執(zhí)行block");
// __strong typeof(weakDog) strongDog = weakDog;
sleep(2);
NSLog(@"狗的名字:%@",weakDog.name);
});
sleep(1);
NSLog(@"模擬weakDog被釋放");
dog = nil;
/*
2018-09-04 14:43:32.966624+0800 Block[80253:7808973] 開(kāi)始執(zhí)行block
2018-09-04 14:43:33.956807+0800 Block[80253:7808911] 模擬weakDog被釋放
2018-09-04 14:43:33.957256+0800 Block[80253:7808911] Dog - dealloc
2018-09-04 14:43:34.972230+0800 Block[80253:7808973] 狗的名字:(null)
*/
}
- (void)dealloc{
NSLog(@"TestClass - %@",NSStringFromSelector(_cmd));
}
@end
模擬block內(nèi)部訪問(wèn)的對(duì)象在外部被提前釋放的情況,我在調(diào)用block的過(guò)程中特意將dog設(shè)置為nil,訪問(wèn)的結(jié)果是:“Block[76269:7674002] 狗的名字:(null)”,項(xiàng)目中block調(diào)用的時(shí)機(jī)是不確定的,被訪問(wèn)的對(duì)象何時(shí)候釋放也是不確定的,故而這種情況下僅僅使用__weak修飾被訪問(wèn)對(duì)象肯定存在問(wèn)題,為了更好解決這樣的問(wèn)題,我們用“__strong”修飾符在block內(nèi)部搭配外部的"__weak"修飾被訪問(wèn)對(duì)象,針對(duì)上面demo,正確的做法如下:
- (void)test{
Dog *dog = [[Dog alloc] init];
dog.name = @"小黑";
__weak Dog *weakDog = dog;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"開(kāi)始執(zhí)行block");
__strong typeof(weakDog) strongDog = weakDog;
sleep(2);
NSLog(@"狗的名字:%@",strongDog.name);
});
sleep(1);
NSLog(@"模擬weakDog被釋放");
dog = nil;
/*
2018-09-04 14:46:32.969188+0800 Block[80345:7811829] 開(kāi)始執(zhí)行block
2018-09-04 14:46:33.961744+0800 Block[80345:7811757] 模擬weakDog被釋放
2018-09-04 14:46:33.962013+0800 Block[80345:7811757] ---------
2018-09-04 14:46:34.974592+0800 Block[80345:7811829] 狗的名字:小黑
2018-09-04 14:46:34.974973+0800 Block[80345:7811829] Dog - dealloc
*/
}
在block內(nèi)將weakDog對(duì)象強(qiáng)引用為strongDog,執(zhí)行block過(guò)程中將dog設(shè)置為nil,結(jié)果仍能繼續(xù)訪問(wèn)。
想了解更多iOS學(xué)習(xí)知識(shí)請(qǐng)聯(lián)系:QQ(814299221)