你好,歡迎來到IOS教程網

block

編輯:IOS開發綜合

一、Blocks概要

Blocks是C語言的擴充功能:帶有自動變量(局部變量)的匿名函數。
顧名思義,所謂的匿名函數就是不帶有名稱的函數。c語言的標准不允許存在這樣的函數。例如:

int func (int count);
int resule = func(10);

如果想使用函數指針來代替直接調用函數,那麼似乎不用知道函數名也能夠使用該函數

int func (int count);
int (*funcptr)(int) = &func;
int result = (*funcptr)(10);

C語言函數中可能使用的變量:

自動變量(局部變量); 函數的參數; 靜態變量(靜態局部變量); 靜態全局變量; 全局變量;

二、Blocks模式

1、Block語法

(1)、^ 返回值類型 參數列表 表達式;

^void (int i){ printf("%d", i);}

(2)、^ 參數列表 表達式;

^(int i){printf("%d", i);}

(3)、^ 表達式;

^{printf("123");}

當你省略返回值類型的時候,你的表達式裡return返回什麼類型,那麼你的返回值類型就是什麼。
當你不適用參數的時候,(void) 參數列表可以省略。

int (^myBlock) (int) = ^(int num){
        return num * num;
};

說明:

(1)、int:返回值類型,如果沒有返回值則為void (2)、(^myBlock):塊定義需要有一個^標記,myBlock是塊名稱 (3)、(int):參數類型列表,如果沒有參數則為void (4)、^(int num):以^開頭的參數列表,如果沒有則為void,也可以省略 (5)、{}:block體(相當於函數體)

2、Block類型變量

(1)、使用block的時候,我們可以聲明block變量,他同c中的函數指針:

int f(int a) {  
     return a;  
}  
int (*fa)(int) = &f;  

(2)、使用Block語法將Block賦值為Block類型變量;

int (^blk)(int) = ^(int a){return a;};

(3)、由^開始的Block語法生成的Block被賦值給變量blk中。因為與通常的變量相同,所有當然也可以由Block類型變量向Block類型變量賦值;

int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;

(4)、在函數參數中使用Block類型的變量可以向函數傳遞Block

void func (int (^blk) (int)){
    NSLog(@"%d",blk(1));
}

(5)、在函數返回值中指定Block類型,可以將Block作為函數的返回值返回

int (^funk())(int){
    return ^(int count){return count + 1;};
}

由此可見,在函數參數和返回值中使用Block類型變量時,記述方式極為復雜。這時,我們可以像使用函數指針類型時那樣,使用typedef來解決該問題:

typedef int (^blk_t)(int);

3、獲取自動變量值

- (void)viewDidLoad {
    [super viewDidLoad];
    int myCount = 1;
    int (^blk)(void) = ^{
        NSLog(@"%d",myCount);
        return myCount;
    };
    myCount ++;
    blk();
    NSLog(@"%d",myCount);
}

打印結果如下:

2016-05-20 15:26:43.499 class_01[42917:1552743] 1
2016-05-20 15:26:43.500 class_01[42917:1552743] 2

Blocks中,Block表達式截獲所使用的自動變量的值,即保存該自動變量的瞬間值。因為Block表達式保存了自動變量的值,所以在執行Block語法後,即使改寫Block中使用的自動變量的值也不會影響Block執行自動變量的值。

4、__block說明符

實際上,自動變量值截獲只能保存執行Block語法的瞬間的值。保存後就不能改寫該值。若嘗試改寫截獲的自動變量值,會出現編譯錯誤。如下:

int val = 0;
void (^blk)(void) = ^{
    val = 1;
}

若想在Block語法的表達式中將值賦給在Block語法外聲明的自動變量,需要在該自動變量上附加__block說明符。

- (void)viewDidLoad {
    [super viewDidLoad];
    __block int myCount = 1;
    int (^blk)(void) = ^{
        NSLog(@"%d",myCount);
        myCount = 4;
        return myCount;
    };
    myCount ++;
    blk();
    NSLog(@"%d",myCount);
}
2016-05-20 15:33:30.154 class_01[42969:1556701] 2
2016-05-20 15:33:30.155 class_01[42969:1556701] 4

注意:
使用附有__block說明符的自動變量可在Block中賦值,該變量稱為__block變量。

