在項目中,我們經(jīng)常會用到block,但是說起block你真的會用嗎?你真的全都了解嗎?如果心里犯嘀咕的話,那么就往下看吧~~~>_<
說block之前,我們先了解一下內(nèi)存的存儲知識,即內(nèi)存分區(qū)的問題.一個由C/C++編譯的程序占用的內(nèi)存分為以下幾個部分:
1.棧(stack) 由編譯器自動分配釋放,存放基本數(shù)據(jù)類型、局部變量和函數(shù)參數(shù),其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧苦掘。
2.堆(heap) 由程序員來分配和管理內(nèi)存,存放類類型(對象等).注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事换帜,分配方式類似于鏈表。
3.全局區(qū)(靜態(tài)區(qū) static) 存儲全局變量和靜態(tài)變量.初始化的全局變量和靜態(tài)變量在一塊區(qū)域鹤啡,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域(BSS)惯驼。 - 程序結(jié)束后由系統(tǒng)釋放
4.文字常量區(qū) 常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放
5.方法區(qū)(程序代碼區(qū)) 存放函數(shù)體的二進制代碼.
創(chuàng)建局部block時,系統(tǒng)默認(rèn)把其創(chuàng)建在棧,出了當(dāng)前block方法體就會被自動釋放.Block的copy递瑰、retain祟牲、release操作 不同于NSObject的copy、retain抖部、release操作,只要實現(xiàn)一個對周圍變量沒有引用的Block说贝,就會顯示為是NSGlobalBlock.如果其中加入了對局部變量的引用,就是NSStackBlock如果你對一個NSStackBlock對象使用了Block_copy()或者發(fā)送了copy消息慎颗,就會得到NSMallocBlock.
當(dāng)block類型是NSGlobalBlock使用copy retain release 都是無效的 如果沒有使用外部變量默認(rèn)是NSGlobalBlock
當(dāng)block引用外部變量時 會轉(zhuǎn)化類型成NSStackBlock 對copy操作有效果:會把block類型改變成NSMallocBlock copy后的block放在堆上面 對copy 和retain release 操作有效
注意NSMallocBlock類型的Block 雖然使用copy retain release 有效 但是系統(tǒng)顯示的引用計數(shù)一直1 盡量不要對block使用retain
Block_copy(<#...#>)和copy效果一樣
Block_release(<#...#>)和release效果一樣
總結(jié)如下:
1)NSGlobalBlock:retain乡恕、copy、release操作都無效俯萎;
2)NSStackBlock:retain几颜、release操作無效,必須注意的是讯屈,NSStackBlock在函數(shù)返回后蛋哭,Block內(nèi)存將被回收。即使retain也沒用涮母。容易犯的錯誤是[mutableAarry addObject:stackBlock]谆趾,(補:在ARC中不用擔(dān)心此問題,因為ARC中會默認(rèn)將實例化的Block拷貝到堆上)在函數(shù)出棧后叛本,從mutableAarry中取到的stackBlock已經(jīng)被回收沪蓬,變成了野指針。正確的做法是先將[stackBlock copy]到堆上来候,然后加入數(shù)組:[mutableAarry addObject:[[stackBlock copy] autorelease]]跷叉。支持copy,copy之后生成新的NSMallocBlock類型對象营搅。
3)NSMallocBlock支持retain云挟、release,雖然retainCount始終是1转质,但內(nèi)存管理器中仍然會增加园欣、減少計數(shù)。copy之后不會生成新的對象休蟹,只是增加了一次引用沸枯,類似retain日矫;
4)Block_copy與copy等效,Block_release與release等效绑榴;
5)對Block不管是retain哪轿、copy、release都不會改變引用計數(shù)retainCount翔怎,retainCount始終是1窃诉;
6)盡量不要對Block使用retain操作,不方便管理。
接下來看例子
1姓惑、局部變量褐奴,在Block中只讀。
Block定義時copy變量的值于毙,在Block中作為常量使用敦冬,所以即使變量的值在Block外改變,也不影響它在Block中的值
int a =3;
void(^myblock)()=^{
NSLog(@">>>%d",a);
};
a=5;
myblock();//輸出3
2唯沮、在block內(nèi)修改外部聲明的局部變量,那么一定要對該變量加__block標(biāo)記.
block內(nèi)部使用外部局部變量的時候 會自動把變量copy一份 內(nèi)部會使用copy后的變量 如果想使用原變量 (不使外部變量copy) 需要在局部變量前面加上__block
__block int b =2;
void(^myblock1)()=^{
b +=1;
NSLog(@"----->>>>%d",b);
};
b =10000;
myblock1();//輸出10001
3脖旱、Static修飾的或全局變量,當(dāng)在外界發(fā)生改變時,block內(nèi)部也會隨之發(fā)生改變
因為全局變量或靜態(tài)變量在內(nèi)存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內(nèi)存讀出介蛉,獲取到的是最新值萌庆,而不是在定義時copy的常量.在Block內(nèi)部修改外部全局變量時 不用加__block;在Block內(nèi)部 修改 static 描述的變量 不用加__block。
Block變量币旧,被__Block修飾的變量稱作Block變量践险。 基本類型的Block變量等效于全局變量或靜態(tài)變量 但對象的block變量不會
//當(dāng)全局變量在block內(nèi)部使用,block內(nèi)部不會Copy全局變量,而是使用原值.因為全局變量前面隱藏__block
self.d=7777;
void(^myblock2)() = ^{
NSLog(@"%d",self.d);
};
self.d=8888;
myblock2();//8888
//static 描述的變量 block內(nèi)部不會copy 而是使用原值
static int w =30000;
void(^myblock3)() = ^{
NSLog(@"%d",w);
};
w=50000;
myblock3 ();//50000
4、為什么在block里面使用self會導(dǎo)致強引用而無法釋放內(nèi)存呢?這里我們用一個例子來說明一下:
block強引用的原因:
[self.teacher requestData:^(NSData *data) {
self.name = @"case";
}];
在這里吹菱,self強引用了teacher, teacher又強引用了一個block巍虫,而該block在回調(diào)時又調(diào)用了self,會導(dǎo)致該block又強引用了self鳍刷,造成了一個保留環(huán)占遥,最終導(dǎo)致self無法釋放。
解決方案:
__weak typeof(self) weakSelf = self;
[self.teacher requestData:^(NSData *data) {
typeof(weakSelf) strongSelf = weakSelf;
strongSelf.name = @"case";
}];
通過__weak的修飾输瓜,先把self弱引用(默認(rèn)是強引用瓦胎,實際上self是有個隱藏的__strong修飾的),然后在block回調(diào)里用weakSelf尤揣,這樣就會打破保留環(huán)搔啊,從而避免了循環(huán)引用,如下:self -> teacher -> block -> weakSelf
PS:一般會在block回調(diào)里再強引用一下weakSelf(typeof(weakSelf) strongSelf = weakSelf;)芹缔,因為__weak修飾的都是存在棧內(nèi)坯癣,可能隨時會被系統(tǒng)釋放,造成后面調(diào)用weakSelf時weakSelf可能已經(jīng)是nil了最欠,后面用weakSelf調(diào)用任何代碼都是無效的示罗。
這里要說一下,除了用__weak這種方式芝硬,還有其他兩種方式可以打破block的循環(huán)引用蚜点,分別是重新聲明一個用__block修飾的控制器的臨時變量,然后在block里面使用的當(dāng)前控制器用這個臨時變量替代拌阴,假如說當(dāng)前控制器的名稱是PushViewController绍绘,那么
__block PushViewController *vc = self;
然后在block用vc代替self,并在不再使用self的時候迟赃,將vc指向nil(將堆區(qū)的vc置空)陪拘。
其原理是__block 本質(zhì)是拷貝當(dāng)前被block捕捉到的對象到block結(jié)構(gòu)體,另一個內(nèi)存區(qū)域,生產(chǎn)新的空間變量,當(dāng)在block中使用了被聲明的局部變量以后,也就改變了局部變量的內(nèi)容地址(由棧區(qū)拷貝到了堆區(qū),棧區(qū)的block也就變成了堆區(qū)的block)
不用__block 是值傳遞纤壁,用__block 是指針傳遞
還有一種是將當(dāng)前控制器對象當(dāng)做參數(shù)傳入到block中進行使用(也就是把當(dāng)前的控制器當(dāng)做臨時變量當(dāng)做參數(shù)傳入block)
//block實現(xiàn)
self.block = ^(PushViewController *vc){}
//block的調(diào)用
self.block(self);