你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> [IOS學習]之九、Blocks的實現續

[IOS學習]之九、Blocks的實現續

編輯:IOS開發綜合
Blocks的存儲域 Block和__block變量的實質就是 在棧上的結構體實例。 如:__block變量實質是 棧上__block變量的結構體實例。 其中Block也是oc的對象,該OC的類為:_NSConcreteStackBlock。 雖然該類並沒有出現在源碼,但是有很多與之類似的類: _NSConcreteStackBlock 它的對象Block設置在棧上 _NSConcreteGlobalBlock 他與全局變量一樣,設置在程序的數據區域(.data區)中。 _NSConcreteMallocBlock 它的對象設置在由malloc函數分配的內存塊中(堆)
如圖: \ \

前面我們看到的是stack,設置在棧上。 但是例如: void (^blk)(void) = ^{printf("sdfs");}; int main() ****
這裡初始化的時候用的是global: impl.isa = &_NSConcreteGlobalBlock;
此時,Block用結構體實例設置在程序的數據區域中。 因為在使用全局變量的地方不能使用自動變量,所以不存在對自動變量進行截獲。 Block用結構體實例的內容不依賴於執行時的狀態,所以整個程序中只需要一個實例。因此將Block用結構體實例設置在與全局變量相同的數據區域中即可。
只在截獲自動變量時,Block用結構體實例截獲的值才會根據執行時的狀態變化。 只要Block不截獲自動變量,就可以將Block用結構體實例設置在程序的數據區域。
在以下情況Block為_NSConcreteGlobalBlock類對象: 1、記述全局變量的地方有Block語法時 2、Block語法的表達式中不使用應截獲的自動變量時
除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象,且設置在棧上。
還有就是設置在堆上的情況: 1、Block超出變量作用域可存在的原因 分配在棧上的Block和__block變量 其所屬的變量作用域結束,該Block或者__block變量也會被廢棄。 但是Blocks提供了 將Block和__block變量從棧上賦值到堆上的方法來解決這個問題, 這樣即使語法記述其作用域結束,堆上的Block也能繼續存在。 如圖: \ \
\ \