(1)、__block是只針對局部變量生效的一種描述變量存儲類型的關鍵字,因此__block類型的變量都是棧變量; (2)、__block類型的變量在其定義的語法范圍裡,和該范圍內的所有block共享存儲空間,當block在被復制到heap區域時,同區域內的__block變量占用的內存不會隨著退棧而銷毀; (3)、出於優化的考慮,棧中的block對象最開始和一般的棧局部變量是相同的,當使用Block_copy對block進行復制時,才被拷貝到heap區域; (4)、__block變量不能是一個可變長數組;

5、截獲的自動變量

由上述可得如果將值賦值給Block中截獲的自動變量,就會產生編譯;
如果截獲Objective-C對象,調用變更該對象的方法如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableArray* arr = [NSMutableArray array];
    void (^blk)(void) = ^{
        [arr addObject:@"1"];
    };
    blk();
    NSLog(@"%@",arr);
}

打印:

2016-05-20 16:10:02.051 class_01[43053:1570116] (
    1
)

arr是一個指針,指向一個可變長度的數組。在block裡面,並沒有修改這個指針,而是修改了這個指針指向的數組。換句話說,arr是一個整數,保存的是一塊內存區域的地址,在block裡,並沒有改變這個地址,而是讀取出這個地址,然後去操作這塊地址控件的內容。這是允許的,因為聲明的block的時候實際上是把當時的臨時變量又復制了一份,在block裡即使修改這些復制的變量,也不影響外面的原始變量,這就是所謂的閉包。但是當變量是一個指針的時候,block裡只是復制了一份這個指針,兩個指針指向同一個地址。所以在block裡面對指針指向內容做的修改,再block外面也一樣生效。

Blocks默認不能修改相同作用域范圍內的變量,但是如果這個相同作用域的變量如果使用了__block關鍵字進行修飾,則可以通過blocks進行修改。

三、Blocks的實現

1、Block的實質

Block是”帶有自動變量值的匿名函數”。
在實際編譯時無法轉換成我們能夠理解的源代碼,但是clang(LLVM編譯器)具有轉換為我們可讀源代碼的功能。通過”-rewrite-objc”選項就能將含有Block語法的源代碼轉換為C++的源代碼。

首先進入該源文件的絕對路徑;

clang -rewrite-objc 源代碼文件名

我們先來轉換一個簡單的block代碼:

int main() {
    void (^blk)(void) = ^{
        printf("Block\n");
    };
    blk();
    return 0;
}

生成一個block.cpp的文件

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};


int main() {
    void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

可以通過轉換後的源代碼看到,通過blocks使用的匿名函數,實際上被作為簡單的c語言函數來處理。
在轉換後的代碼的命名上,是根據block語法所屬的函數名和該block語法在該函數出現的順序值。

來看block的語法:

^{printf();};

對應的是

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     printf();
}

這裡的__cself相當於c++中的this。

函數參數聲明:struct __main_block_impl_0 *__cself
與c++的this和oc的self相同,參數__cself 是 __main_block_impl_0結構體的指針。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};

這裡是取出構造函數的代碼,
其中impl是__block_impl結構體。

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

Desc是指針,是__main_block_desc_0結構體的。

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

下面初始化含有這些結構體的__main_block_impl_0結構體的構造函數;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

_NSConcreteStackBlock用於初始化__block_impl結構體的isa成員。

main中構造函數是如何調用的:

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

拆分:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;

這樣就容易理解了。 該源代碼在棧上生成__main_block_impl_0結構體實例的指針。我們將棧上生成的__main_block_impl_0結構體實例的指針賦值給__main_block_impl_0結構體指針類型的變量blk;

注意到生成tmp的函數的參數, 第一個參數是由block語法轉換的c語言函數指針,第二個參數是作為靜態全局變量初始化的__main_block_desc_0結構體實例指針。
看一下結構體初始化:
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
這裡使用__main_block_impl_0結構體實例的大小進行初始化。

下面看看棧上的_main_block_impl_0結構體實例(即Block)是如何根據這些參數進行初始化的。如果展開__main_block_impl_0結構體的__block_impl結構體,如下:

struct __main_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0* Desc;
}

該結構體根據構造函數:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

2、__block說明符

Block中所使用的被截獲自動變量就如:帶有自動變量值的匿名函數。 所說的,僅截獲自動變量的值。
Block中使用自動變量後,在Block的結構體實例中重寫該自動變量也不會改變原先截獲的自動變量。
如果在Block中視圖改變自動變量,將引起編譯錯誤。
但是這樣,我們就不能再block中保存值了。
解決方案有兩種方法:
(1)、c語言中有一個變量,允許Block改寫值。具體如下:

靜態變量; 靜態全局變量; 全局變量;

在Block中,訪問靜態全局變量/全局變量沒有什麼改變,可以直接使用。
靜態變量中,轉換後的函數原本就設置在含有block語法的函數外,所以無法從變量作用域訪問。

(2)、 使用__block說明符
__block存儲域類說明符 __block storage-class-specifier

c中的存儲域類說明符:

typedef extern static auto register

__block說明符類似於static、auto和register,它們用於作為靜態變量值設置到那個存儲區域中。例如,auto表示作為自動變量存儲在棧中,static表示作為靜態變量存儲再數據區中。

3、Block存儲域

Block和__block變量的實質就是在棧上的結構體實例。Block轉換為Block的結構體型的自動變量,__block變量轉換為__block變量的結構體類型的自動變量。所謂的結構體類型的自動變量,即棧上生成的該結構體的實例;

名稱 實質 Block 棧上Block的結構體實例 __block變量 棧上__block變量的結構體實例

其中Block也是oc的對象,該OC的類為:_NSConreteStackBlock。

類似的類還有:

(1)、_NSConcreteGlobalBlock:他與全局變量一樣,設置在程序的數據區域(.data區)中; (2)、_NSConcreteStackBlock:它的對象Block設置在棧上; (3)、_NSConcreteMallocBlock:它的對象設置在由malloc函數分配的內存塊中(堆); 類 設置對象的存儲域 _NSConcreteStackBlock 棧 _NSConcreteGlobalBlock 程序的數據區域(.data區) _NSConcreteMallocBlock 堆

這裡寫圖片描述

(1)、_NSConcreteGlobalBlock

void (^blk)(void) = ^{
    printf("Global Block\n");
};

該Block的類為_NSConcreteGlobalBlock類。此Block即該Block用結構體實例設置在程序的數據區域中。因為在使用全局變量的地方不能使用自動變量,所以不存在對自動變量進行截獲。由此Block用結構體實例的內容不依賴執行時的狀態,所以整個程序只需要一個實例。因此將Block用結構體實例設置在於全局變量相同的數據區域中即可。
只在截獲自動變量時,Block用結構體實例截獲的值才會根據執行時的狀態變化。
只要Block不截獲自動變量,就可以將Block用結構體實例設置在程序的數據區域。
在以下情況Block為_NSConcreteGlobalBlock類對象:

1、記述全局變量的地方有Block語法時; 2、Block語法的表達式中不使用截獲的自動變量時;

(2)、_NSConcreteStackBlock

NSArray *testArr = @[@"1", @"2"];
void (^TestBlock)(void) = ^{
    NSLog(@"testArr :%@", testArr);
};

需要截獲自動變量的為_NSConcreteStackBlock類對象,換句話說:
除_NSConcreteGlobalBlock的Block語法生成的Block都為_NSConcreteStackBlock類對象;

(3)、_NSConcreteMallocBlock

1、Block超出變量作用域可存在的原因

分配在棧上的Block和__block變量 其所屬的變量作用域結束,該Block或者__block變量也會被廢棄。
但是Blocks提供了 將Block和__block變量從棧上復制到堆上的方法來解決這個問題, 這樣即使語法記述其作用域結束,堆上的Block也能繼續存在。
如圖:
這裡寫圖片描述
這裡寫圖片描述
此時,賦值到堆上的Block將_NSConcreteMallocBlock類對象寫入Block用結構體實例的成員變量isa;
impl.isa = _NSConcreteMallocBlock;

2、__block變量用結構體成員變量__forwarding存在的原因

上面的情況下只是Block,而__block變量需要用結構體成員變量__fZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcndhcmRpbme/ydLUyrXP1iDO3sLbX19ibG9ja7Hkwb/F5NbD1NrVu8nPu7nKx7bRyc/Ksba8xNy5u9X9yLe12LfDzspfX2Jsb2NrseTBv6GjPGJyIC8+DQrU2s/Cw+bO0sPHu+HLtaO61NpfX2Jsb2NrseTBv8Xk1sPU2rbRyc+1xNe0zKzPwqOs0rK/ydLUt8POytW7yc+1xF9fYmxvY2ux5MG/oaO0y8qxo6zWu9Kq1bvJz7XEveG5uczlyrXA/bPJ1LGx5MG/X19mb3J3YXJkaW5n1rjP8rbRyc+1xL3hubnM5cq1wP2jrLK7udzKx7TT1bvJz7XEX19ibG9ja7Hkwb+7ucrHtNO20cnPtcRfX2Jsb2NrseTBv7a8xNzV/ci3t8POyqGjPC9wPg0KPHA+QmxvY2tzzOG5qbXEuLTWxre9t6ijrMjnus6009W7yc+4tNbGtb220cnPtcSjvyBBUkPT0NCntcTKsbryo6yx4NLrxve/ydLU19S2r8XQts+hozxiciAvPg0KwLS/tEJsb2Nruq/K/aO6PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> typedef int (^blk_t)(int); blk_t func(int rate) { return ^(int count){return rate*count;}; }

此時將會返回配置在棧上的Block的函數。 即 程序執行中,從該函數返回函數調用方時變量作用域結束,因此棧上的Block也被廢棄。 雖然有問題,但是ARC的編譯如下:

blk_t func(int rate) {
    //將通過Block語法生成的Block, 即配置在棧上的Block用結構體實例, 賦值給相當於Block類型的變量tmp中,此處的tmp相當於blk_t __strong tmp;
    blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
    //_Block_copy函數   將棧上的Block復制到堆上,復制後,將堆上的地址作為指針賦值給變量tmp,此處的objc_retainBlock相當於_Block_copy
    tmp = objc_retainBlock(tmp);
    //將堆上的Block作為OC對象,  注冊到autoreleasepool中,然後返回該對象。
    return objc_autoreleaseReturnValue(tmp);
}

將Block作為函數返回值返回時,編譯器會自動生成復制到堆上的代碼。

對於配置在堆上的Block以及配置在程序的數據區域上的Block,調用copy方法如下:

Block的類 副本源的配置存儲域 復制效果 _NSConcreteGlobalBlock 程序的數據區域 什麼也不做 _NSConcreteStackBlock 棧 從棧復制到堆 _NSConcreteMallocBlock 堆 引用計數增加

注意:

①、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],(補:在arc中不用擔心此問題,因為arc中會默認將實例化的block拷貝到堆上)在函數出棧後,從mutableAarry中取到的stackBlock已經被回收,變成了野指針。正確的做法是先將stackBlock copy到堆上,然後加入數組:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之後生成新的NSMallocBlock類型對象。 ⑤、NSMallocBlock支持retain、release,雖然retainCount始終是1,但內存管理器中仍然會增加、減少計數。copy之後不會生成新的對象,只是增加了一次引用,類似retain; ⑥、盡量不要對Block使用retain操作。

4、__block變量存儲區

Block從棧復制到堆上時,對__block變量產生的影響

__block變量的配置存儲域 Block從棧復制到堆時的影響 棧 從棧復制到堆並被Block持有 堆 被Block持有

(1)、如果一個Block中使用__block變量,當該Block從棧復制到堆時,使用的所有__block變量也必定配置在棧上。這些__block變量也全部被從棧復制到堆上。 此時Block持有__block變量。 即使在該Block已復制到堆的情況下,復制Block也對所使用的__block變量沒有任何影響。
(2)、在多個Block中使用__block變量時,因為最先會將所有的Block配置在棧上,所以__block變量也會配置在棧上。在任何一個Block從棧復制到堆時,__block變量也會一並從棧復制到堆並被該Block所持有。當剩下的Block從棧復制到堆時,被復制的Block持有__block變量,並增加__block變量的引用計數;
(3)、如果配置在堆上的Block被棄用,那麼它所使用的__block變量也就被釋放。

5、截獲對象

什麼時候棧上的Block賦值到堆?

調用Block的copy實例方法時; Block作為函數返回值返回時; 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時; 在方法名中有usingBlock的Cocao框架方法或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實例方法。

①、Block作為函數返回值返回時。 ②、將Block賦值給類的附有__strong修飾符的id類型或Block類型成員變量時。 ③、向方法名中含有usingBlock的Cocoa框架方法或Crand Central Dispatch的API中傳遞Block時。

6、__block變量和對象

__block id obj = [[NSObject alloc]init];

clang 轉化為

//__block變量用結構體部分
struct __Block_byref_obj_0 {
     void *__isa;
     __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     __strong id obj;
};

static void __Block_byref_id_object_copy_131(void* dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void**)((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void**)((vhar*)src + 40), 131);
}

//__block變量聲明部分
__Block_byref_obj_0 obj = {
     0,
     &obj,
     0x2000000,
     sizeof(__Block_byref_obj_0),
     __Block_byref_id_object_copy_131,
     __Block_byref_id_object_dispose_131,
     [[NSObject alloc] init]
};

ARC有效時:
當Block從棧復制到堆時,使用_Block_object_assign函數,持有Block截獲的對象。 當堆上的Block被廢棄時,使用_Block_object_dispose函數,釋放Block截獲的對象;

在__block變量為附有__strong修飾符的id類型或對象類型自動變量的情形下會發生同樣的過程。當__block變量從棧復制到堆上,使用_Block_object_assign函數,持有賦值給__block變量的對象。當堆上的__block被廢棄時,使用_Block_object_dispose函數,釋放賦值給__block變量的對象。

由此可見,即使對象復制到堆上的附有__strong修飾符的對象類型__block變量中,只要__block變量在堆上存在,那麼該對象就會繼續處於被持有狀態。這與Block中使用賦值給附有__strong修飾符的對象類型自動變量的對象相同。

7、Block循環引用

ARC有效時:

(1)weak消除循環引用

①、self—-Block

當我們在Block使用__strong修飾符的對象類型自動變量,那麼當Block從棧復制到堆時,該對象為Block所持有。 這樣容易引起循環引用。

typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
    blk_t blk_;
}
@end

@implementation MyObject
- (id)init {
    self = [super init];
    blk_ = ^{NSLog(@"self = %@", self);};
    return self;
}
- (void)dealloc {
    NSLog(@"dealloc");
}
@end
int main() {
    id o = [[MyObject alloc] init];
    NSLog(@"%@", o);
    return 0;
}

源代碼中,MyObject類的dealloc實例方法一直沒有被調用;
MyObject類對象的Block類型成員變量blk_持有賦值為Block的強引用。即MyObject類對象持有Block。init實例方法中執行Block語法使用附有_strong修飾符的id類型變量self。並且由於Block語法賦值在了成員變量blk中,因此通過Block語法生成在棧上的Block此時由棧賦值到堆,並持有所使用的self。self持有Block,Block持有self。這正是循環引用。

為了避免循環引用,可聲明附有__weak修飾符的變量,並將self賦值使用。

- (id)init{
    self = [super init];
    id __weak tmp = self;
    blk_ = ^{NSLog(@"self = %@", tmp);};
    return self;
}
②、成員變量id——Block

下面代碼也會引起循環:

@interface MyObject:NSObject {
     blk_t blk_;
     id obj_;
}
@end

@implementation MyObject
- (id) init {
     self = [super init];
     blk_ = ^{NSLog(@"obj_ = %@", obj_);};
     return self;
}
@end

Block中沒有self,也同樣截獲了self,引起循環。
其實在block語法中使用了obj_,其實就已經截獲了self: self->obj_。
與前面一樣,我們可以聲明__weak的臨時變量來避免循環引用;

(2)、__block消除循環引用

我們還可以使用__block變量來避免循環引用:

typedef void (^blk_t)(void);
@interface MyObject:NSObject{
     blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
     self = [super init];
     __block id tmp = self;
     blk = ^{
          NSLog(@"self = %@", tmp);
          tmp = nil;
     };
     return self;
}
- (void)execBlock{
     blk_();
}
- (void)dealloc{
     NSLog(@"dealloc");
}
@end

int main() {
     id o = [[MyObject alloc] init];
     [o execBlock];
     return 0;
}

這裡並沒有引起循環引用,但是如果不調用execBlock實例方法,即不執行賦值給成員變量blk_的Block,便會循環引用並引起內存洩露。原因如下:

MyObject類對象持有Block; Block持有__block變量; __block變量持有MyObject類對象。;

(3)、__block,__weak,__unsafe_unretained

①、使用__block變量的優點:

通過__block變量可控制對象的持有期間; 在不能使用__weak修飾符的環境中不使用__unsafe_unretained修飾符即可(不必擔心懸垂指針); 在執行Block時可動態地決定是否將nil或其他對象賦值在__block變量中。

②、使用__block變量的缺點

為避免循環引用必須執行Block;
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved