block的深度探究(截取自動變量、__block、截獲對象、存儲域、copy函數 與 dispose函數、循環引用、ARC無效時Block的copy/release)

本章目錄

  • Block截取自動變量
  • __block說明符
  • Block存儲域
  • __block變量存儲域
  • Block中截獲對象
  • Block的copy函數 與 dispose函數調用時機
  • __block變量和對象
  • Block循環引用
  • ARC無效時Block的copy / release
  • 參考文獻

Block截取自動變量

  • 如何截獲自動變量?

Block語法轉換成C函數后,Block語法表達式中用到的自動變量會被作為成員變量追加到了__main_block_impl_0結構體中。
而此結構體中的成員變量類型與自動變量類型完全相同,但僅限于Block語法中使用到的自動變量。
因此,所謂“截獲自動變量值”意味著執行Block語法時,Block語法表達式所使用的自動變量值被保存到Block的結構體實例中。

  • 為什么Block語法中不能使用數組(C語言創建的數組)?

因為結構體中的成員變量與自動變量類型完全相同。
所以結構體中使用數組截取數組值,而后調用時再賦值給另一個數組。
也就是數組賦值給數組,這在C語言中是不被允許的。

簡單來說:Block轉化后,是個C語言的Struct結構體。在C語言里,數組是不能直接賦值給數組的。所以,Block中無法截獲C語言中的數組,因為對于C語言數組而言,直接賦值會報錯。

而我們平常對NSMutableArray的增刪和遍歷操作是可以用的,但是遍歷時不可進行刪操作,否則數組越界報錯。

__block說明符

  • 在Block中修改截獲的自動變量值有兩種方法。

  • 第一種:使用靜態變量,靜態全局變量,全局變量

從Block語法轉換成的C語言函數中訪問靜態全局變量 / 全局變量并沒有任何改變,可直接使用。
但是靜態變量卻是用靜態變量的指針來對其進行訪問,這是超出作用域使用變量的最簡單方法。

  • 為何靜態變量的這種方法不適用于自動變量?

因為靜態變量會存儲在堆上,而自動變量卻存在棧上。
當超出其作用域的時候,靜態變量還會存在,而自動變量所占內存則會被釋放因而被廢棄。所以不能通過指針訪問原來的自動變量。

  • 第二種方法: 使用 “ __block ” 修飾符

__block存儲域類說明符。存儲域類說明符,指定將變量設置到哪個存儲域中,如:auto 棧 , static 堆 , extern 全局 , register 寄存器 ,

若將__block修飾符修飾在一個自動變量前,如:

int main() {
    __block int val = 10;
    void (^blk)(void) = ^{val = 1;};
}

通過clang編譯后提取的Block相關代碼為:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t 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};
int main() {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

}

在此源碼中,__block(前面有兩個“ _ ”,這個markDown編譯器為何把這兩個符號給吞了 )所修飾的變量也同Block一樣變成了__Block_byref_val_0結構體類型的自動變量,即棧上生成的__Block_byref_val_0結構體實例。
關于此結構體的聲明:

struct __Block_byref_val_0 {
  void *__isa; (isa指針,指向一個存儲有自身基礎信息的地址,類似OC中的元類)
__Block_byref_val_0 *__forwarding; (forwarding指針,指向自身)
 int __flags; (標記)
 int __size; (大小)
 int val; (存儲的值,相當于原自動變量的成員變量)
};

給__block變量賦值的代碼 ^{val = 1;}; 會被轉換為:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}

剛剛在Block中向靜態變量賦值時,使用了指向該靜態變量的指針。
而向__block變量賦值時(又吞了),Block的__main_block_impl_0結構體實例會持有指向__block變量的_Block_byref_val_0 結構體實例的指針。

也就是Block的__main_block_impl_0結構體實例會持有一個指針,指向__block修飾的變量所轉換的__Block_byref_val_0結構體實例。

而__Block_byref_val_0結構體通過成員變量__forwarding指針訪問成員變量val

另外,為了能在多個Block中使用__block變量,__block變量的__Block_byref_val_0結構體并不在Block用__main_block_impl_0結構體中。
Block是通過__Block_byref_val_0結構體實例的指針來訪問。

Block存儲域

