iOS中Block介紹(二)內存管理與其他特性
一、block放在哪里
我們針對不同情況來討論block的存放位置:
1.棧和堆
以下情況中的block位于堆中:
- void foo()
- {
- __block int i = 1024;
- int j = 1;
- void (^blk)(void);
- void (^blkInHeap)(void);
- blk = ^{ printf("%d, %d\n", i, j);};//blk在棧里
- blkInHeap = Block_copy(blk);//blkInHeap在堆里
- }
- - (void)fooBar
- {
- _oi = 1;
- OBJ1* oj = self;
- void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};
- void (^oblkInHeap)(void) = [oblk copy];//oblkInHeap在堆中
- }
2.全局區(qū)
以下情況中的block位于全局區(qū):
- static int(^maxIntBlock)(int, int) = ^(int a, int b){return a>b?a:b;};
- - (void)fooBar
- {
- int(^maxIntBlockCopied)(int, int) =[maxIntBlock copy];
- }
- void foo()
- {
- int(^maxIntBlockCopied)(int, int) = Block_copy(maxIntBlock);
- }
需要注意的是,這里復制過后的block依舊位于全局區(qū),實際上,復制操作是直接返回了原block對象。
二、block引用的變量在哪里
1.全局區(qū)
全局區(qū)的變量存儲位置與block無關:
- static int gVar = 0;
- //__block static int gMVar = 1;
- void foo()
- {
- static int stackVar = 0;
- // __block static int stackMVar = 0;
- }
注意:static變量是不允許添加__block標記的
2.堆棧
此時,你可能會問,當函數foo返回后,棧上的j已經回收,那么blkInHeap怎么能繼續(xù)使用它?這是因為沒有__block標記的變量,會被當做實參傳入block的底層實現函數中,當block中的代碼被執(zhí)行時,j已經不是原來的j了,所謂物是人非就是這樣吧~
另外,如果使用到變量j的所有block都沒有被復制至heap,那么這個變量j也不會被復制至heap。
因此,即使將j++這一句放到blk()這句之前,這段代碼執(zhí)行后,控制臺打印結果也是:1024, 1。而不是1024, 2
三、其他特性
1.復制的行為
對block調用復制,有以下幾種情況:
1.對全局區(qū)的block調用copy,會返回原指針,并且這期間不處理任何東西(至少目前的內部實現是這樣);
2.對棧上的block調用copy,每次會返回新復制到堆上的block的指針,同時,所有__block變量都會被復制至堆一份(多次拷貝,只會生成一份)。
3.對已經位于heap上的block,再次調用copy,只會增加block的引用計數。
為什么我們不討論retian的行為?原因是并沒有Block_retain()這樣的函數,而且objc里面的retain消息發(fā)送給block對象后,其內部實現是什么都不做。
2.objc類中的block復制
objc類實例方法中的block如果被復制至heap,那么當前實例會被增加引用計數,當這個block被釋放時,此實例會被減少引用計數。
但如果這個block沒有使用當前實例的任何成員,那么當前實例不會被增加引用計數。這也是很自然的道理,我既然沒有用到這個instance的任何東西,那么我干嘛要retian它?
我們要注意的一點是,我看到網上有很多人說block引起了實例與block之間的循環(huán)引用(retain-cycle),并且給出解決方案:不直接使用self而先將self賦值給一個臨時變量,然后再使用這個臨時變量。
但是,大家注意,我們一定要為這個臨時變量增加__block標記(多謝第三篇文章回帖網友的提醒)。
這一章我們以結果導向的方式來說明了各種情況下,block的內存問題,下一章,我將剖析運行時庫的源碼,從根源闡述block的行為。也就是過程導向的方式了。