iOS開發(fā)之Block訪問外部變量和循環(huán)引用問題

說起B(yǎng)lock在iOS開發(fā)中作用非常多患膛,用處也非常廣。但要用好Block耻蛇,確保業(yè)務(wù)邏輯正常踪蹬,并且內(nèi)存管理不出問題胞此,也是不簡(jiǎn)單的。本篇不再闡述Block的概念和語法用法跃捣,就Block訪問外部變量和循環(huán)引用問題來介紹一下漱牵。

一、Block訪問外部變量

我們先看一個(gè)block訪問外部變量的例子:

        int a = 10;
        void(^myblock)(void) = ^(void) {
            NSLog(@"a=%d",a);
        };
        a = 20;
        myblock();

結(jié)果會(huì)發(fā)現(xiàn)打印的 a = 10

而且如果試圖在block里修改a的值的話疚漆,Xcode就會(huì)報(bào)錯(cuò)酣胀,提示在a的定義前添加__block。
按提示修改代碼后發(fā)現(xiàn):

        __block int a = 10;
        void(^myblock)(void) = ^(void) {
            NSLog(@"a=%d",a);
        };
        a = 20;
        myblock();

現(xiàn)在打印的就是 a = 20 了娶聘。
這是為什么呢闻镶?我們把mian.m文件編譯成C++代碼看一下到底是怎么實(shí)現(xiàn)的:

~/Desktop $ clang -rewrite-objc main.m

得到的main.cpp文件,其中main函數(shù)編譯成了

int main(int argc, const char * argv[]) {

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void(*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        (a.__forwarding->a) = 20;
        ((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);

    return 0;
}

我們的block被編譯成了一個(gè)結(jié)構(gòu)體__main_block_impl_0丸升,變量a作為了結(jié)構(gòu)體的一個(gè)成員:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

在main函數(shù)里我們可以看到铆农,a傳過來不只是a的值,而是(__Block_byref_a_0 *)&a狡耻,變量a的指針地址墩剖,所以我們就可以任意讀寫a的值了。

所以:
  • block在訪問外部變量時(shí)夷狰,會(huì)在block內(nèi)部創(chuàng)建一個(gè)新的變量來保存這個(gè)外部變量岭皂,修改的是內(nèi)部新變量的值,外部的變量不受影響孵淘;
  • 當(dāng)外部變量加了__block修飾蒲障,block保存了其指針引用,對(duì)指針變量的修改直接影響外部變量的值瘫证;
  • 另外,靜態(tài)變量庄撮、全局變量和全局靜態(tài)變量背捌,傳入的就是地址值,可以直接被block修改洞斯。

二毡庆、Block循環(huán)引用問題

首先什么是循環(huán)引用呢?就是兩個(gè)對(duì)象相互持有烙如,在釋放時(shí)么抗,相互等待釋放,造成死循環(huán)誰都釋放不了亚铁,從而內(nèi)存泄露蝇刀。

即block作為self的屬性時(shí),又在block內(nèi)部調(diào)用了self的屬性和方法徘溢,block和self相互持有吞琐,那么兩者的引用計(jì)數(shù)都至少是1捆探,都不會(huì)被釋放。

舉個(gè)栗子:

//
//  SecondViewController.m
//  UITest
//
//  Created by z on 2019/8/10.
//  Copyright ? 2019年 com.jzsec. All rights reserved.
//

#import "SecondViewController.h"

typedef void(^MyBlock)(int a);

@interface SecondViewController ()

@property (nonatomic,copy) MyBlock myblock;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
   SecondViewController *weakSelf = self;
    self.myblock = ^(int a) {
        [self doSomething];
    };
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)doSomething
{
    NSLog(@"%s",__func__);
}

-(void)dealloc
{
    NSLog(@"%s",__func__);
}

@end

這個(gè)地方還有第一個(gè)界面我就沒寫站粟,很簡(jiǎn)單就是點(diǎn)擊屏幕彈出這個(gè)SecondViewController黍图,這個(gè)second點(diǎn)擊界面就dismiss掉。但是運(yùn)行后我們發(fā)現(xiàn):

  • second的dealloc方法并沒有調(diào)用奴烙,說明第二個(gè)界面消失后并沒有釋放助被;
  • 代碼中[self doSomething];編譯器給出警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle,可以看出block里的變量默認(rèn)是strong持有的切诀,self又持有myBlock揩环,所以提示循環(huán)引用了;

那么怎么避免造成循環(huán)引用呢趾牧?這里介紹三種方法:

  • ARC下用__weak或__block;
  • MRC下用__block;
  • self作為block的參數(shù)傳進(jìn)去检盼;
1、__weak

使用__weak定義一個(gè)弱引用的self臨時(shí)變量翘单,去掉強(qiáng)引用吨枉,這也是最常用的一種方法:

__weak typeof(self) weakSelf = self;
    self.myblock = ^(int a) {
        [weakSelf doSomething];
    };

這樣既去掉了編譯器警告,而且還打印出了-[SecondViewController dealloc]哄芜,說明second已經(jīng)釋放掉貌亭,解決了循環(huán)引用。

2认臊、__block

同樣是聲明一個(gè)臨時(shí)self的變量圃庭,但是要記得在block里使用完這個(gè)臨時(shí)變量時(shí),把它置空失晴,而且還要調(diào)用這個(gè)block:

__block SecondViewController *weakSelf = self;
    self.myblock = ^(int a) {
        [weakSelf doSomething];
        weakSelf = nil;
    };
    self.myblock(1);

此時(shí)打印出:

2019-08-10 19:02:35.638863+0800 UITest[11066:395433] -[SecondViewController doSomething]
2019-08-10 19:02:36.792805+0800 UITest[11066:395433] -[SecondViewController dealloc]

說明second已經(jīng)釋放掉剧腻,解決了循環(huán)引用。

3涂屁、self作為block的參數(shù)

這個(gè)地方我們需要做一些改造书在,block重新定義一下:

typedef void(^MySecondBlock)(SecondViewController *vc);
... ...
@property (nonatomic,copy) MySecondBlock mySecondblock;
... ...

self.mySecondblock = ^(SecondViewController *vc) {
        [vc doSomething];
    };
    self.mySecondblock(self);

此時(shí)運(yùn)行后,依然能打印出:

2019-08-10 19:05:12.690490+0800 UITest[11160:399180] -[SecondViewController doSomething]
2019-08-10 19:05:14.369980+0800 UITest[11160:399180] -[SecondViewController dealloc]

說明second已經(jīng)釋放掉拆又,解決了循環(huán)引用儒旬。

三、注意

1帖族、block屬性

block作為屬性時(shí)栈源,修飾符一定要用copy!在ARC下使用strong和copy效果一樣竖般,如果使用weak甚垦,那么這個(gè)block屬性隨時(shí)都有可能被釋放掉,而無法調(diào)用。

2制轰、不會(huì)造成循環(huán)引用的情況

那么只要是用到block前计,而不用上面三種方法就一定會(huì)造成循環(huán)引用嗎?不是的垃杖,下面情況不會(huì)造成循環(huán)引用:

  • 大部分GCD的block男杈;
  • Block作為臨時(shí)變量;
3调俘、避免self在block里提前釋放

比如說self在block里延遲使用了伶棒,但是block走完之后weakSelf就不存在了,所以doSomething也就沒有響應(yīng):

__weak typeof(self) weakSelf = self;
    self.myblock = ^(int a) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakSelf doSomething];
        });
    };

