iOS Block

Block的分類

Block有三種類型:全局Block应结,堆區(qū)Block泉唁,棧區(qū)Block

全局Block

當(dāng)Block沒有引用到局部變量時(shí)或者Block里面使用的是全局變量,靜態(tài)變量時(shí)為全局Block

    int a = 10;
    void (^block)(void) = ^{
         NSLog(@"hello world");
    };

    NSLog(@"block:%@", block);

輸出結(jié)果:block:<__NSGlobalBlock__: 0x104e580f8>
    static int a1 = 20;
    void (^block)(void) = ^{
        NSLog(@"hello - %d",a1);
    };

    NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSGlobalBlock__: 0x100cec0f8>

堆區(qū)Block

當(dāng)Block里有引用到局部變量時(shí)為堆區(qū)block

    int a = 10;
    void (^block)(void) = ^{
        NSLog(@"hello - %d",a);
    };

    NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSMallocBlock__: 0x281065950>
    __block int a = 10;
    void (^block)(void) = ^{
        NSLog(@"hello - %d",a);
    };

    NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSMallocBlock__: 0x281aad5c0>

棧區(qū)Block

在Block名前面加個(gè)__weak就是棧區(qū)block

    __block int a = 10;
    static int a1 = 20;
    void (^__weak block)(void) = ^{
        NSLog(@"hello - %d",a);
        NSLog(@"hello - %d",a1);
    };

    NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSStackBlock__: 0x16f7ccfb0>

堆Block和棧Block的區(qū)別

接下來看看這兩種block有什么區(qū)別呢扮休?
先看個(gè)示例:

    __block int a = 10;
    __block int b = 20;
    NSLog(@"a:%p---b:%p", &a, &b);
    void (^__weak block)(void) = ^{
        NSLog(@"hello - %d---%p",a, &a);
        a++;
    };
    void (^block1)(void) = ^{
        NSLog(@"hello - %d---%p",b, &b);
        b++;
    };
    block();
    block1();
    NSLog(@"block:%@---block1:%@", block, block1);
    NSLog(@"a:%d---b:%d", a, b);
    NSLog(@"a:%p---b:%p", &a, &b);
輸出結(jié)果:
a:0x16bb7cfe8---b:0x16bb7cfc8
hello - 10---0x16bb7cfe8
hello - 20---0x283e0b1b8
block:<__NSStackBlock__: 0x16bb7cf70>---block1:<__NSMallocBlock__: 0x28307d9b0>
a:11---b:21
a:0x16bb7cfe8---b:0x283e0b1b8

通過結(jié)果我們看到,首先block的地址是在棧區(qū)拴鸵,而block1的地址是在堆區(qū)宝踪,而棧block引用的變量a的地址并沒有變化,而堆block1引用的變量b的地址也相應(yīng)變成了堆區(qū)0x283e0b1b8瘩燥,并且后面使用的b的地址都是堆區(qū)上的。
總結(jié):棧block存放在棧區(qū)溶耘,對(duì)局部變量引用只拷貝局部變量的地址服鹅,而堆block存放在堆區(qū),并且直接將局部變量拷貝了一份到堆空間庐扫。
接下來我們?cè)賮砜磦€(gè)示例:

NSObject *objc = [NSObject new];
    NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));// 1
    // block 底層源碼
    // 捕獲 + 1
    // 堆區(qū)block
    // 棧 - 內(nèi)存 -> 堆  + 1
    void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕獲 + 1 = 2
        NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    strongBlock();

    void(^__weak weakBlock)(void) = ^{ // + 1
        NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
    };
    weakBlock();
    
    void(^mallocBlock)(void) = [weakBlock copy];
    mallocBlock();
輸出結(jié)果:
<NSObject: 0x28292ff20>---1
<NSObject: 0x28292ff20>---3
<NSObject: 0x28292ff20>---4
<NSObject: 0x28292ff20>---5

奇怪為什么堆區(qū)block里面的對(duì)象引用計(jì)數(shù)加2呢形庭?而后面的mallocBlock只加1呢?
首先objcstrongBlock里面必然會(huì)拷貝一份到堆區(qū)萨醒,所以會(huì)加1,但是他是從當(dāng)前函數(shù)的棧區(qū)拷貝嗎囤踩?并不是晓褪,對(duì)于堆區(qū)Block一開始編譯時(shí)是棧block這時(shí)候objc對(duì)象地址拷貝了一份引用計(jì)數(shù)加1,后面從棧block變成堆block怔锌,又拷貝了一份引用計(jì)數(shù)又加1变过,所以這時(shí)候是3
weakBlock棧block僅拷貝了一份,所以引用計(jì)數(shù)加1岛杀,這時(shí)候是4
mallocBlockweakblock拷貝了一份崭孤,所以引用計(jì)數(shù)再加1,這時(shí)候是5
相當(dāng)于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];

再來看個(gè)示例:

    NSObject *a = [NSObject alloc];
    NSLog(@"1---%@--%p", a, &a);
    void(^__weak weakBlock)(void) = nil;
    {
        // 棧區(qū)
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"2---%@--%p", a, &a);
        };
        weakBlock = strongBlock;
        strongBlock();
        NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
    NSLog(@"4---%@--%p", a, &a);
輸出結(jié)果:
1---<NSObject: 0x2820337a0>--0x16bcf4fd8
2---<NSObject: 0x2820337a0>--0x16bcf4fc0
3 - <__NSStackBlock__: 0x16bcf4fa0> - <__NSStackBlock__: 0x16bcf4fa0>
2---(null)--0x16bcf4fc0
4---<NSObject: 0x2820337a0>--0x16bcf4fd8

當(dāng)前是棧區(qū)strongBlock的賦值給外面的棧區(qū)weakBlock遗锣,因?yàn)槎际谴娣旁跅嗤形?臻g的,只有當(dāng)前函數(shù)結(jié)束才會(huì)被銷毀笔咽,隨意這邊weakBlock調(diào)用并不會(huì)有什么問題霹期。如果換成堆區(qū)block就不一樣了
注意:這邊的a對(duì)象weakBlock()調(diào)用時(shí)是nil,通過上面打印可以看出a對(duì)象在進(jìn)入到strongblock里甩十,&a拷貝了一份,拷貝的這一份地址指向的跟外面一樣溢十,但是當(dāng)strongblock出了{(lán)},盡管strongblock對(duì)象不再了达吞,但是其指向的內(nèi)存空間還在荒典,銷毀之前給了外面的weakBlock,同理a也一樣覆糟,對(duì)象(此時(shí)a指向的內(nèi)容)不在了遮咖,但是內(nèi)存空間卻還在。

NSObject *a = [NSObject alloc];
//    NSLog(@"1---%@--%p", a, &a);
    void(^__weak weakBlock)(void) = nil;
    {
        // 堆區(qū)
        void(^strongBlock)(void) = ^{
            NSLog(@"2---%@--%p", a, &a);
        };
        weakBlock = strongBlock;
        strongBlock();
        NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
    }
    weakBlock();
//    NSLog(@"4---%@--%p", a, &a);
輸出結(jié)果:
2---<NSObject: 0x281218810>--0x281e7f6e0
3 - <__NSMallocBlock__: 0x281e7f6c0> - <__NSMallocBlock__: 0x281e7f6c0>
調(diào)用weakBlock時(shí)崩潰

為什么呢麦箍?因?yàn)樵趝}里面的堆區(qū)strongBlock出了大括號(hào)就會(huì)被銷毀陶珠,此時(shí)你去調(diào)用這個(gè)block就會(huì)崩潰
注意:這邊weakBlock為什么也是__NSMallocBlock__,其實(shí)weakBlock相當(dāng)于是指針诀蓉,此時(shí)指向的是一個(gè)堆上的內(nèi)存所以是__NSMallocBlock__

Block的循環(huán)引用

內(nèi)存泄漏一個(gè)主要原因就是block的循環(huán)引用暑脆。那么如何解決循環(huán)引用呢?

- (void)viewDidLoad {
    [super viewDidLoad];
    // 循環(huán)引用
    self.name = @"hongfa";
    self.block = ^{
        NSLog(@"%@", self.name);
    }
    
    self.block();
}

這邊vc 引用了block 沥曹,block引用了vc根资,最后面造成了循環(huán)引用,那么如何解決呢部脚?
方案一:通過weak來解決

- (void)viewDidLoad {
    [super viewDidLoad];
    // 循環(huán)引用
    self.name = @"hongfa";
    __weak typeof(self) weakself = self;
    self.block = ^{
        NSLog(@"%@", weakself.name);
    };
    
    self.block();
}
直接使用weakself來代替self裤纹,weakself是弱引用丧没,這樣不會(huì)導(dǎo)致引用計(jì)數(shù)+1呕童。
這邊也有一個(gè)問題如下:
    self.name = @"hongfa";
    __weak typeof(self) weakself = self;
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakself.name);
        });
    };
    
    self.block();

如果當(dāng)我們延遲使用weakself的話淆珊,這時(shí)候的weakself可能已經(jīng)被銷毀了,這時(shí)候就需要用到__strong typeof(weakself) strongself = weakself;

    self.name = @"hongfa";
    __weak typeof(self) weakself = self;
    self.block = ^{
        __strong typeof(weakself) strongself = weakself;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongself.name);
        });
    };
    
    self.block();

__strong可以讓對(duì)象暫時(shí)在存活一段時(shí)間往声,用完就會(huì)銷毀戳吝,這樣也不會(huì)帶來內(nèi)存泄漏。
以上就是通過__weak__strong來解決block的循環(huán)引用慢洋。
方案二:通過臨時(shí)變量來解決

    __block ViewController *vc = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.block();

定義一個(gè)跟self一樣類型的vc = self陆盘,然后block引用vc,用完之后再把vc=nil斑芜,這樣引用鏈:self -> block -> vc -> self這樣也可以解決循環(huán)引用問題

方案三:通過參數(shù)將self傳進(jìn)去祟霍,傳參的話,參數(shù)是在棧區(qū)醇王,函數(shù)運(yùn)行好棧區(qū)銷毀參數(shù)也就跟著銷毀崭添,所以也可以解決循環(huán)引用問題

   // 通訊 參數(shù) block 通知
    self.hfblock = ^(ViewController *vc){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    self.hfblock(self);

以上就是目前掌握的三種解決循環(huán)引用的方案。

block的本質(zhì)

接下來看看block通過xcrun后究竟是什么棘伴?

int main(int argc, char * argv[]) {
    int a = 9;
    __block int b = 10;
    NSObject *objc = [NSObject alloc];
    void(^Block)(void) = ^{
        NSLog(@"a:%d", a);
        NSLog(@"b:%d", b);
        NSLog(@"objc:%@", objc);
    };
    return 0;
}

xcrun -sdk iphonesimulator clang -rewrite-objc main.m
int main(int argc, char * argv[]) {
    int a = 9;
    __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};

    NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));

    void(*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, objc, (__Block_byref_b_0 *)&b, 570425344));
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  NSObject *objc;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSObject *_objc, __Block_byref_b_0 *_b, int flags=0) : a(_a), objc(_objc), b(_b->__forwarding) { // 構(gòu)造函數(shù)
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

xcrun后看到的__block b焊夸,底層是變成了結(jié)構(gòu)體b蓝角,里面保存了b的值10饭冬,整個(gè)block也變成了__main_block_impl_0結(jié)構(gòu)體對(duì)象揪阶,把a, b, objc作為參數(shù)傳進(jìn)去。而在block結(jié)構(gòu)體里定義了三個(gè)成員變量來保存a,b,objc
通過上面的例子我們就很清楚block的底層結(jié)構(gòu)以及他是如何對(duì)引用局部變量

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炊苫,一起剝皮案震驚了整個(gè)濱河市劝评,隨后出現(xiàn)的幾起案子倦淀,更是在濱河造成了極大的恐慌声畏,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愿棋,死亡現(xiàn)場(chǎng)離奇詭異均牢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)甘邀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門垮庐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逗抑,你說我怎么就攤上這事寒亥。” “怎么了褂傀?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵腐宋,是天一觀的道長(zhǎng)檀轨。 經(jīng)常有香客問我参萄,道長(zhǎng)煎饼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任筒溃,我火速辦了婚禮沾乘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翅阵。我一直安慰自己掷匠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布钙皮。 她就那樣靜靜地躺著顽决,像睡著了一般。 火紅的嫁衣襯著肌膚如雪擎值。 梳的紋絲不亂的頭發(fā)上鸠儿,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音汹粤,去河邊找鬼田晚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贤徒,可吹牛的內(nèi)容都是我干的汇四。 我是一名探鬼主播踢涌,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼睁壁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了行剂?” 一聲冷哼從身側(cè)響起钳降,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎固阁,沒想到半個(gè)月后城菊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碉克,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漏麦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了更耻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捏膨。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖目胡,靈堂內(nèi)的尸體忽然破棺而出链快,到底是詐尸還是另有隱情,我是刑警寧澤域蜗,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站袱蜡,受9級(jí)特大地震影響疼阔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜婆廊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一淘邻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宾舅,春花似錦、人聲如沸扶平。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)麻献。三九已至猜扮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旅赢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工源譬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踩娘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓雷绢,卻偏偏與公主長(zhǎng)得像理卑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子藐唠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

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