棧對(duì)象、堆對(duì)象
棧
是一塊保存局部變量或函數(shù)參數(shù)值的內(nèi)存區(qū)域坟岔。在現(xiàn)代的操作系統(tǒng)中谒兄,每個(gè)線程都有一個(gè)對(duì)應(yīng)的棧。當(dāng)函數(shù)調(diào)用時(shí)炮车,一個(gè)棧幀Stack Frame會(huì)被放入棧內(nèi)。棧幀保存了這個(gè)函數(shù)涉及的參數(shù)酣溃、局部變量瘦穆、返回地址等相關(guān)信息。當(dāng)函數(shù)返回后這個(gè)棧幀就會(huì)被銷(xiāo)毀赊豌。而這一切都是系統(tǒng)自動(dòng)完成的扛或,程序員無(wú)需關(guān)心。
堆
同樣是一塊內(nèi)存區(qū)域碘饼。在這里內(nèi)存可以隨時(shí)分配和銷(xiāo)毀熙兔,但需要我們自己去請(qǐng)求悲伶,而不是系統(tǒng)幫我們完成。不像Stack里面的變量會(huì)隨著函數(shù)執(zhí)行而銷(xiāo)毀住涉,堆上面的東西在函數(shù)執(zhí)行之外依然能夠存活麸锉。
關(guān)于棧對(duì)象和堆對(duì)象,首先要搞清楚什么是對(duì)象舆声。事實(shí)上對(duì)象就是一段連續(xù)的內(nèi)存空間花沉,它有固定的布局。對(duì)象可以放在棧上也可以放在堆上媳握。對(duì)象被放在棧上就是棧對(duì)象碱屁,被放在堆上就是堆對(duì)象。但是在Object-C中蛾找,對(duì)象都是在堆上面創(chuàng)建的娩脾,比如執(zhí)行以下代碼:
NSObject *obj = [[NSObject alloc] init];
obj這個(gè)指針變量本身是保存在棧上面的,它的本質(zhì)就是int類(lèi)型打毛,但是它指向的對(duì)象是保存在堆上面的柿赊。[NSObject alloc]
就是請(qǐng)求在堆上面分配內(nèi)存。
那Object-C有沒(méi)有辦法在棧上面創(chuàng)建對(duì)象呢隘冲?Object-C沒(méi)有提供直接的方法闹瞧,但是還是可以通過(guò)以下方式來(lái)創(chuàng)建一個(gè)棧對(duì)象:
struct {
Class isa;
} fakeNSObject;
fakeNSObject.isa = [NSObject class];
NSObject *obj = (__bridge NSObject *)&fakeNSObject;
NSLog(@"%@", [obj description]);
fakeNSObject這個(gè)結(jié)構(gòu)體是保存在棧上面的,但是它有isa展辞,所以根據(jù)對(duì)象的定義它也是對(duì)象奥邮,所以它就成了一個(gè)棧對(duì)象。
事實(shí)上有些語(yǔ)言是支持在棧上面創(chuàng)建對(duì)象的罗珍,比如C++洽腺。在C++中類(lèi)的對(duì)象建立分為兩種,一種是靜態(tài)建立
覆旱,如A a
蘸朋;另一種是動(dòng)態(tài)建立
,如A* p=new A()扣唱,A*p=(A*)malloc()
藕坯;
靜態(tài)建立一個(gè)類(lèi)對(duì)象,是由編譯器為對(duì)象在椩肷常空間中分配內(nèi)存炼彪,通過(guò)直接移動(dòng)棧頂指針挪出適當(dāng)?shù)目臻g,然后在這片內(nèi)存空間上調(diào)用構(gòu)造函數(shù)形成一個(gè)棧對(duì)象正歼。
動(dòng)態(tài)建立類(lèi)對(duì)象辐马,是使用new運(yùn)算符將對(duì)象建立在堆空間中,在棧中只保留了指向該對(duì)象的指針局义。
棧對(duì)象有好處也有不好的地方喜爷。好處是內(nèi)存分配很快冗疮,而且它有確切的生命周期,不會(huì)內(nèi)存泄漏檩帐,因?yàn)楹瘮?shù)執(zhí)行完變量會(huì)被自動(dòng)銷(xiāo)毀术幔。但它的生命周期也是它的劣勢(shì)之一,因?yàn)楹瘮?shù)執(zhí)行完就被銷(xiāo)毀了轿塔,沒(méi)辦法在函數(shù)之外再使用它特愿。
那為什么Object-C總是在堆上面創(chuàng)建對(duì)象呢?
- 1勾缭、因?yàn)?strong>Object-C使用引用計(jì)數(shù)來(lái)管理內(nèi)存揍障。這種方式的好處是一個(gè)對(duì)象可以有多個(gè)Owner,而且只有當(dāng)這個(gè)對(duì)象的Owner個(gè)數(shù)為0時(shí)它才會(huì)被銷(xiāo)毀俩由。而棧對(duì)象有一個(gè)固有的Owner毒嫡,就是它所在的函數(shù)。當(dāng)我們把一個(gè)棧對(duì)象傳給另一段代碼時(shí)幻梯,即使是引用它也沒(méi)辦法阻止它在原有函數(shù)執(zhí)行完畢后被銷(xiāo)毀兜畸,當(dāng)它被銷(xiāo)毀后再去訪問(wèn)它就會(huì)發(fā)生錯(cuò)誤。簡(jiǎn)單而言碘梢,就是棧對(duì)象的生命周期不適合Objective-C的引用計(jì)數(shù)內(nèi)存管理方法咬摇。
- 2、棧對(duì)象不夠靈活煞躬,它在創(chuàng)建時(shí)長(zhǎng)度就已經(jīng)是固定的肛鹏。而Runtime的靈活性都是基于堆對(duì)象的。
但是Objective-C也不是完全不存在棧對(duì)象恩沛,Block就是一個(gè)特例在扰。
Block
Block的本質(zhì)
先創(chuàng)建一個(gè)簡(jiǎn)單的Block:
int blockStudy() {
void (^blk)(void) = ^{
int a = 0;
};
blk();
return 0;
}
將該代碼轉(zhuǎn)為C語(yǔ)言源碼:
// MRC
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc BlockStudy.m
// ARC
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.3.0 BlockStudy.m
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
// 構(gòu)造函數(shù)
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
int a = 0;
}
static struct __blockStudy_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};
int blockStudy() {
void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
可以看到
void (^blk)(void) = ^{int a = 0;};
被轉(zhuǎn)換成了
void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA));
去掉類(lèi)型轉(zhuǎn)換的部分,就變成了
void (*blk)(void) = &__blockStudy_block_impl_0(__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA);
其實(shí)就是調(diào)用__blockStudy_block_impl_0
的構(gòu)造函數(shù)在棧區(qū)創(chuàng)建了一個(gè)結(jié)構(gòu)體雷客。
首先看__blockStudy_block_impl_0
的定義:
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
// 構(gòu)造函數(shù)
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
調(diào)用構(gòu)造函數(shù)時(shí)的第一個(gè)參數(shù)是一個(gè)函數(shù)的指針:
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
int a = 0;
}
這個(gè)函數(shù)里面的代碼就是我們定義Block時(shí)的執(zhí)行函數(shù)芒珠,這個(gè)函數(shù)指針被保存在impl.FuncPtr。這個(gè)函數(shù)接收的參數(shù)__cself
其實(shí)就是Block本身的地址搅裙,相當(dāng)于我們平時(shí)在函數(shù)里面用的self皱卓。
第二個(gè)參數(shù)是一個(gè)靜態(tài)全局變量__blockStudy_block_desc_0_DATA
:
static struct __blockStudy_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};
里面保存了Block的大小
執(zhí)行Block的代碼
blk();
被轉(zhuǎn)換為:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉類(lèi)型轉(zhuǎn)換的部分
(blk->FuncPtr)(blk);
就是簡(jiǎn)單的使用函數(shù)指針調(diào)用函數(shù)。
這里有個(gè)地方需要注意部逮,就是__blockStudy_block_impl_0
被強(qiáng)轉(zhuǎn)換為了__block_impl
娜汁,這在C語(yǔ)言是可行的,因?yàn)?code>__block_impl位于__blockStudy_block_impl_0
的最頂部甥啄,就相當(dāng)于__block_impl
的變量直接排列在__blockStudy_block_impl_0
的頂部存炮。
再看__block_impl
的定義:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
它有一個(gè)isa變量
炬搭,而且這里它被賦值為&_NSConcreteStackBlock
蜈漓,結(jié)合前面我們定義Object-C中的棧對(duì)象穆桂,我們可以知道Block其實(shí)就是一個(gè)棧對(duì)象,它的類(lèi)就是_NSConcreteStackBlock
融虽。
自由變量的截取
一般我們創(chuàng)建的局部變量都是自由變量享完,因?yàn)橄到y(tǒng)會(huì)自動(dòng)在前面加上auto
int age = 10;
auto int age = 10;
int global_val = 1;
static int static_global_val = 1;
int blockStudy() {
int var = 10; // auto 變量
static int static_val = 2; // static 變量
void (^blk)(void) = ^{
NSLog(@"%@ %@ %@ %@", global_val, static_global_val, var, static_val);
};
blk();
return 0;
}
同樣把上面的截取局部變量的代碼轉(zhuǎn)為C語(yǔ)言源碼:
int global_val = 1;
static int static_global_val = 1;
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
int var;
int *static_val;
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int _var, int *_static_val, int flags=0) : var(_var), static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
int var = __cself->var; // bound by copy
int *static_val = __cself->static_val; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2__6smntvs6ng1ww18kf8713tc0000gn_T_BlockStudy_7f4b92_mi_0, global_val, static_global_val, var, (*static_val));
}
static struct __blockStudy_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};
int blockStudy() {
int var = 10;
static int static_val = 2;
void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, var, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
可以看到我們?cè)贐lock里面使用到的局部變量被作為成員變量追加到了__blockStudy_block_impl_0
結(jié)構(gòu)體中(值傳遞),靜態(tài)局部變量也是(指針傳遞)有额,而全局變量和未用到的局部變量則不會(huì)般又。
結(jié)論:我們創(chuàng)建一個(gè)block時(shí),實(shí)際上是創(chuàng)建了一個(gè)結(jié)構(gòu)體巍佑,這個(gè)結(jié)構(gòu)體保存了block的執(zhí)行函數(shù)的地址以及這個(gè)函數(shù)里面使用到的局部變量(自由變量)茴迁,一般情況下這些局部變量是被拷貝進(jìn)去的。
結(jié)合前面Block的源碼可以知道Block的內(nèi)存布局如下:
再看block執(zhí)行函數(shù)的實(shí)現(xiàn):
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int var = __cself->var; // bound by copy
int *static_val = __cself->static_val; // bound by copy
const char *a = fmt;
int b = var;
int c = global_val;
int d = static_global_val;
int e = (*static_val);
}
自動(dòng)生成的注釋已經(jīng)說(shuō)明了: bound by copy
萤衰,block對(duì)它引用的局部變量做了只讀拷貝堕义,也就是說(shuō)block引用的是局部變量的副本。所以在執(zhí)行block語(yǔ)法后脆栋,即使改寫(xiě)block中使用的局部變量的值也不會(huì)影響block執(zhí)行時(shí)局部變量的值倦卖。這也就是為什么在block里面修改局部變量的值時(shí)要額外加上__block
修飾符。
__block
我們都知道要在Block內(nèi)修改外面的局部變量就得給局部變量加上__block
修飾符椿争,不然編譯器會(huì)報(bào)錯(cuò)怕膛。接下來(lái)看看__block
的本質(zhì)。
先定義一個(gè)簡(jiǎn)單的__block變量:
void blockStudy() {
__block int myNumber = 10;
}
轉(zhuǎn)換為源碼:
struct __Block_byref_myNumber_0 {
void *__isa;
__Block_byref_myNumber_0 *__forwarding;
int __flags;
int __size;
int myNumber;
};
void blockStudy() {
__attribute__((__blocks__(byref))) __Block_byref_myNumber_0 myNumber = {
(void*)0,(__Block_byref_myNumber_0 *)&myNumber,
0,
sizeof(__Block_byref_myNumber_0),
10};
}
可以看到__block變量實(shí)際是生成了一個(gè)結(jié)構(gòu)體的實(shí)例秦踪,而這個(gè)實(shí)例最后的一個(gè)成員變量才是我們定義自由變量褐捻,而且還多了一個(gè)特別的__forwarding
變量。再看在block里面引用這個(gè)__block變量的情況:
int blockStudy() {
__block int number = 10;
void (^blk)(void) = ^{
number = 5;
};
blk();
return 0;
}
struct __Block_byref_myNumber_0 {
void *__isa;
__Block_byref_myNumber_0 *__forwarding;
int __flags;
int __size;
int myNumber;
};
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
__Block_byref_myNumber_0 *myNumber; // by ref
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, __Block_byref_myNumber_0 *_myNumber, int flags=0) : myNumber(_myNumber->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
__Block_byref_myNumber_0 *myNumber = __cself->myNumber; // bound by ref
(myNumber->__forwarding->myNumber) = 5;
}
static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
_Block_object_assign((void*)&dst->myNumber, (void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
_Block_object_dispose((void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __blockStudy_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};
int blockStudy() {
__attribute__((__blocks__(byref))) __Block_byref_myNumber_0 myNumber = {
(void*)0,
(__Block_byref_myNumber_0 *)&myNumber,
0,
sizeof(__Block_byref_myNumber_0),
10
};
void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, (__Block_byref_myNumber_0 *)&myNumber, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
同樣的__block變量被block捕獲了洋侨,但這次是引用傳遞:
__Block_byref_myNumber_0 *myNumber; // by ref
給__block變量賦值的執(zhí)行函數(shù)被轉(zhuǎn)換為:
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
__Block_byref_myNumber_0 *myNumber = __cself->myNumber; // bound by ref
(myNumber->__forwarding->myNumber) = 5;
}
這里的注釋變?yōu)榱?code>bound by ref舍扰,說(shuō)明是引用傳遞。而且這里是通過(guò)myNumber的__forwarding去修改值的希坚,而不是直接通過(guò)myNumber->myNumber來(lái)修改边苹,這里就涉及到block的存儲(chǔ)域問(wèn)題了。
Block存儲(chǔ)域
我們前面創(chuàng)建的Block都是存在棧上面的裁僧,而且它的類(lèi)是_NSConcreteStackBlock
个束。但除此之外還有另外兩種Block:_NSConcreteGlobalBlock
、_NSConcreteMallocBlock
聊疲。
_NSConcreteGlobalBlock
和全局變量一樣他是存在程序的數(shù)據(jù)區(qū)域的茬底,這種Block不捕獲任何自由變量,相當(dāng)于代碼片段获洲。
void (^blk)(void) = ^{};
int blockStudy() {
return 0;
}
// C語(yǔ)言源碼
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
_NSConcreteMallocBlock
是保存在堆上的阱表。
為什么需要_NSConcreteMallocBlock
呢?因?yàn)闂I系腂lock會(huì)隨著其所屬的變量作用域結(jié)束而被廢棄。這時(shí)候就需要把棧上的Block拷貝到堆上面最爬,使得Block語(yǔ)法記述的變量的作用域結(jié)束后堆上的Block依舊可以繼續(xù)存在涉馁。這也是為什么要把block的屬性聲明為copy的緣故。
但是在ARC之后block會(huì)被自動(dòng)復(fù)制到堆上爱致,即使你聲明的修飾符是strong,實(shí)際上效果是與聲明為copy一樣的烤送,也就不強(qiáng)求聲明為copy了,但官方仍然建議我們使用copy以顯示相關(guān)拷貝行為糠悯。
大部分的情況編譯器會(huì)自動(dòng)幫我們把block復(fù)制到堆上面帮坚,比如以下代碼:
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count)(return rate * count);
}
// 轉(zhuǎn)換后的源碼
blk_t func(int rate) {
blk_t tmp = &__func_block_impl_0(
__func_block_func_0, &__func_block_desc_0_DATA, rate);
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp);
}
再比如我們平常調(diào)用接口的方法:
- (void)loadData {
__block id myData;
[self getMyData:^(id result, NSError *error) {
myData = result;
}];
}
- (void)getMyData:(void(^)(id result, NSError *error))completionBlock {
if (completionBlock) { // __NSStackBlock__
}
[@[@"0"] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
completionBlock(nil, nil); // __NSStackBlock__
}];
dispatch_sync(dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL), ^{
completionBlock(nil, nil); // __NSStackBlock__
});
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(nil, nil); // __NSMallocBlock__
});
}
當(dāng)執(zhí)行到dispatch_async時(shí)block就被復(fù)制到堆上面了,如果還在棧上面互艾,那getMyData函數(shù)執(zhí)行完成后這個(gè)block就會(huì)被釋放试和,等異步回來(lái)執(zhí)行時(shí)就會(huì)出錯(cuò)。
那block被拷貝到堆上后纫普,__block變量會(huì)發(fā)生什么呢灰署?
當(dāng)Block被復(fù)制到堆上面后,它所使用的__block變量
也會(huì)被復(fù)制到堆上面局嘁。除此之外還有一個(gè)__forwarding
變量需要注意溉箕。一開(kāi)始__block變量
在棧上面創(chuàng)建時(shí),它的__forwarding
是指向它自己的悦昵,當(dāng)它被復(fù)制到堆上面后肴茄,原本的棧上面的__block變量
的__forwarding
會(huì)指向被復(fù)制出來(lái)的堆上面的__block變量
,而被復(fù)制到堆上面的__block變量
的__forwarding
則會(huì)指向它自己但指。
void blockStudy()
{
dispatch_block_t myBlock;
{
__block int a = 0;
myBlock = ^() {
a = 1;
};
a = 2;
}
}
把上面這段代碼轉(zhuǎn)成C語(yǔ)言源碼后:
void blockStudy()
{
dispatch_block_t myBlock;
{
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
(a.__forwarding->a) = 2;
}
}
可以看到原本的a = 2;
被轉(zhuǎn)成了(a.__forwarding->a) = 2;
寡痰,而a其實(shí)是個(gè)結(jié)構(gòu)體__Block_byref_a_0
。創(chuàng)建__Block_byref_a_0
時(shí)的第二個(gè)參數(shù)就是__forwarding
棋凳,這里傳進(jìn)去的值是(__Block_byref_a_0 *)&a
拦坠,也就是它自己本身。
小結(jié):當(dāng)用
__block
修飾一個(gè)局部變量時(shí)剩岳,實(shí)際上是在棧上創(chuàng)建了一個(gè)包含該變量的結(jié)構(gòu)體A1贞滨,該結(jié)構(gòu)體包含一個(gè)變量__forwarding
,此時(shí)棧上的A1的__forwarding
指向A1本身拍棕。block通過(guò)引用傳遞捕獲__block變量
晓铆,當(dāng)block被拷貝到堆上時(shí),__block變量
也被拷貝到堆上面有了A2绰播,這時(shí)就會(huì)同時(shí)存在A1和A2骄噪,讀取和修改就會(huì)有同步問(wèn)題,這時(shí)__forwarding變量
就起作用了蠢箩。復(fù)制到堆上面后链蕊,A1和A2的__forwarding變量
都會(huì)指向A2事甜,外部統(tǒng)一通過(guò)__forwarding變量
來(lái)讀取和修改變量就能保持一致了。
block捕獲對(duì)象類(lèi)型的自由變量
先看以下ARC環(huán)境下的簡(jiǎn)單例子:
當(dāng)超出實(shí)例的作用域后它就會(huì)被銷(xiāo)毀滔韵,如果我們用一個(gè)block來(lái)對(duì)這個(gè)變量進(jìn)行引用會(huì)發(fā)生什么呢讳侨?
這個(gè)時(shí)候myObject變量不會(huì)被銷(xiāo)毀,而且這個(gè)時(shí)候block已經(jīng)被復(fù)制到堆上了:
我們可以理解為堆上的block對(duì)myObject進(jìn)行了引用奏属,所以myObject不會(huì)被銷(xiāo)毀。
ARC環(huán)境下潮峦,一旦Block賦值就會(huì)觸發(fā)copy囱皿,__block就會(huì)copy到堆上,Block也是__NSMallocBlock忱嘹。
把ARC改為MRC:
myObject又被銷(xiāo)毀了嘱腥,而且block沒(méi)有復(fù)制到堆上:
我們手動(dòng)進(jìn)行copy:
myObject又被持有了。
結(jié)論:棧上的block不會(huì)持有對(duì)象類(lèi)型的自由變量拘悦,而堆上的block則會(huì)齿兔。
返回ARC再對(duì)改代碼進(jìn)行一些修改:
用__weak來(lái)修飾myObject,可以看到myObject在走到NSLog時(shí)會(huì)被回收础米,所以block內(nèi)部并沒(méi)有強(qiáng)引用myObject分苇。
查看源碼:
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
MyObject *__weak weakMyObject;
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *__weak _weakMyObject, int flags=0) : weakMyObject(_weakMyObject) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block內(nèi)部同樣用__weak來(lái)修飾。
為什么棧上的block不會(huì)持有對(duì)象型的自由變量呢屁桑?
回到前面的代碼:
void blockStudy() {
dispatch_block_t myBlock;
{
MyObject *myObject = [MyObject new];
myBlock = ^() {
NSLog(@"%@", NSStringFromClass(myObject.class));
};
}
NSLog(@"End");
}
轉(zhuǎn)為源碼:
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
MyObject *myObject;
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *_myObject, int flags=0) : myObject(_myObject) {
impl.isa = &_NSConcreteStackBlock; // 棧上面的Block
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
MyObject *myObject = __cself->myObject; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2__6smntvs6ng1ww18kf8713tc0000gn_T_BlockStudy_f6b706_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)myObject, sel_registerName("class"))));
}
static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
_Block_object_assign((void*)&dst->myObject, (void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
_Block_object_dispose((void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __blockStudy_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};
void blockStudy() {
dispatch_block_t myBlock;
{
MyObject *myObject = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("new"));
myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, myObject, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2__6smntvs6ng1ww18kf8713tc0000gn_T_BlockStudy_f6b706_mi_1);
}
創(chuàng)建block的時(shí)候把myObject傳了進(jìn)去
myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, myObject, 570425344));
保存在了__blockStudy_block_impl_0中:
struct __blockStudy_block_impl_0 {
struct __block_impl impl;
struct __blockStudy_block_desc_0* Desc;
MyObject *myObject;
__blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *_myObject, int flags=0) : myObject(_myObject) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這時(shí)候并沒(méi)有對(duì)myObject進(jìn)行持有医寿,也就是沒(méi)有增加它的引用計(jì)數(shù)。只有當(dāng)block被復(fù)制到堆上面時(shí)蘑斧,這個(gè)時(shí)候__blockStudy_block_copy_0
函數(shù)會(huì)被調(diào)用靖秩,才會(huì)通過(guò)_Block_object_assign
會(huì)根據(jù)自由變量的修飾符(__strong
,__weak
竖瘾,__unretained
)進(jìn)行相應(yīng)的操作沟突,形成強(qiáng)引用(retain)或者弱引用。__blockStudy_block_dispose_0
則在block被銷(xiāo)毀時(shí)進(jìn)行調(diào)用捕传。這就是捕獲對(duì)象型自由變量時(shí)__blockStudy_block_desc_0會(huì)多出兩個(gè)函數(shù)指針的原因了惠拭。
static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
_Block_object_assign((void*)&dst->myObject, (void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
_Block_object_dispose((void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __blockStudy_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};
事實(shí)上捕獲__block變量
時(shí)也會(huì)有這兩個(gè)函數(shù)指針,只是調(diào)用_Block_object_assign
和__blockStudy_block_dispose_0
函數(shù)時(shí)第三個(gè)參數(shù)有所不同庸论。它們?cè)赺_block變量被復(fù)制到堆上面時(shí)會(huì)被調(diào)用求橄。
static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
_Block_object_assign((void*)&dst->myNumber, (void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
_Block_object_dispose((void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}
而__block變量在ARC和MRC下是不一樣的。
- MRC 環(huán)境下葡公,block 截獲外部用 __block 修飾的變量罐农,不會(huì)增加對(duì)象的引用計(jì)數(shù);
- ARC 環(huán)境下催什,block 截獲外部用 __block 修飾的變量涵亏,會(huì)增加對(duì)象的引用計(jì)數(shù)。
主要原因是_Block_object_assign
方法,它根據(jù)第三個(gè)參數(shù)來(lái)決定是否需要增加對(duì)象的引用計(jì)數(shù)气筋。所以MRC下可以利用 __block
來(lái)打破循環(huán)引用拆内,在 ARC 環(huán)境下,則需要用__weak
來(lái)打破循環(huán)引用宠默。
小結(jié):ARC環(huán)境下麸恍,block會(huì)強(qiáng)引用捕獲到的局部變量,是因?yàn)閎lock被拷貝到堆上面時(shí)搀矫,會(huì)執(zhí)行內(nèi)部的copy函數(shù)抹沪,而這個(gè)copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign,該函數(shù)會(huì)根據(jù)局部變量的修飾符(
__strong
瓤球,__weak
融欧,__unretained
)來(lái)對(duì)該變量進(jìn)行強(qiáng)引用或弱引用。棧上的block在拷貝到堆上面之前是不會(huì)強(qiáng)引用局部變量的卦羡。
總結(jié):
__block
修飾的自由變量噪馏,不管是對(duì)象類(lèi)型的還是非對(duì)象類(lèi)型的,其實(shí)都是定義了一個(gè)結(jié)構(gòu)體绿饵,我們?cè)鞠胍x的變量是被封裝到這個(gè)結(jié)構(gòu)體里面的欠肾,最后使用的都是這個(gè)結(jié)構(gòu)體,通過(guò)__forwarding
引用值拟赊。這個(gè)結(jié)構(gòu)體可以被復(fù)制到堆上面董济,所以值可以被修改。當(dāng)被復(fù)制到堆上時(shí)要门,__forwarding變量指向的是__block變量自己虏肾,而棧上的__forwarding也是指向堆上的__block變量,這就保證了讀取和修改的同步欢搜。如果不用
__block
修飾封豪,block捕獲非對(duì)象類(lèi)型的自由變量時(shí)只是把它的值拷貝進(jìn)來(lái),自然就無(wú)法修改炒瘟,修改了也不會(huì)影響到外面吹埠。如果是對(duì)象類(lèi)型的,捕獲的也是對(duì)象的指針疮装,同樣是拷貝這個(gè)地址就行了缘琅。但是block可能會(huì)增加對(duì)象類(lèi)型的引用計(jì)數(shù),是因?yàn)閎lock被拷貝到堆上面時(shí)廓推,會(huì)執(zhí)行block的copy函數(shù)刷袍,也就是會(huì)執(zhí)行_Block_object_assign
這個(gè)方法,這個(gè)方法會(huì)根據(jù)自由變量的修飾符來(lái)決定對(duì)該變量進(jìn)行強(qiáng)引用或弱引用樊展,這里的引用當(dāng)然是堆上的變量呻纹,和棧上的指針無(wú)關(guān)堆生。
- Friday Q&A 2010-01-15: Stack and Heap Objects in Objective-C
- Objective-C 拾遺:從Heap and Stack到Block
- Block如何捕獲外部變量二:對(duì)象類(lèi)型
- 深入研究 Block 捕獲外部變量和 __block 實(shí)現(xiàn)原理
- 《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》