此時,賦值到堆上的Block將_NSConcreteMallocBlock類對象寫入Block用結構體實例的成員變量isa; impl.isa = _NSConcreteMallocBlock;
2、__block變量用結構體成員變量__forwarding存在的原因 上面的情況下只是Block,而__block變量需要用結構體成員變量__forwarding可以實現 無論__block變量配置在棧上還是堆上時都能夠正確地訪問__block變量。
在下面我們會說:在__block變量配置在堆上的狀態下,也可以訪問棧上的__block變量。 此時,只要棧上的結構體實例成員變量__forwarding指向堆上的結構體實例,不管是從棧上的__block變量還是從堆上的__block變量都能正確訪問。
Blocks提供的復制方法,如何從棧上復制到堆上的? ARC有效的時候,編譯器可以自動判斷。 來看Block函數: typedef int (^blk_t)(int); blk_t func(int rate) { return ^(int count){return rate*count;}; } 此時將會返回配置在棧上的Block的函數。 即 程序執行中,從該函數返回函數調用方時變量作用域結束,因此棧上的Block也被廢棄。 雖然有問題,但是ARC的編譯如下: blk_t func(int rate) { blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate); tmp = objc_retainBlock(tmp); return objc_autoreleaseReturnValue(tmp); } 此時tmp為:blk_t __strong tmp。 這裡的objc_retainBlock實際上是:_Block_copy //將通過Block語法生成的Block, 即配置在棧上的Block用結構體實例, 賦值給相當於Block類型的變量tmp中 tmp = _Block_copy(tmp); //_Block_copy函數 將棧上的Block復制到堆上, 復制後, 將堆上的地址作為指針賦值給變量tmp return objc_autoreleaseReturnVlaue(tmp); //將堆上的Block作為OC對象, 注冊到autoreleasepool中,然後返回該對象。
將Block作為函數返回值返回時,編譯器會自動生成復制到堆上的代碼。
但是 當我們使用copy實例方法的時候,我們需要手動生成代碼,將Block從棧上復制到堆上:alloc/new/copy/mutableCopy中的copy。 當我們向方法或者函數的參數中傳遞Block時 我們需要復制, 但是如果在方法或者函數中適當地復制了傳遞過來的參數,那麼就不需要在調用該方法或函數前手動復制了。 如: Cocoa框架的方法且方法名中含有usingBlock等。 Grand Central Dispatch的API。 例: - (id) getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects:^{NSLog(@"blk0:%d", val);}, ^{NSLog("blk1:%d", val);}, nil]; }
id obj = getBlockArray(); typedef void (^blk_t)(void); blk_t blk = (blk_t)[obj objectAtIndex:0]; blk();
此時,blk()會發生錯誤,因為getBlockArray函數結束的時候,棧上的Block被廢棄。 需要如下: return [[NSArray alloc] initWithObjects:[^{NSLog(@"blk0:%d", val);} copy], [^{NSLog(@"blk1:%d", val);} copy], nil];
這裡Block可以直接調用copy方法。 最後的blk = [blk copy];
對於配置在堆上的Block以及配置在程序的數據區域上的Block,調用copy會怎樣 ? 如圖: \ \
我們多次調用copy又會怎樣呢 ? 如:blk = [[[[blk copy] copy] copy] copy]; 解釋: { //配置在棧上的Block,賦值給變量blk中 blk_t tmp = [blk copy]; //將配置在堆上的Block賦值給變量tmp中,變量tmp持有強引用的Block blk = tmp; //將變量tmp 用Block賦值為變量blk, 變量blk持有強引用的Block。 //因為原先賦值的Block配置在棧上,所以不受此賦值的影響, 此時Block的持有者為變量blk和變量tmp } //由於變量作用域結束,所以變量tmp廢棄, 其強引用失效並釋放所持有的Block //由於Block被變量blk持有,所有沒有被廢棄。 { //配置在堆上的Block被賦值變量blk,同時變量blk持有強引用的Block blk_t tmp = [blk copy]; //配置在堆上的Block被賦值到變量tmp中,變量tmp持有強引用的Block blk = tmp; //由於變量blk進行了賦值,所以現在賦值的Block的強引用失效,Block被釋放。 //由於Block被變量tmp所持有,所以沒有被廢棄。 //變量blk中賦值了變量tmp的Block, 變量blk持有強引用的Block。 //此時Block的持有者為變量blk和變量tmp } //由於變量作用域結束,變量tmp被廢棄, 其強引用失效被釋放所持有的Block //由於變量blk還處於持有狀態,Block沒有被廢棄。 { blk_t tmp = [blk copy]; blk = tmp; } { blk_t tmp = [blk copy]; blk = tmp; }

__block變量存儲區 現在來說一下使用__block變量的Block從棧上復制到堆上會有什麼影響。 如圖: \ \
如果一個Block中使用__block變量,當該Block從棧賦值到堆時,使用的所有__block變量也必定配置在棧上。這些__block變量也全部被從棧復制到堆上。 此時Block持有__block變量。 即使在該Block已復制到堆的情況下,復制Block也對所使用的__block變量沒有任何影響。 如圖: \
\ 當多個Block使用__block變量呢 ? 此時,第一個block復制到堆上後,剩下的Block復制到堆上時,被復制的Block也會持有__block變量,並增加__block變量的引用計數。 如圖: \ \
如果Block被廢棄,那麼它所使用的__block變量被釋放。 \ \
這裡的思考方式與OC的引用計數式內存管理完全相同。
現在來看一下__block變量用結構體成員變量__forwarding的原因: 不管__block變量配置在棧上還是在堆上,都能夠正確地訪問該變量。 也就是說,通過Block的復制,__block變量也從棧復制到了堆上,此時可同時訪問棧上的__block變量和堆上的__block變量: __block int val = 0; void (^blk)(void) = [^{++val;} copy]; ++val; blk(); NSLog(@"%d", val);
我們利用copy方法復制使用了__block變量的Block語法。 Block和__block變量兩者均是從棧上復制到堆上。 ^{++val;} //Block語法的表達式中使用初始化後的__block變量 堆上的__block變量 ++val; //Block語法之後使用與Block無關的變量。 復制前 棧上的__block變量
轉換: ++(val.__forwarding->val); 在棧上的__block變量用結構體實例在__block變量從棧復制到堆上時,會將成員變量__forwarding的值替換為復制目標堆上的__block變量用結構體實例的地址: \
\ 這樣,無論是在Block語法中,語法外使用__block變量,還是__block變量配置在棧或者堆上,都能順利訪問同一個__block變量。

截獲對象 blk_t blk; { id array = [[NSMutableArray alloc] init]; blk = [^(id obj) {[array addObject:obj];NSLog(@"array count = %ld", [array count]);} copy]; } blk ([[NSObject alloc] init]); blk ([[NSObject alloc] init]); blk ([[NSObject alloc] init]);
變量作用域結束,變量array被廢棄,強引用失效。 但是代碼運行正常, 這就說明賦值給變量array的NSMutableArray類的對象在該源代碼最後Block的執行部分超出其變量作用域而存在。 轉換後的源碼: //Block結構體,函數 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id __strong array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id __strong_array, int flags = 0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) { id __strong array = __cself->array; [array addObject:obj]; NSLog(@"array count = %ld", [array count]); }
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) { _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT); }
static void __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0); __main_block_copy_0, __main_block_dispose_0 };
//Block語法,使用Block部分 blk_t blk; { id __strong array = [[NSMutableArray alloc] init]; blk = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, array, 0x22000000); blk = [blk copy]; } (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]); (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]); (*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
這裡要注意,被賦值NSMutableArray類對象並被截獲的自動變量array,可以看到他是Block用的結構體中附有__strong修飾符的成員: struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id __strong array; }; 之前說:oc中,c結構體中不能含有附有__strong修飾符的變量,因為編譯器不知道何時進行c結構體的初始化和廢棄。 但是oc的運行時庫可以准確把握Block從棧復制到堆以及堆上的Block被廢棄的時機。 所以 我們需要在__main_block_desc_0中增加copy和dispose,以及作為指針賦值給該成員變量的__main_block_copy_0函數和__main_block_dispose_0函數。
在源代碼的Block中,含有附有__strong修飾符的對象類型變量array,所以需要恰當管理賦值給變量array的對象,因此__main_block_copy_0函數使用_Block_object_assign函數將對象類型對象賦值給Block用結構體的成員變量array中並持有對象。 static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) { _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT); } _Block_object_assign 函數調用想讓與retain實例方法。 當然在dispose函數中也是這樣的。 _Block_object_dispose相當於 release實例方法。
我們來看看copy和dispose函數什麼時候被調用:

什麼時候棧上的Block賦值到堆? 1、調用Block的copy實例方法時。 2、Block作為函數返回值返回時。 3、將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時。 4、在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時。
在調用Block的copy的時候,如果Block配置在棧上,那麼該Block會從棧復制到堆。 當Block作為函數返回值的時候,將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量的時候,編譯器會自動將對象的Block作為參數,並調用_Block_copy函數。這與調用Block的copy實例方法效果相同。在方法中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中傳遞Block時,在該方法或函數內部對傳遞過來的Block調用Block的copy實例方法或者_Block_copy函數。 也就是說,雖然從源代碼來看,在上面這些情況下棧上的Block被復制到了堆上, 但其實可歸結為_Block_copy函數被調用時Block從棧復制到堆。 相反,釋放復制到堆上的Block後,誰都不持有Block而使其被廢棄時調用dispose函數, 相當於對象的dealloc實例方法。 這樣,通過使用附有__strong修飾符的自動變量,Block中截獲的對象就能超出其變量作用域存在了。
截獲對象和使用__block變量時的不同: 對象:BLOCK_FIELD_IS_OBJECT __block變量:BLOCK_FIELD_IS_BYREF 通過標志來區分是對象還是__block變量。
但是與copy函數持有截獲的對象,dispose函數釋放截獲的對象相同,copy函數持有所使用的__block比那裡,dispose函數釋放所使用的__block變量。
因此,Block中使用的賦值給附有__strong修飾符的自動變量的對象和復制到堆上的__block變量由於被堆上的Block所持有,因而可超出其變量作用域而存在。
在Block中使用對象類型自動變量時,除了以下3種情況,推薦調用Block的copy實例方法。 1、Block作為函數返回值返回時。 2、將Block賦值給類的附有__strong修飾符的id類型或Block類型成員變量時。 3、向方法名中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中傳遞Block時。



-------2014/3/22 Beijing
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved