Block在內(nèi)存中的位置

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)存分配大概如下所示

image.png

從下到上,是低地址到高地址筷屡。
代碼區(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里面也能改變原變量雨膨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擂涛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子聊记,更是在濱河造成了極大的恐慌撒妈,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件排监,死亡現(xiàn)場離奇詭異狰右,居然都是意外死亡,警方通過查閱死者的電腦和手機舆床,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門棋蚌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挨队,你說我怎么就攤上這事谷暮。” “怎么了盛垦?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵湿弦,是天一觀的道長。 經(jīng)常有香客問我腾夯,道長颊埃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任蝶俱,我火速辦了婚禮班利,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榨呆。我一直安慰自己罗标,他們只是感情好,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布愕提。 她就那樣靜靜地躺著馒稍,像睡著了一般皿哨。 火紅的嫁衣襯著肌膚如雪浅侨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天证膨,我揣著相機與錄音如输,去河邊找鬼。 笑死,一個胖子當著我的面吹牛不见,可吹牛的內(nèi)容都是我干的澳化。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼稳吮,長吁一口氣:“原來是場噩夢啊……” “哼缎谷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灶似,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤列林,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酪惭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體希痴,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年春感,在試婚紗的時候發(fā)現(xiàn)自己被綠了砌创。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖攒至,靈堂內(nèi)的尸體忽然破棺而出啼器,到底是詐尸還是另有隱情,我是刑警寧澤舶赔,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站谦秧,受9級特大地震影響竟纳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疚鲤,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一锥累、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧集歇,春花似錦桶略、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姑蓝,卻和暖如春鹅心,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纺荧。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工旭愧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留颅筋,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓输枯,卻偏偏與公主長得像议泵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桃熄,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容