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)引用讯屈。