Block轉換為Block的結構體類型的自動變量,__block(又吞了)變量轉換為__block變量的結構體類型的自動變量。結構體類型的自動變量,即棧上生成的該結構體的實例。

所以,Block的實質是棧上Block的結構體實例,__block變量(又吞了)的實質是 棧上__block變量的結構體實例。

Block有三種類型:
_NSConcreteStackBlock
_NSConcreteGlobalBlock
_NSConcreteMallocBlock


Block的類型與Block的存儲區域有關:
_NSConcreteStackBlock類的Block對象設置在棧上(Stack 棧)
_NSConcreteGlobalBlock類的Block對象與全局變量一樣,設置在程序的數據區域(.data區)中(global 全局)
_NSConcreteMallocBlock類的Block對象則設置在堆中(Malloc 函數分配的內存塊)

在聲明全局變量的地方使用Block語法,生成的Block為_NSConcreteGlobalBlock
則該Block中的isa指針初始化時: impl.isa = &_NSConcreteGlobalBlock;
代表該Block的類為_NSConcreteGlobalBlock類。
則該Block結構體實例設置在程序的數據區域中。

還有一種情況,Block為_NSConcreteGlobalBlock類對象:
Block語法的表達式中不使用應截獲的自動變量時。(注意是自動變量,包括函數形參和非static局部變量,說明這個變量是在棧上運行時自動創建自動撤銷。)

因為在使用全局變量的地方不能使用自動變量,所以不存在對自動變量進行截獲。
由此Block用結構體實例的內容不依賴于執行時的狀態,所以整個程序中只需一個實例。
因此將Block用結構體實例設置在與全局變量相同的數據區域中即可。

也就是說只有當截獲自動變量時,Block結構體實例截獲的值才會根據執行時的狀態變化。
而當Block語法的表達式中不使用應截獲的自動變量時,Block結構體實例截獲的值不會根據執行時的狀態變化。

列如:

typedef int (^blk_t)(int);
for(int rate = 0; rate < 10; rate++){
    blk_t blk = ^(int count){return rate * count;};
}

此Block結構體實例每次for循環中截獲的值都不同,但去掉return rate * count 中的自動變量rate,則Block的結構體實例每次截獲的值都完全相同。

所以:

記述全局變量的地方有Block語法時
Block語法的表達式中不使用應截獲的自動變量時(函數形參和非static的局部變量)
在以上兩種情況下,Block為_NSConcreteGlobalBlock類對象。即Block配置在程序的數據區域中。

那么,若Block使用了一個全局變量Int a,而 a 在每次使用前都會修改值。
如:

typedef int (^blk_t)(int);
extern int a ;
for(int rate = 0; rate < 10; rate++){
    a = rate;
    blk_t blk = ^(int count){return count;};
    blk(a);
}

則該Block的類型仍舊為_NSConcreteGlobalBlock,因為并Block語法的表達式中沒有使用應截獲的自動變量,是直接使用的全局變量。

除此之外的Block語法生成的Block為_NSConcreteStackBlock類對象,且設置在棧上。
而棧上的Block通過Copy操作改變到堆上,就是_NSConcreteMallocBlock類對象。

設置在棧上的Block,如果其所屬的變量作用域結束,該Block就會被廢棄。

若__block變量也配置在棧上,如果其所屬的變量作用域結束,則該__block(又吞)變量也會被廢棄。
因此,Block通過Copy操作會復制到堆上,其所引用的__block(又吞)變量也會復制到堆上。

復制到堆上的Block其所屬類型為:impl.isa = &_NSConcreteMallocBlock;

當__block變量復制到堆上時,之前所說的__block(又吞,下同,不再贅述)變量的結構體成員變量__forwading會指向復制到堆上的自身,由此實現無論__block變量配置在堆上還是棧上都能夠正確的訪問__block變量。

那么編譯器何時會對Block自動判斷進行Copy(復制)操作何時不能?

在ARC環境下自動判斷進行Copy操作:

  • 將Block作為函數參數返回值返回時,編譯器會自動進行Copy操作。
  • 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時

如果不能自動Copy,調用一下Copy實例方法就可以復制到堆上。

ARC環境下,返回一個對象時會先將該對象賦值給一個臨時實例指針,而后對其進行retain操作,最后將對象注冊到注冊表中返回。
而runtime/objc-arr.mm里面有說明,Block的retain操作objc_retainBlock函數實際上是Block_copy函數。
在執行對Block的retain操作,objc_retainBlock函數執行后,棧上的Block復制到堆上,同時返回堆上的地址作為指針賦值給臨時實例變量。

  • 編譯期不能自動判斷進行Copy操作:

  • 向方法或函數的參數中傳遞Block時。(注意:Cocoa框架中的方法,其方法名中若含有usingBlock等時 與 GCD的API中會自動判斷進行Copy操作)。

如NSArray的initWithObjects需要手動執行Copy操作,而enumerateObjectsUsingBlock則不用,因為編譯器會自動進行復制操作。
復制操作,就是Block的實例執行Copy實例方法即可。

  • Block的復制效果

注意: 無論Block配置在何處,用copy方法復制都不會引起任何問題!包括內存泄露也不可能!不用擔心無法進行releas操作!所以不確定的時候盡情調用Copy方法吧!

如:blk = [[[[blk copy] copy] copy] copy];
可轉換為:

{
    //第一次copy操作會將配置在棧上的Block賦值給變量blk中

    //而后對blk進行Copy后,將配置在堆上的Block賦值給變量tmp
    //此時變量tmp持有強引用的Block

    blk_t tmp = [blk copy];

    //將變量tmp的Block賦值為變量blk,變量blk持有強引用的Block
    //因為之前賦值的Block配置在棧上,所以不受此賦值的影響。
    //此時Block的持有者為變量blk 和 變量 tmp

    blk = tmp;
}
    //變量作用域結束,所以變量tmp被廢棄,其強引用失效并釋放所持有的Block
    //由于Block被變量blk持有,所以沒有被廢棄

{
    //配置在堆上的Block被賦值變量blk,同時變量blk持有強引用的Block    

    //配置在堆上的Block被賦值到變量tmp中,變量tmp持有強引用的Block
    blk_t tmp = [blk copy];

    //由于向變量blk進行了賦值,所以現在賦值的Block的強引用失效,Block被釋放
    //由于Block被變量tmp所持有,所以未被廢棄
    //變量blk中賦值了變量tmp的Block,變量blk持有強引用的Block
    //此時Block的持有者為變量tmp與變量blk
    blk = tmp;
}
    //變量作用域結束,變量tmp被廢棄
    //強引用失效并釋放所持有的Block
    //由于變量blk還處于持有的狀態,Block沒有被廢棄

    //以下重復,先用一個變量tmp持有堆上的Block再賦值給blk
    //于是blk釋放舊值,保留新值
    //超出變量作用域后釋放tmp,此時只有blk持有
{
    blk_t tmp = [blk copy];
    blk = tmp;
}
{
    blk_t tmp = [blk copy];
    blk = tmp;
}

__block變量存儲域

block 1

如圖所示,一個Block從棧復制到堆時,使用的所有__block變量也都會復制到堆上并被Block持有。若此時__block變量已經在堆上,則被該Block持有。

若配置在堆上的Block被廢棄,那么它所有的__block變量也就被釋放。

在代碼:

__block int val = 0;
 void (^blk)(void) = [^{++val;} copy];
++val;
blk();

利用copy方法復制使用了__block變量的Block語法,于是二者都從棧復制到堆上。

而其中 ^{++val;} 與 ++val; 都可轉換形式為: ++(val.__forwarding -> val);

在變換Block語法的函數中,該變量val為復制到堆上的__block變量的結構體實例。

而與Block無關的變量val,則為復制前棧上的__block變量用結構體實例。

但是棧上__block變量的結構體實例在__block變量從棧復制到堆上時,會將成員變量__forwarding的值替換為復制到目標堆上的__block變量用結構體實例的地址。如圖:

屏幕快照 2016-11-15 上午9.48.49.png

由此實現無論Block語法中、語法外使用__block變量,還是__block變量配置在堆上或棧上,都可以順利訪問同一個__block變量。

Block中截獲對象

在代碼:

   id array = [NSMutableArray array];
        blk_t blk = ^(id obj){
            
            [array addObject:obj];
            NSLog(@"array count = %ld",[array count]);
        
        };
        
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);

輸出結果為:
array count = 1 
array count = 2
array count = 3

意味著賦值給變量array的對象在超出其變量作用域后仍存在。
將源碼用clang編譯后,截取Block相關的部分:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
// @implementation block


struct __block__init_block_impl_0 {
  struct __block_impl impl;
  struct __block__init_block_desc_0* Desc;
  id array;
  __block__init_block_impl_0(void *fp, struct __block__init_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __block__init_block_func_0(struct __block__init_block_impl_0 *__cself, id obj) {
  id array = __cself->array; // bound by copy


            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_2587xc0n2dv6f08yv3mdbf7c0000gn_T_block_679c7b_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));

        }
static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __block__init_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __block__init_block_impl_0*, struct __block__init_block_impl_0*);
  void (*dispose)(struct __block__init_block_impl_0*);
} __block__init_block_desc_0_DATA = { 0, sizeof(struct __block__init_block_impl_0), __block__init_block_copy_0, __block__init_block_dispose_0};

//使用部分

 id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
 
 blk_t blk = ((void (*)(id))&__block__init_block_impl_0((void *)__block__init_block_func_0, &__block__init_block_desc_0_DATA, array, 570425344));

      
  ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
      
  ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
      
  ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));

轉換一下:
 id array = [NSMutableArray array];
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,array,570425344);

(*blk -> impl.FuncPtr)(blk,[[NSObject alloc] init]);
(*blk -> imply.FuncPtr)(blk,[[NSObject alloc] init]);
(*blk -> imply.FuncPtr)(blk,[[NSObject alloc] init]);b

請注意此結構體:

struct __block__init_block_impl_0 {
  struct __block_impl impl;
  struct __block__init_block_desc_0* Desc;
  id array;
}

XCode8中對截獲的變量array 不進行顯示__strong(這里也有兩個 “ _ ")修飾了,但依舊使用__block__init_block_copy_0 函數(在此之前是__main_block_copy_0) 和 __block__init_block_dispose_0函數(在此之前是__main_block_dispose_0)進行管理。

在此之前的OC中,C語言結構體不能含有附有__strong修飾符的變量,因為編譯器不知道何時進行C語言結構體的初始化和廢棄操作,因此不能很好地管理內存。
但是OC的運行庫能準確把握Block從棧復制到堆以及堆上Block被廢棄的時機。

因此Block的結構體中可以恰當地進行初始化和廢棄,附有__strong修飾符或__weak修飾符的變量。
為此需使用在__main_block_desc_0結構體中增加的成員變量copy 和 dispose,以及作為指針賦值給該成員變量的__main_block_copy_0函數和__block__init_block_dispose_0函數。
但現在沒有成員變量copy 和 dispose 而是直接使用函數指針__block__init_block_copy_0 函數 __block__init_block_dispose_0函數.

由于Block結構體中需要管理賦值給變量array的對象,因此__block(這里有四個 “ _ ")__init_block_copy_0函數通過_Block_object_assign函數將對象類型對象賦值給Block結構體的成員變量array中并持有該對象。

static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_assign函數調用相當于retain實例方法的函數。

__block(又尼瑪吞了四個“ _ ”!)__init_block_dispose_0 函數使用_Block_object_dispose函數,釋放賦值在Block用結構體成員變量array中的對象。

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

_Block_object_dispose函數調用相當于release實例方法的函數,釋放賦值在對象類型的結構體成員變量中的對象。

Block的copy函數 與 dispose函數調用時機

  • 什么時候棧上的Block會復制到堆上?

1.調用Block的copy實例方法
2.將Block作為函數參數返回值返回時,編譯器會自動進行Copy操作。
3.將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量
4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API傳遞Block時

Block從棧復制到堆時_Block_copy函數被調用。
釋放復制到堆上的Block,誰都不持有Block而使其被廢棄時調用dispose函數。相當于對象的dealloc實例方法。

由于此種構造,Block中截獲的對象就能夠超出其變量作用域而存在。

另外,在使用__block變量時也是使用了copy函數和dispose函數來管理。

通過這兩個參數來區分copy函數和dispose函數的對象類型對象還是__block變量。

copy函數持有截獲的對象 或 持有所使用的__block變量,dispose函數釋放截獲的對象 或 釋放所使用的__block變量。

Block中使用的賦值給id類型的自動變量的對象 和 復制到堆上的__block變量由于被堆上的Block所持有,因而可超出其變量作用域而存在。

需要注意的是,若Block不復制到堆上,則其不會持有截獲的對象。則對象會隨著變量作用域結束而結束。

__block變量和對象

如前文所述,在Block中使用附有__strong修飾符的id類型或對象類型自動變量的情況下,當Block從棧復制到堆時,使用_Block_object_assign函數持有Block截獲的對象。

當堆上的Block被廢棄時,使用_Block_object_dispose函數釋放Block截獲的對象。

  • 在__block變量為附有__strong修飾符的id類型或對象類型自動變量的情形下會發生同樣的過程

當__block變量從棧復制到堆時,使用_Block_object_assign函數,持有賦值給__block變量的對象。
當堆上的__block變量被廢棄時,使用_Block_object_dispose函數,釋放賦值給__block變量的對象。

如:  
 __block id array = [NSMutableArray array];
        blk_t blk = ^{
            NSLog(@"%@",array);
        };
        blk();
會轉換為:
struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

static void __block__init_block_copy_0(struct __block__init_block_impl_0*dst, struct __block__init_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __block__init_block_dispose_0(struct __block__init_block_impl_0*src) {_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

__block變量聲明部分:
 __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
簡化為: __Block_byref_array_0 array = {
    0,
    &array,
    33554432,
    sizeof(__Block_byref_array_0),
    __Block_byref_id_object_copy_131, 
    __Block_byref_id_object_dispose_131,
    [[NSMutableArray alloc]init]
};

在Block中使用附有__weak修飾符的id類型變量會如何?如以下代碼

   blk_t blk;
        
       {
            
            id array = [NSMutableArray array];
            id __weak array2 = array;
            blk = ^(id obj){
                
                [array2 addObject:obj];
                NSLog(@"array2 count = %ld" ,[array2 count]);
            };
        }
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
      
輸出結果為:
array2 count = 0
array2 count = 0
array2 count = 0

因為__strong修飾的變量array在該變量作用域結束的同時被釋放、廢棄,nil被賦值給變量array2。

即使在array2前面加上__block說明符,結果也是不變。因為array依舊會被釋放廢棄,依舊是nil賦值給array2

另外,使用___unsafe_unretained修飾符的變量只不過與指針相同,不會像__strong或__weak修飾符那樣進行處理,因而通過懸垂指針訪問已被廢棄的對象程序會崩潰。
比如上述代碼的__weak替換為__unsafe_unretained就會崩潰。

若一個變量obj同時指定__autoreleasing修飾符和__block說明符會引起編譯錯誤,因為并沒有設定__autoreleasing修飾符與Block同時使用的方法。

Block循環引用

因為寫的人實在太多,所以不想過多講述,簡單提下吧。

如果在Block中使用附有__strong修飾符的對象類型自動變量,那么Block從棧復制到堆時,該對象為Block所持有。因而容易引起循環引用。

為避免此類事件,可用__weak修飾對象。

而在面向iOS4 與 OS Snow Leopard的應用程序中,必須使用__unsafe_unretained修飾符代替__weak。因為這個版本還沒有__weak。

另外還可使用__block變量來避免循環引用,只要在Block語法中手動讓__block修飾的對象置為nil即可,因此只要最少調用一次Block即可避免。

ARC無效時的copy / release

ARC無效時,需要手動將Block從棧復制到堆。此外,ARC無效時要手動釋放復制的Block。使用copy實例方法來復制,用releas實例方法來釋放。

只要Block有一次復制并配置在堆上,就可通過retain實例方法持有。若配置在棧上則retain實例方法不起任何作用。因此推薦用copy方法來持有。

由于Block是C語言的擴展,所以在C語言中也可以使用Block語法。此時用Block_copy函數 和 Block_release函數代替Copy / release實例方法,使用方法和引用計數的思考方式同OC中的copy / release實例方法:
Block_copy(blk)、Block_release(blk)
Block_copy函數就是之前提過的_Block_copy函數

ARC無效時,當Block從棧復制到堆時,若Block使用的變量為附有__block說明符的id類型或對象類型的自動變量,不會被retain。因此,ARC無效時__block說明符被用來Block中的循環引用。而若Block使用的變量為沒有__block說明符的id類型或對象類型的自動變量,則無論ARC有效與否都會被retain。

參考文獻:

Objective - C 高級編程:iOS與OS X多線程和內存管理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容