參考:
http://www.devtang.com/blog/2013/07/28/a-look-inside-blocks/
http://blog.csdn.net/wildfireli/article/details/21979955#t2
http://www.tuicool.com/articles/i6Bfee
http://blog.csdn.net/fengsh998/article/details/38090205
http://www.tekuba.net/program/349/
Block學習
可以先看看 cocoachina提供的block測試題你真的知道blocks在Objective-C中是怎麼工作的嗎
Block在內存中的位置
根據Block在內存中的位置分爲三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。
- NSGlobalBlock:類似函數,位於text段;
- NSStackBlock:位於棧內存,函數返回後Block將無效;
- NSMallocBlock:位於堆內存。
BlkSum blk1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"blk1 = %@", blk1);// blk1 = <__NSGlobalBlock__: 0x47d0>
int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // blk2 = <__NSStackBlock__: 0xbfffddf8>
BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // blk3 = <__NSMallocBlock__: 0x902fda0>
爲什麼blk1類型是NSGlobalBlock,而blk2類型是NSStackBlock?blk1和blk2的區別在於,blk1沒有使用Block以外的任何外部變量,Block不需要建立局部變量值的快照,這使blk1與函數沒有任何區別,從blk1所在內存地址0x47d0猜測編譯器把blk1放到了text代碼段。blk2與blk1唯一不同是的使用了局部變量base,在定義(注意是定義,不是運行)blk2時,局部變量base當前值被copy到棧上,作爲常量供Block使用。執行下面代碼,結果是203,而不是204。
int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld",sum(1,2));
在Block內變量base是隻讀的,如果想在Block內改變base的值,在定義base時要用 __block修飾:__block int base = 100;。__block int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
base += 10;
return base + a + b;
};
base++;
printf("%ld\n",sum(1,2));
printf("%d\n",base);
輸出將是214,211。Block中使用__block修飾的變量時,將取變量此刻運行時的值,而不是定義時的快照。這個例子中,執行sum(1,2)時,base將取base++之後的值,也就是201,再執行Blockbase+=10;
base+a+b,運行結果是214。執行完Block時,base已經變成211了。Block的copy retain release
不同於NSObjec的copy、retain、release操作:
● Block_copy與copy等效,Block_release與release等效;
● 對Block不管是retain、copy、release都不會改變引用計數retainCount,retainCount始終是1;
● NSGlobalBlock:retain、copy、release操作都無效;NSStackBlock:retain、release操作無效,必須注意的是,NSStackBlock在函數返回後,Block內存將被回收。即使retain也沒用。容易犯的錯誤是[[mutableAarry addObject:stackBlock],在函數出棧後,從mutableAarry中取到的stackBlock已經被回收,變成了野指針。正確的做法是先將stackBlock copy到堆上,然後加入數組:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之後生成新的NSMallocBlock類型對象。
● NSMallocBlock支持retain、release,雖然retainCount始終是1,但內存管理器中仍然會增加、減少計數。copy之後不會生成新的對象,只是增加了一次引用,類似retain;
● 儘量不要對Block使用retain操作。
Block對不同類型的變量的存取
基本類型
局部自動變量,
在Block中只讀。Block定義時copy變量的值,在Block中作爲常量使用,所以即使變量的值在Block外改變,也不影響他在Block中的值。
<span style="font-family:SimSun;">int base = 100;
BlkSum sum = ^ long (int a, int b) {
// base++; 編譯錯誤,只讀
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2)); // 這裏輸出是103,而不是3</span>
static變量、全局變量
如果把上個例子的base改成全局的、或static。Block就可以對他進行讀寫了。因爲全局變量或靜態變量在內存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內存讀出,獲取到的是最新值,而不是在定義時copy的常量。
static int base = 100;
BlkSum sum = ^ long (int a, int b) {
base++;
return base + a + b;
};
base = 0;
printf("%d\n", base);
printf("%ld\n",sum(1,2)); // 這裏輸出是3,而不是103
printf("%d\n", base);
輸出結果是0
4 1,表明Block外部對base的更新會影響Block中的base的取值,同樣Block對base的更新也會影響Block外部的base值。
Block變量,被__block修飾的變量稱作Block變量。 基本類型的Block變量等效於全局變量、或靜態變量。
Block被另一個Block使用時,另一個Block被copy到堆上時,被使用的Block也會被copy。但作爲參數的Block是不會發生copy的。
void foo() {
int base = 100;
BlkSum blk = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"%@", blk); // <__NSStackBlock__: 0xbfffdb40>
bar(blk);
}
void bar(BlkSum sum_blk) {
NSLog(@"%@",sum_blk); // 與上面一樣,說明作爲參數傳遞時,並不會發生copy
void (^blk) (BlkSum) = ^ (BlkSum sum) {
NSLog(@"%@",sum); // 無論blk在堆上還是棧上,作爲參數的Block不會發生copy。
NSLog(@"%@",sum_blk); // 當blk copy到堆上時,sum_blk也被copy了一分到堆上上。
};
blk(sum_blk); // blk在棧上
blk = [[blk copy] autorelease];
blk(sum_blk); // blk在堆上
}
ObjC對象,不同於基本類型,Block會引起對象的引用計數變化。
先看下面代碼
@interface MyClass : NSObject {
NSObject* _instanceObj;
}
@end
@implementation MyClass
NSObject* __globalObj = nil;
- (id) init {
if (self = [super init]) {
_instanceObj = [[NSObject alloc] init];
}
return self;
}
- (void) test {
static NSObject* __staticObj = nil;
__globalObj = [[NSObject alloc] init];
__staticObj = [[NSObject alloc] init];
NSObject* localObj = [[NSObject alloc] init];
__block NSObject* blockObj = [[NSObject alloc] init];
typedef void (^MyBlock)(void) ;
MyBlock aBlock = ^{
NSLog(@"%@", __globalObj);
NSLog(@"%@", __staticObj);
NSLog(@"%@", _instanceObj);
NSLog(@"%@", localObj);
NSLog(@"%@", blockObj);
};
aBlock = [[aBlock copy] autorelease];
aBlock();
NSLog(@"%d", [__globalObj retainCount]);
NSLog(@"%d", [__staticObj retainCount]);
NSLog(@"%d", [_instanceObj retainCount]);
NSLog(@"%d", [localObj retainCount]);
NSLog(@"%d", [blockObj retainCount]);
}
@end
int main(int argc, char *argv[]) {
@autoreleasepool {
MyClass* obj = [[[MyClass alloc] init] autorelease];
[obj test];
return 0;
}
}
執行結果爲1 1 1 2 1。
__globalObj和__staticObj在內存中的位置是確定的,所以Block copy時不會retain對象。
_instanceObj在Block copy時也沒有直接retain _instanceObj對象本身,但會retain self。所以在Block中可以直接讀寫_instanceObj變量。
localObj在Block copy時,系統自動retain對象,增加其引用計數。
blockObj在Block copy時也不會retain。
非ObjC對象,如GCD隊列dispatch_queue_t。Block copy時並不會自動增加他的引用計數,這點要非常小心。
Block中使用的ObjC對象的行爲
1
2
3
4
5
6
|
@property (nonatomic, copy) void (^myBlock)( void ); MyClass* obj = [[[MyClass alloc] init] autorelease]; self.myBlock = ^ { [obj doSomething]; }; |
對象obj在Block被copy到堆上的時候自動retain了一次。因爲Block不知道obj什麼時候被釋放,爲了不在Block使用obj前被釋放,Block retain了obj一次,在Block被釋放的時候,obj被release一次。
Block在ARC和非ARC的區別
配置在棧上的Block也就是NSConcreteStackBlock類型的Block,如果其所屬的變量作用域結束該Block就會廢棄
。這個時候如果繼續使用該Block,就應該使用copy方法,將NSConcreteStackBlock拷貝爲_NSConcreteMallo
cBlock。當_NSConcreteMallocBlock的引用計數變爲0,該_NSConcreteMallocBlock就會被釋放。
如果是非ARC環境,需要顯式的執行copy或者antorelease方法。
而當ARC有效的時候,實際上大部分情況下編譯器已經爲我們做好了,自動的將Block從棧上覆制到堆上。包括以
下幾個情況:
1,Block作爲返回值時
類似在非ARC的時候,對返回值Block執行[[returnedBlock copy] autorelease];
2 將Block賦值給附有__strong修飾符的id類型的類或者Blcok類型成員變量時
3,Cocoa框架中方法名中還有useringBlock等時
4,GCD相關的一系列API傳遞Block時。
5. 被執行copy方法
比如:[mutableAarry addObject:stackBlock];這段代碼在非ARC環境下肯定有問題,而在ARC環境下方法參數
中傳遞NSConcreteStackBlock會自動執行copy。
void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
//Test2
void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("C\n");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
//Test3
typedef void (^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%c\n", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
Test1:
exampleB_addBlockToArray添加的Block爲NSConcreteStackBlock,在非ARC環境下,執行該Block時,棧
上的數據已經被清除了,錯誤。而在ARC環境下,作爲參數的Block會自動copy,不會出現問題;對應是五(2)情
況。
Test2:
exampleC_addBlockToArray添加的Block爲_NSConcreteGlobalBlock,存儲在程序的數據區,相當於一個不
使用外部環境的函數,沒有用到棧上的數據,所有無論是ARC還是非ARC都能正常執行。
Test3
ARC中作爲返回值的NSConcreteStackBlock會被執行copy操作。所有ARC運行正常,非ARC錯誤。對應是五(1)
情況。
Block的循環引用
retain cycle問題的根源在於Block和obj可能會互相強引用,互相retain對方,這樣就導致了retain cycle,最後這個Block和obj就變成了孤島,誰也釋放不了誰。比如:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];
解決這個問題的辦法是使用弱引用打斷retain cycle:
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString* string = [request responseString];
}];
request被持有者釋放後。request 的retainCount變成0,request被dealloc,request釋放持有的Block,導致Block的retainCount變成0,也被銷燬。這樣這兩個對象內存都被回收。
與上面情況類似的陷阱:
self.myBlock = ^ {
[self doSomething];
};
這裏self和myBlock循環引用,解決辦法同上:
__block MyClass* weakSelf = self;
self.myBlock = ^ {
[weakSelf doSomething];
};
@property (nonatomic, retain) NSString* someVar;
self.myBlock = ^ {
NSLog(@"%@", _someVer);
};
這裏在Block中雖然沒直接使用self,但使用了成員變量。在Block中使用成員變量,retain的不是這個變量,而會retain self。解決辦法也和上面一樣。
@property (nonatomic, retain) NSString* someVar;
__block MyClass* weakSelf = self;
self.myBlock = ^ {
NSLog(@"%@", self.someVer);
};
或者
NSString* str = _someVer;
self.myBlock = ^ {
NSLog(@"%@", str);
};
retain cycle不只發生在兩個對象之間,也可能發生在多個對象之間,這樣問題更復雜,更難發現
ClassA* objA = [[[ClassA alloc] init] autorelease];
objA.myBlock = ^{
[self doSomething];
};
self.objA = objA;
解決辦法同樣是用__block打破循環引用
ClassA* objA = [[[ClassA alloc] init] autorelease];
<span style="color: rgb(20, 25, 30); font-family: SimSun; font-size: 14px; line-height: 28px;">__block </span>MyClass* weakSelf = self;
objA.myBlock = ^{
[weakSelf doSomething];
};
self.objA = objA;
注意:MRC中__block是不會引起retain;但在ARC中__block則會引起retain。ARC中應該使用__weak或__unsafe_unretained弱引用。__weak只能在iOS5以後使用。
下面演示一下用weak的
像上面的寫法其實在ARC中會產生(Capturing 'demo' strongly in this block is likely to lead to a retain cycle)告警。如下圖:
在ARC中,編譯器智能化了,直接提示這樣寫會產生循環引用。因此很多愛去除告警的朋友就會想法去掉,好,咱再來看去掉時需注意的問題。
情況一:
- (IBAction)onTest:(id)sender
{
__weak BlockDemo *demo = [[BlockDemo alloc]init];
[demo setExecuteFinished:^{
if (demo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
直接在前面加一個__weak,但這樣真的沒有告警了嗎?如果有,哪麼恭喜歡你,說明編譯器還幫你大忙。見下圖
這時還會告警,說這是一個WEAK變量,就馬上會被release。因此就不會執行block中的內容。大家可以運行一下看
輸出結果爲:
2014-07-24 19:38:02.453 blockDemo[25305:60b] Object Constructor!
2014-07-24 19:38:02.454 blockDemo[25305:60b] Object Destoryed!
很顯然,馬上被release了,所以block 中的代碼根本就不執行。
謝天謝地,幸好編譯器提前告訴了我們有這個隱性危險。相信大家爲解決告警,又會得到一個比較圓滿的解決方案,見下:
- (IBAction)onTest:(id)sender
{
BlockDemo *demo = [[BlockDemo alloc]init];
__weak typeof(BlockDemo) *weakDemo = demo;
[demo setExecuteFinished:^{
if (weakDemo.resultCode == 200) {
NSLog(@"call back ok.");
}
}];
[demo executeTest];
}
這樣寫,即去除了告警又保證了block的運行。這纔是我們最終想要的結果。
輸出爲:
- 2014-07-24 19:40:33.204 blockDemo[25328:60b] Object Constructor!
- 2014-07-24 19:40:38.206 blockDemo[25328:60b] call back ok.
- 2014-07-24 19:40:38.207 blockDemo[25328:60b] Object Destoryed!
但大家別得意。有提示,相信大家都能處理,並得到個好的解決方法。哪麼下面大來再來看一下這個寫法,讓你真心甘拜下風。。。。。
- - (IBAction)onTest:(id)sender
- {
- __weak BlockDemo *demo = [BlockDemo blockdemo]; //這裏纔是重點,前面是[[BlockDemo alloc]init];會有告警。
- [demo setExecuteFinished:^{
- if (demo.resultCode == 200) {
- NSLog(@"call back ok.");
- }
- }];
- [demo executeTest];
- }
- + (BlockDemo *)blockdemo
- {
- return OBJC_AUTORELEASE([[BlockDemo alloc]init]);
- }
直接輸出:
- 2014-07-24 19:47:53.033 blockDemo[25395:60b] Object Constructor!
- 2014-07-24 19:47:53.035 blockDemo[25395:60b] Object Destoryed!
因此,寫這個主要用來告戒一些喜歡用BLOCK但又想當然的朋友,有一些朋友喜歡去除告警,但只是盲目的加上__weak 或__block關鍵語,往往可能存在一些重大的安全隱患。就像演示中block根本不走。如果到了發佈時,爲了去告警而這樣簡單的處理了,並沒有進行測試就打包。哪麼將死得很慘。。。。。
block會不會引行死循環,我說不會的理由。
見碼:
- - (IBAction)onTest:(id)sender
- {
- BlockDemo *demo = [BlockDemo blockdemo];//[[BlockDemo alloc]init];
- [demo setExecuteFinishedParam:^(BlockDemo * ademo) {
- if (ademo.resultCode == 200) {
- NSLog(@"call back ok.");
- }
- }];
- [demo executeTest];
- }
不管是在外面init,還是在裏面,且沒有加__block 及__weak。爲什麼,因爲我個人常常在使用自己寫的block時,如果是回調,比較喜歡把自身當作參數傳到block中。這樣期實是編譯器給我們做了弱引用。因此不會產生循環引用。
Block使用對象被提前釋放
看下面例子,有這種情況,如果不只是request持有了Block,另一個對象也持有了Block。
這時如果request 被持有者釋放。
這時request已被完全釋放,但Block仍被objA持有,沒有釋放,如果這時觸發了Block,在Block中將訪問已經銷燬的request,這將導致程序crash。爲了避免這種情況,開發者必須要注意對象和Block的生命週期。
// 有待考證
另一個常見錯誤使用是,開發者擔心retain cycle錯誤的使用__block。比如
<del>__block kkProducView* weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.xx = xx;
});</del>
將Block作爲參數傳給dispatch_async時,系統會將Block拷貝到堆上,如果Block中使用了實例變量,還將retain self,因爲dispatch_async並不知道self會在什麼時候被釋放,爲了確保系統調度執行Block中的任務時self沒有被意外釋放掉,dispatch_async必須自己retain一次self,任務完成後再release self。但這裏使用__block,使dispatch_async沒有增加self的引用計數,這使得在系統在調度執行Block之前,self可能已被銷燬,但系統並不知道這個情況,導致Block被調度執行時self已經被釋放導致crash。
<del>// MyClass.m
- (void) test {
__block MyClass* weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(@"%@", weakSelf);
});
}
// other.m
MyClass* obj = [[[MyClass alloc] init] autorelease];
[obj test];</del>
這裏用dispatch_after模擬了一個異步任務,10秒後執行Block。但執行Block的時候MyClass* obj已經被釋放了,導致crash。解決辦法是不要使用__block。
__block、__weak等修飾符
在MRC下,我們通常使用__block ,而在ARC下我們通常使用__weak , 或者__unsafe_unretaine(不安全,不建議使用)
來修飾對象防止循環引用而造成的內存泄露