Block在內(nèi)存中的三種位置
首先,在 MRC 下扔水,Block 默認是分配在棧上的,除非進行顯式的 copy而线,但是在ARC的中铭污,一般都是分配在堆中。
@interface SecondViewController ()
typedef void (^blk)(void);
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
__block int val = 1;
//block保存在堆區(qū)
__strong blk heapBlock = ^{
val = 2;
};
NSLog(@"heapBlock: %@", heapBlock);
//block保存在棧區(qū)
__weak blk stackBlock = ^{
val = 2;
};
NSLog(@"stackBlock: %@", stackBlock);
//block保存在全局區(qū)
blk globalBlock = ^{
};
NSLog(@"globalBlock: %@", globalBlock);
}
Log輸出
2018-04-09 15:50:14.351940+0800 TestMRC[29483:4870456] heapBlock: <__NSMallocBlock__: 0x60000024c090>
2018-04-09 15:50:14.352184+0800 TestMRC[29483:4870456] stackBlock: <__NSStackBlock__: 0x7ffee57543d0>
2018-04-09 15:50:14.352318+0800 TestMRC[29483:4870456] globalBlock: <__NSGlobalBlock__: 0x10a4a9148>
簡單來說分兩種情況
A膀篮、沒有引用外部變量 --- block存放在全局區(qū)
B嘹狞、引用了外部變量----顯式聲明為weak類型的block則存放在棧區(qū),反之則是存在在堆區(qū)的誓竿,也就是說block是strong類型的磅网。
內(nèi)存分區(qū)
iOS App的內(nèi)存分配大概如下所示
從下到上,是低地址到高地址筷屡。
代碼區(qū)(Code)
: 存放App代碼涧偷;常量區(qū)(Const)
:存放App聲明的常量;全局區(qū)/靜態(tài)區(qū)(Global/Static)
:存放App的全局變量與靜態(tài)變量毙死;堆區(qū)(heap)
:一般是存放對象類型燎潮,需要手動管理內(nèi)存,ARC項目的話扼倘,編譯器接手內(nèi)存管理工作确封;堆區(qū)是使用鏈表來存儲的空閑內(nèi)存地址的,是不連續(xù)的再菊,而鏈表的遍歷方向是由低地址向高地址爪喘。棧區(qū)(stack)
:存放的自動變量(沒有定義static的局部變量)、一旦出了函數(shù)的作用域就會被銷毀纠拔,不需要手動管理秉剑,棧區(qū)地址從高到低分配,遵循先進后出的,如果申請的棧區(qū)內(nèi)存大小超過剩余的大小稠诲,就會反正棧區(qū)溢出overflow問題侦鹏,一般棧區(qū)內(nèi)存大小為2M。
使用block的注意事項
因為在ARC中臀叙,block還是有可能為NSStackBlock類型的略水,也就是說,隨著作用域結(jié)束匹耕,block將會銷毀回收聚请。
所以有時候需要對block進行copy操作,手動復制到堆區(qū)內(nèi)存中,防止被回收驶赏,導致block無法使用炸卑。
自動copy
以下情況,系統(tǒng)會將block自動復制到堆上煤傍,自動對block調(diào)用copy方法盖文。
1、當 block 作為函數(shù)返回值返回時蚯姆;
2五续、當 block 被賦值給__strong修飾的 id 類型的對象或 block 對象時;
3龄恋、當 block 作為參數(shù)被傳入方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 時疙驾。
因為在ARC下,對象默認是用__strong修飾的郭毕,所以大部分情況下編譯器都會將 block從棧自動復制到堆上它碎,所以iOS編程中,一般聲明block的property修飾符為copy显押,顯示地說明是在堆區(qū)的扳肛,ARC會自動完成從棧區(qū)copy到堆區(qū)的操作,當然使用 strong 去修飾 block 也是沒有問題的乘碑,還是一樣會自動copy挖息。
不自動copy
以下情況,系統(tǒng)不會自動復制到堆上兽肤,也就是說套腹,作用域結(jié)束,block就被回收銷毀轿衔。
block 作為方法或函數(shù)的參數(shù)傳遞沉迹。
block 作為臨時變量睦疫,沒有賦值給其他block
注意害驹,上述不一定是從棧區(qū)復制到堆區(qū),也有可能是global 區(qū)復制過去的蛤育。
__block的探究
捕獲變量
自動變量
Block捕獲外部自動變量是傳值的方式捕獲的宛官, Block只能訪問此變量的值,也非通過此變量的內(nèi)存地址訪問瓦糕。非自動變量
Block捕獲靜態(tài)變量時底洗,是通過傳遞該靜態(tài)變量的內(nèi)存地址
Block捕獲全局區(qū)變量是,因為作用域是全局咕娄,Block也可以直接修改亥揖。總結(jié)
Block本質(zhì)上是一個匿名函數(shù),對于函數(shù)來說,捕獲變量可以理解為傳遞參數(shù)费变。
至于為什么OC沒有默認就給自動變量設(shè)置為指針傳值摧扇,是因為棧區(qū)的隨著函數(shù)作用域結(jié)束而結(jié)束,而Block可能會強引用而被copy到堆區(qū)挚歧,這時候Block還引用自動變量就不合適扛稽。
修改值
由上面可知,如果想在Block內(nèi)部修改外部變量的值有兩個方法可以做到
- 傳值為內(nèi)存地址方式
- 改變變量在內(nèi)存中的位置
在OC中滑负,對棧區(qū)的自動變量加了前綴__block
后在张,Block就會創(chuàng)建一個帶isa指針的構(gòu)體來替換*原本的自動變量,里面存放著該變量的內(nèi)存地址等信息矮慕。
注意帮匾,這里是替換
如原代碼,定義block痴鳄,讓block修改自動變量 autoValue
辟狈。
typedef void (^MyBlock)(void);
int main(int argc, char * argv[]) {
__block int autoValue = 1;
MyBlock co_block = ^{
autoValue = 2;
};
autoValue = 3;
co_block();
autoValue = 999;
return 0;;
}
使用Clang轉(zhuǎn)化為cpp
clang -rewrite-objc main.m
可得到關(guān)鍵cpp代碼如下
typedef void (*MyBlock)(void);
struct __Block_byref_autoValue_0 {
void *__isa;
__Block_byref_autoValue_0 *__forwarding;
int __flags;
int __size;
int autoValue;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_autoValue_0 *autoValue; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_autoValue_0 *_autoValue, int flags=0) : autoValue(_autoValue->__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_autoValue_0 *autoValue = __cself->autoValue; // bound by ref
(autoValue->__forwarding->autoValue) = 2;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->autoValue, (void*)src->autoValue, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->autoValue, 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[]) {
__attribute__((__blocks__(byref))) __Block_byref_autoValue_0 autoValue = {(void*)0,(__Block_byref_autoValue_0 *)&autoValue, 0, sizeof(__Block_byref_autoValue_0), 1};
MyBlock co_block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_autoValue_0 *)&autoValue, 570425344));
(autoValue.__forwarding->autoValue) = 3;
((void (*)(__block_impl *))((__block_impl *)co_block)->FuncPtr)((__block_impl *)co_block);
(autoValue.__forwarding->autoValue) = 999;
return 0;;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
由上面cpp代碼可知
當自動變量被加上__block
后,原本的變量就替換為一個結(jié)構(gòu)體類型夏跷,這個結(jié)構(gòu)體中存在原本的自動變量int autoValue
和哼转,和一個__forwarding
指針,這個指針的類型是此結(jié)構(gòu)體槽华,而Block內(nèi)部訪問和后續(xù)Block外面訪問這個自動變量全部替換成如下
新變量->__forwarding->原變量同類型變量
如
(autoValue.__forwarding->autoValue) = 3;
(autoValue.__forwarding->autoValue) = 999;
注意
對自動變量加__block
其實和block沒有必要的聯(lián)系壹蔓。
就算沒有block捕獲自動變量,我們對一個自動變量加上__block
猫态,oc也會在編譯時把他替換為一個結(jié)構(gòu)體類型佣蓉。如
int main(int argc, char * argv[]) {
__block int autoValue = 1;
autoValue = 999;
return 0;;
}
轉(zhuǎn)化后如下
struct __Block_byref_autoValue_0 {
void *__isa;
__Block_byref_autoValue_0 *__forwarding;
int __flags;
int __size;
int autoValue;
};
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_autoValue_0 autoValue = {(void*)0,(__Block_byref_autoValue_0 *)&autoValue, 0, sizeof(__Block_byref_autoValue_0), 1};
(autoValue.__forwarding->autoValue) = 999;
return 0;;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
對象類型也同理。
總結(jié)
__block
的作用就是原地構(gòu)建一個新的結(jié)構(gòu)體來替換原本的自動變量亲雪,使得block捕獲這個自動變量時勇凭,可以使用指針訪問,這樣就可以修改這個自動變量的值了义辕。
需要注意的是
這個自動變量被替換為結(jié)構(gòu)體后虾标,其實還是一個自動變量,生命周期會隨著函數(shù)結(jié)束而結(jié)束灌砖,但是平常使用的Block一般都是存放在堆區(qū)的璧函,而且Block里面的變量也是存在堆區(qū),Bloc捕獲外面的自動變量時基显,會將變量從椪合牛空間copy 到堆空間。
當__block
變量是在椓糜模空間库继,其__forwarding
指針指向自身,當變量從棧空間copy到堆空間時宪萄,原來椌俗空間的變量的forwarding指向了新創(chuàng)建的變量(堆空間上),這樣block里面也能改變原變量雨膨。