iOS-block捕獲變量與循環(huán)引用問(wèn)題

block捕獲變量

一般,block代碼塊內(nèi) 使用的外部變量有3種類型捣鲸。

-

一般的局部變量默認(rèn)是auto修飾(自動(dòng)變量)離開(kāi)作用域就銷毀瑟匆。

1.局部變量auto(自動(dòng)變量)

int age = 20;  // 或 auto int age = 20;  auto可省略
void (^block)(void) =  ^{
    NSLog(@"age is %d",age);
};
age = 25;
       
block();  // 調(diào)用

運(yùn)行后,打印的是 20栽惶〕盍铮可見(jiàn),直接把a(bǔ)ge的值 20傳到了block中媒役,后面再修改age的值祝谚,并不能改變block里面的age值。

  • 結(jié)論:auto修飾的局部變量酣衷,是值傳遞交惯。

其實(shí)也很好理解,因?yàn)閍uto修飾的局部變量穿仪,離開(kāi)作用域就銷毀了席爽。

2.局部變量 static

static修飾的局部變量,不會(huì)被銷毀啊片。

static int age = 20;  
void (^block)(void) =  ^{
    NSLog(@"age is %d",age);
};
age = 25;
       
block();  // 調(diào)用

運(yùn)行后只锻,打印的是 25∽瞎龋可見(jiàn)齐饮,用static修飾后,同樣的操作笤昨,block里的age值會(huì)產(chǎn)生變化祖驱。

  • 結(jié)論:static修飾的局部變量,是指針傳遞瞒窒。

指針傳遞可能導(dǎo)致訪問(wèn)的時(shí)候捺僻,該變量已經(jīng)銷毀,程序會(huì)出問(wèn)題。

3.全局變量

int age1 = 11;
static int height1 = 22;

...

void (^block)(void) =  ^{
    NSLog(@"age1 is %d height1 = %d",age1,height1);
};
age1 = 31;
height1 = 33;
block();  // 調(diào)用

打迂芭鳌:age1 is 31 height1 = 33

這個(gè)情況束昵,并沒(méi)有捕獲全局變量。訪問(wèn)的時(shí)候葛峻,是直接去訪問(wèn)的锹雏,根本不需要捕獲。

全局變量本來(lái)就是在哪里都可以訪問(wèn)的术奖,所以無(wú)需捕獲逼侦。

關(guān)鍵字__block

上面討論的是,在block的外部 修改捕獲變量的值腰耙。那么,如果需要在block代碼塊內(nèi) 修改捕獲變量的值呢铲球?有3種方法修改局部變量挺庞。

1.把局部變量寫(xiě)成全局變量

即,上面提到的第3種情況稼病,這里不再累贅选侨。

全局變量是所有地方都可訪問(wèn)的,在block內(nèi)部也可以直接操作它的內(nèi)存地址然走。調(diào)用完block之后援制,全局變量指向的地址的值已經(jīng)被更改。

2.把局部變量用static修飾

即芍瑞,上面提到的第2種情況晨仑,這里不再累贅。

當(dāng)局部變量用static修飾之后拆檬,這個(gè)block內(nèi)部會(huì)把變量的地址捕獲了洪己。這樣的話,當(dāng)然在block內(nèi)部可以修改局部變量了竟贯。

以上兩種方法答捕,雖然可以達(dá)到在block內(nèi)部修改局部變量的目的,但缺點(diǎn)是變量無(wú)法及時(shí)銷毀屑那,會(huì)一直存在內(nèi)存中拱镐。而很多時(shí)候,我們只需要臨時(shí)用一下持际,當(dāng)不用的時(shí)候沃琅,能銷毀掉。那么就需要第3種方法__block选酗。

3.關(guān)鍵字__block

把局部變量用__block修飾阵难。

__block int age = 20;  
void (^block)(int a) =  ^{
    age = a;
    NSLog(@"age is %d",age);
}; 
block(33);  // 調(diào)用

打印 age is 33

注意:__block不能修飾全局變量、靜態(tài)變量static

——>下面來(lái)探索__block的底層:

因?yàn)椴东@了__block變量age芒填,這個(gè)時(shí)候block的底層結(jié)構(gòu)體為:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age;      // by ref
    // fp是函數(shù)地址  desc是描述信息  __Block_byref_age_0 類型的結(jié)構(gòu)體  *_age  flags標(biāo)記
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp; //fp是函數(shù)地址
    Desc = desc;
  }
};

相比一般的block呜叫,發(fā)現(xiàn)多了__Block_byref_age_0類型的結(jié)構(gòu)體空繁,其具體結(jié)構(gòu)是:

struct __Block_byref_age_0 {
    void *__isa; //isa指針
    __Block_byref_age_0 *__forwarding; // 指向自身的指針
    int __flags;
    int __size;
    int age; //使用值
};

這個(gè)結(jié)構(gòu)是因?yàn)?code>__block變量產(chǎn)生的。其中第二個(gè)__forwarding存放指向自身的指針朱庆,第五個(gè)age里面存放局部變量盛泡。

  • __block底層原理和邏輯:
    編譯器會(huì)將__block變量 包裝成一個(gè)對(duì)象。調(diào)用變量時(shí)娱颊,根據(jù)__Block_byref_age_0里的__forwarding傲诵,找到變量age所在的內(nèi)存,然后修改值箱硕。

block訪問(wèn)OC對(duì)象

上面討論的都是block訪問(wèn)變量拴竹。如果訪問(wèn)OC對(duì)象,又會(huì)如何剧罩?

NSObject *obj = [[NSObject alloc] init];
void (^block)(void) =  ^{
    NSLog(@"%@",obj);
}; 
block( );  // 調(diào)用

這時(shí)栓拜,block的結(jié)構(gòu)體如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // ——>
  NSObject *__strong obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

內(nèi)部 會(huì)根據(jù)代碼中的修飾符(__strong、__weak惠昔、__unsafe_unretained)而對(duì)其進(jìn)行強(qiáng)引用或弱引用幕与。

  • 堆空間的block 對(duì)其捕獲的__block變量會(huì)形成強(qiáng)引用。

因此镇防,雖然棧上的局部變量隨時(shí)會(huì)銷毀啦鸣。但對(duì)于__block修飾的局部變量,卻有了強(qiáng)引用来氧。

棧上的block诫给,并不會(huì)對(duì) 任何捕獲的變量產(chǎn)生強(qiáng)引用。

block循環(huán)引用問(wèn)題

typedef void (^YZBlock) (void);

@interface YZPerson : NSObject

@property (copy, nonatomic) YZBlock block;
@property (assign, nonatomic) int age;

@end
{
    YZPerson *person = [[YZPerson alloc] init];
    person.age = 10;
    person.block = ^{
         NSLog(@"person.age--- %d",person.age);
    }; 
}

程序結(jié)束啦扬,person都沒(méi)有釋放蝙搔,造成了內(nèi)存泄漏。

原因:block會(huì)自動(dòng)copy到堆上(block內(nèi)部的變量person也會(huì)被copy到堆上)并且block對(duì)person強(qiáng)引用考传。而本來(lái)吃型,block就是person的屬性,person對(duì)block強(qiáng)引用僚楞∏谕恚互相強(qiáng)引用,誰(shuí)都釋放不了泉褐。

解決循環(huán)引用

有3種方式來(lái)解決:

1.關(guān)鍵字__weak
{
    YZPerson *person = [[YZPerson alloc] init];
    person.age = 10;
    
    __weak YZPerson *weakPerson = person;
    person.block = ^{
         NSLog(@"person.age--- %d",  weakPerson.age);
    }; 
}

當(dāng)局部變量消失時(shí)候赐写,對(duì)于YZPseson來(lái)說(shuō),只有一個(gè)弱指針指向它膜赃,那它就銷毀挺邀,然后block也銷毀。

2.關(guān)鍵字__unsafe_unretained

跟上面的也類似,能夠解決循環(huán)引用端铛。但是不安全泣矛,指向的對(duì)象銷毀時(shí),指針存儲(chǔ)的地址值不變禾蚕。

3.關(guān)鍵字__block
__block YZPerson *person = [[YZPerson alloc] init];
person.block = ^{
    NSLog(@"person.age--- %d",person.age);
       
    person = nil;   //這一句 不能少
};

這個(gè)方法的本質(zhì)是您朽,在block內(nèi)部改變 局部變量的值(置nil

在ARC下,上面三種方式對(duì)比换淆,最好的是__weak哗总。
在MRC下,因?yàn)椴恢С秩踔羔?code>__weak倍试,只能用__unsafe_unretained__block來(lái)解決循環(huán)引用讯屈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市县习,隨后出現(xiàn)的幾起案子耻煤,更是在濱河造成了極大的恐慌,老刑警劉巖准颓,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異棺妓,居然都是意外死亡攘已,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門怜跑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)样勃,“玉大人,你說(shuō)我怎么就攤上這事性芬∠靠簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵植锉,是天一觀的道長(zhǎng)辫樱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)俊庇,這世上最難降的妖魔是什么狮暑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮辉饱,結(jié)果婚禮上搬男,老公的妹妹穿的比我還像新娘。我一直安慰自己彭沼,他們只是感情好缔逛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般褐奴。 火紅的嫁衣襯著肌膚如雪按脚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天歉糜,我揣著相機(jī)與錄音乘寒,去河邊找鬼。 笑死匪补,一個(gè)胖子當(dāng)著我的面吹牛伞辛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夯缺,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚤氏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了踊兜?” 一聲冷哼從身側(cè)響起竿滨,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捏境,沒(méi)想到半個(gè)月后于游,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垫言,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年贰剥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筷频。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚌成,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凛捏,到底是詐尸還是另有隱情担忧,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布坯癣,位于F島的核電站瓶盛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏示罗。R本人自食惡果不足惜蓬网,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹉勒。 院中可真熱鬧帆锋,春花似錦、人聲如沸禽额。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至实辑,卻和暖如春捺氢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剪撬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工摄乒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人残黑。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓馍佑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梨水。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拭荤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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