Block執(zhí)行過程中self對(duì)象被釋放彩库,用__strong修飾self肤无,記得得調(diào)用了Block才起作用:

__weak typeof(self) weakSelf = self;
    self.myblock = ^(int a) {
        __strong typeof(self) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [strongSelf doSomething];
        });
    };
    self.myblock(1);

此時(shí)依然打印出:

2019-08-10 19:18:34.574815+0800 UITest[11490:411849] -[SecondViewController doSomething]
2019-08-10 19:18:34.575089+0800 UITest[11490:411849] -[SecondViewController dealloc]

__strong typeof(self) strongSelf = weakSelf;
這句延長(zhǎng)了weakSelf的生命周期,走完dispatch_after的代碼才會(huì)釋放掉骇钦,即延遲釋放宛渐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市眯搭,隨后出現(xiàn)的幾起案子窥翩,更是在濱河造成了極大的恐慌,老刑警劉巖鳞仙,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寇蚊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棍好,警方通過查閱死者的電腦和手機(jī)仗岸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來借笙,“玉大人扒怖,你說我怎么就攤上這事∫导冢” “怎么了姚垃?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)盼忌。 經(jīng)常有香客問我,道長(zhǎng)掂墓,這世上最難降的妖魔是什么谦纱? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮君编,結(jié)果婚禮上跨嘉,老公的妹妹穿的比我還像新娘。我一直安慰自己吃嘿,他們只是感情好祠乃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布梦重。 她就那樣靜靜地躺著,像睡著了一般亮瓷。 火紅的嫁衣襯著肌膚如雪琴拧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天嘱支,我揣著相機(jī)與錄音蚓胸,去河邊找鬼。 笑死除师,一個(gè)胖子當(dāng)著我的面吹牛沛膳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汛聚,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锹安,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了倚舀?” 一聲冷哼從身側(cè)響起叹哭,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞄桨,沒想到半個(gè)月后话速,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芯侥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年泊交,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柱查。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廓俭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唉工,到底是詐尸還是另有隱情研乒,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布淋硝,位于F島的核電站雹熬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谣膳。R本人自食惡果不足惜竿报,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望继谚。 院中可真熱鬧烈菌,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至济瓢,卻和暖如春荠割,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背葬荷。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工涨共, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宠漩。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓举反,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親扒吁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子火鼻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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