Block源碼解析和深入理解

Block源碼解析和深入理解

Block的本質

Block是”帶有自動變量值的匿名函數”.

我們通過Clang(LLVM編譯器)來將OC的代碼轉換成C++源碼的形式,通過如下命令:

clang -rewrite-objc 源代碼文件名

下面,我們要轉換的Block語法

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
    void (^blk)(void) = ^{
        printf("Block\n");
    };
    blk();
    return 0;
}

該源代碼通過Clang 可變換爲以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
    __block_impl (block)結構體聲明
*/
struct __block_impl {
  void *isa; // isa 指針,指向父類的實例。void * 相當於 id 是個實例。
  int Flags; // 
  int Reserved;
  void *FuncPtr; //函數指針 指向block代碼塊的實現函數
};
/*
    __main_block_impl_0 匿名的block 結構體聲明和實現
*/
struct __main_block_impl_0 {
  struct __block_impl impl;//block 的結構體實例
  struct __main_block_desc_0* Desc; //block des的指針 指向block的詳情
  /*
    __main_block_impl_0 結構體構造函數實現
  */
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; // 初始化 block 實例屬性 isa ,表示該block 是 _NSConcreteStackBlock (棧)類型的代碼塊
    impl.Flags = flags;
    impl.FuncPtr = fp;// block 具體的函數實現指針
    Desc = desc;//desc 指針
  }
};
/*
     匿名block 具體的函數實現
*/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block\n");
    }
/*
    匿名block desc 指針的具體函數實現,對block(__main_block_impl_0) 結構體實例的大小進行初始化
*/
static struct __main_block_desc_0 {
  size_t reserved; // 升級所需區域
  size_t Block_size;//block 實際內存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

/*
    把多餘的轉換去掉,看起來就比較清楚了:
    第一部分:block的初始化
    __main_block_func_0: 參數一 是block語法轉換的C語言函數指針。
    __main_block_desc_0_DATA: 參數二 作爲靜態全局變量初始化的 __main_block_desc_0 結構體實例指針
    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;
    第二部分:
    block的執行: blk()
    去掉轉化部分:
       (*blk -> imp.FuncPtr)(blk);
    這就是簡單地使用函數指針調用函數。由Block語法轉換的 __main_block_func_0 函數的指針被賦值成員變量FuncPtr中,另外 __main_block_func_0的函數的參數 __cself 指向Block的值,通過源碼可以看出 Block 正式作爲參數進行傳遞的。
*/
int main(int argc, const char * argv[]) {
    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;
}

針對源碼的解釋 大部分在代碼中都註釋了。需要特別指出的是:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 

中的參數 cself 是指向 main_block_impl_0 的指針,及匿名block 自身。
擴展:該句源碼類似如 OC 中的方法消息傳遞,OC中每個方法都默認帶兩個參數 一個是指向自身的實例self 一個是該方法的SEL 對象。
例如:

1
2
3
- (void) method: (int)argc{
    NLog(@"%p %d \n",self,arg)
}

Objective - C 編譯器同C++的方法一樣,也將該方法作爲C語言的函數來處理.源碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
    方法中 在轉換成源碼後 自動的添加了self, _cmd兩個參數 
*/
    void _I_MyObjct_method_(struct Myobject *self,SEL _cmd, int arg){
        NSLog (@"%p %d \n",self,arg);
    }
``` 

#### 截獲自動變量值(局部變量)

```objc 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int val; //局部變量跟block外的類型一直
  const char *fmt; //跟block外的類型一致
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _dmy, int _val, const char *_fmt, int flags=0) : dmy(_dmy), val(_val), fmt(_fmt) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy //block 調用外部的局部變量 實際上 相當於Copy 了一份 所以不會影響 局部變量的值 也不能修改值 
  const char *fmt = __cself->fmt; // bound by copy

        printf("Block\n .. ,%d %s",dmy,val,fmt);
    }

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(int argc, const char * argv[]) {
    int dmy = 256; //局部變量 
    int val = 10; // 局部變量
    const char *fmt = "val = %d \n"; //局部變量
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, fmt));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

源碼解析:block 在調用 外部局部變量的時候 其實是將外部局部變量 copy了一份 使用的 所以在沒有任何修飾符的時候是不可以修改外部局部變量的。

__block 說明符

之前的分析中,block 無法改變被截獲的自動變量的值。這樣極爲不便:
解決這個問題有兩種方法,
第一種:C 語言中有一個變量,允許block改成值。具體如下:

  • 靜態變量
  • 靜態全局變量
  • 全局變量

    雖然Block語法的匿名函數部分簡單的轉換爲了C語言函數,但從這個C語言函數中訪問靜態全局,全局變量並沒有任何改變,可直接使用。
    但靜態變量的情況,轉換後的函數原本就設置在含有Block語法的函數外,所以無法從變量作用域訪問。
    看看這段代碼的源碼:

     int global_val = 1;
    
     static int static_global_val = 2;
    
     int main(int argc, const char * argv[]) {
    
    static int static_val = 3;
    
    void (^blk)(void) = ^{
    
        global_val += 1;
    
        static_global_val += 2;
    
        static_val += 3;
    
};

blk();

return 0;

}

該源代碼中使用了Block 改寫靜態變量 靜態全局變量 全局變量。該源代碼轉換後如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int global_val = 1; //全局變量
 static int static_global_val = 2; //靜態全局變量
			
 struct __main_block_impl_0 {
		  struct __block_impl impl;
		  struct __main_block_desc_0* Desc;
		  int *static_val;//局部靜態變量   --->  可以看出 跟局部變量不同 這邊是接受的指針
		  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
		    impl.isa = &_NSConcreteStackBlock;
		    impl.Flags = flags;
		    impl.FuncPtr = fp;
		    Desc = desc;
		  }
		};
		static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
		  int *static_val = __cself->static_val; // bound by copy // 改代碼跟局部變量 相似,實際上改變的是一個 複製後的指針.但該指針最終指向的 還是最初的變量值。
		
		        global_val += 1;
		        static_global_val += 2;
		        (*static_val) += 3;
		
		    }
		
		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(int argc, const char * argv[]) {
		    static int static_val = 3;
		    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
		    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
		    return 0;
		}

分析該源碼:發現無論是全局 還是 靜態全局 都可以在Block中直接訪問 修改變量值。

然而,靜態局部變量,貌似也可以正常訪問,其調用原理,跟之前的局部變量的調用相似,唯一的不同是,在Block中調用的是 指向該變量的指針,並且是賦值了一份指針(但還是最終指向原來的變量)。所以我們可以在Block中改變原理變量的值。
這樣就有個疑問,我們爲什麼不使用靜態局部變量,來使用去自動變量(局部變量)的訪問呢?
原因:在該靜態局部變量,有變量作用域,當block超出了該作用域,執行的時候,其內部調用的靜態局部變量會被廢棄,我們就無法調用到。因此Block中超出變量作用域而存在的變量同靜態變量一樣,將不能通過指針訪問原來的自動變量。

解決Block 中不能保存值這一問題的第二個方法是使用__block

1
2
3
4
5
6
7
8
int main(int argc, const char * argv[]) {
    __block int val = 3;
    void (^blk)(void) = ^{
        val = 1;
    };
    blk();
    return 0;
}

將上面代碼用 clang 轉化後如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
	__block 轉化成了結構體 
*/
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; // block 爲棧類型
    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 ;類似於 靜態局部變量 都是copy 一份指向源變量的結構體指針。

        (val->__forwarding->val) = 1;//通過訪問 __block 結構體 成員變量 __forwarding 來訪問源變量
    }
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(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

源碼解析:Block_byref_val_0 結構體實例的成員變量forwarding持有指向該實例自身的指針。通過成員變量__forwarding訪問成員變量val。(成員變量val是該實例自身持有的變量,它相當於原自動變量)
如圖所示:

Block存儲域
Block 是Objective-C對象。上面我們所創建的block類 都爲_NSConcreteStackBlock.
由上面我們提到的源碼可以知道:

1
impl.isa = &_NSConcreteStackBlock

根據 block 結構體實例的 isa 指針進行分類:

  • _NSConcreteStackBlock //不難看出 其存儲域在棧上
  • _NSConcreteGlobalBlock // 其存儲域 在全局
  • _NSConcreteMallocBlock // 其存儲域 在堆上
    詳細分類如圖所示:

_NSConcreteGlobalBlock: 存在的情況:

  • 記述全局變量的地方有Block語法時
  • Block語法的表達式中不使用應截獲的自動變量時
  • 以上情況Block 爲 全局類對象。除此之外Block語法生成的Block爲棧類對象,
    例如(一):
1
2
3
4
5
6
7
8
9
/*
	在下面的block中由於for循環的值 一直在變 所以Block截獲的局部變量一直在變。
*/
	typedef int (^blk_t)(int);
	for (int rate = 0;rate < 10; ++rate){
		blk_t blk = ^(int count){
			return rate * count;
		}
	}

轉化爲源碼如下:

1
2
3
4
5
6
7
8
9
10
11
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int rate;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _rate, int flags=0) : rate(_rate) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

由此可見 雖然block 聲明在全局中,但由於block初始化的時候調用了局部變量,所以該block創建成棧類型的。
_NSConcreteMallocBlock :存在的情況
在分析之前我們看下之前遺留的問題:

  • Block 超出變量作用域可存在的原因
  • block變量用結構體成員變量forwarding存在的原因

配置在全局變量上的Block,從變量作用域外也可以通過指針安全的使用。但設置在棧上的Blcok,如果其變量作用域結束,該Block就被廢棄,同樣的block也配置在棧上,所以其所屬的變量作用域結束,則該block變量也會被廢棄。
Block提供了將Block和block變量從棧上覆制到堆上的方法來解決這個問題

block 變量用結構體成員變量forwarding可以實現無論block變量配置在棧上還是堆上都能夠正確的訪問__block變量。

深入理解blocks提供的複製方法究竟是啥?

實際上當ARC有效時,編譯器會進行判斷自動的將block從棧上覆制到堆上
如:

1
2
3
4
5
6
typedef int (^blk_t)(int);
	blk_t func (int count){
		return ^(int count){
			return rate *count;
		};
	}

源碼轉換爲:

1
2
3
4
5
6
7
8
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);
}

分析源碼:從源碼來看 在ARC狀態下 block複製到堆上 實際上其引用計數增加了。

__block變量的存儲域

當block從棧中 複製到堆上時,由於block持有block變量,所以其blcok變量也會從棧中複製到堆上,所以當block超出作用域調用block變量也可以成功。這是和靜態局部變量最大的區別。而靜態局部變量,在block從棧中複製到堆上時,由於block不持有變量,所以靜態局部變量不 會複製到堆上,其作用域沒變。故出作用域調用會崩潰。
如圖所示:
![](http://7xsugd.com2.z0.glb.clouddn.com/runningyoungBlog/images/
block持有.png)

截獲對象

下面我們將id對象類型的局部變量 在block中調用。id類型的對象 默認修飾符 都是__strong類型的。

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef void (^blk_t)(id);
blk_t blk;
int main(int argc, const char * argv[]) {
    {
        id array = [[NSMutableArray alloc]init]; // __strong 類型修改的局部變量
        blk = [^(id objc){
            [array addObject:objc];
            NSLog(@"array count = %ld",[array count]);
        } copy];
    }
    blk(@"ww");
    return 0;
}

分析 :按理來說 array 對象出了大括號作用域,強引用失效 其對象就會廢棄。但改代碼運行正常。那麼就意味着,array對象出大括號作用域時,沒有被廢棄 ,仍能正常訪問。那麼是什麼原因呢,我們看下Clang之後的源碼.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
typedef void (*blk_t)(id);
blk_t blk;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _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 objc) {
  id array = __cself->array; // bound by copy //複製一份指針 賦值

            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)objc);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_0b_9hq6xqxs5gjcxx5j_skhh8n00000gn_T_main_1808b3_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
        /*
        		關鍵方法:該方法 相當於ARC 中的 retain方法,將對象的引用計數加一。但該方法除引用計數加一外,還有一個操作就是將block 從棧上覆制到堆上,從而可以出作用域,調用id __strong修飾類型的對象。
        */
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
/*
		dispose 相當於ARC 模式下的 release 將對象的引用計數減一。引用計數減一得同時,將堆上的block 廢棄掉。
*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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(int argc, const char * argv[]) {
    {
        id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));///必須調用block 的copy 方法才能正常運行
    }
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, (NSString *)&__NSConstantStringImpl__var_folders_0b_9hq6xqxs5gjcxx5j_skhh8n00000gn_T_main_1808b3_mi_1);
    return 0;
}

//從上面的源碼可以發現:前提:當block調用copy方法,從棧中複製到對象,當Block調用的局部變量是個id對象的時候,該對象在block中自動的引用計數加一,並且該block持有該對象,也就是說,對象出了作用域也能被調用,知道block 從堆上廢棄掉爲止。如果block 的最後沒有調用copy,那麼該對象值,也會隨着作用域的結束而被廢棄。
總結:

什麼時候棧上的Block會複製到堆上呢?

  • 調用Block的copy實例方法時。
  • Block作爲函數返回值返回時。
  • 將Block賦值給附有__strong修飾符id類型的類或者Block類型成員變量時。
  • 在方法名中含有usingBlock的cocoa框架方法或者GCD的API中傳遞Block時。

對象和__block的區別?

  • 如果調用對象的Block,沒有調用Copy 或者不在棧上,那麼該對象出作用域就會被釋放。
  • 如果調用對象的Block,調用了Copy,或者Block在堆上,那麼該對象的作用域跟使用__block修飾的變量的作用域一直,都會被Block所持有,並且生命週期,會隨着Block的廢除,而釋放。

因此當Block中使用對象類型的自動變量時,除以下情形外,推薦調用Block的copy實例方法!!

  • block作爲函數返回值返回時。
  • Block賦值給類的附加__strong修飾符的id類型或者Block類型的成員變量時。
  • 向方法名中含有usingBlock的Cocoa框架方法或者GCD的API中傳遞Block時。

__block變量和對象

從前面我們看到__block可以修飾任意類型:

  • 當然包括id對象strong類型了,其原理是相同的:
    當 block 從棧上覆制到 堆上時,
    block 所修飾的自動變量也會從棧上覆制到堆上,使用_Block_objct_assign函數,持有賦值給block變量的對象。當 block 廢棄時,block所修飾的自動變量,也會通過函數_Block_objct_dispose ,釋放掉__block變量的對象。
  • weak修飾符修飾時,由於weak修飾的自動變量出作用域後會廢棄 自動置nil,所以當block調用的時候,其實是調用的nil對象所以不會崩潰,但取不到值。
  • block weak 同時修飾自動變量時,還是因爲weak(不持有對象)的原因,當 block 從棧上覆制到堆上時,block變量複製到堆上的是一個nil值,所以對該變量進行的操作都是無效的。
  • block 和 unsafeunretained 同時修飾變量時,跟weak不同,當unsafeunretained,所修飾的對象邊nil 時 該變量不會自動置nil,而是變成野指針,所以當block 從棧上覆制到堆上時,實際上__block變量是一個野指針,所以當調用的時候回出錯,導致程序崩潰
  • block 和 autoreleasing 修飾跟 上面的unsafeunretained是一樣的。

Block 循環引用

存在循環引用的情況:當block對象 作爲類的 屬性或者成員變量,並且在block初始化的時候,調用了self或者self相關類的成員變量。都會引起引用循環。

解決方法:

  • 使用__weak 修飾要截取的自動變量,
  • 當在MRC 中時,可以使用__unsafe_unretained(弊端 不會自動置nil 容易出現野指針) 修飾。
  • 可以使用block 修飾,前提是 必須 執行block代碼塊,而且可以適當地在代碼塊中 手動的把block變量置nil
    以下是相關解決方法的實例:
    實例一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
	typedef void (^blk_t)(void);
	@interface Myobject : NSObject
	{
		blk_t blk_; //成員變量
		id _objc;//成員變量
	}
	@end
	@implementation MyObject
	- (id)init
	{
		self = [super init];
		/*
			分析改代碼會出現兩種情況的引用循環:
			 * 一種是:成員變量block 調用 self,self中持有block ,block中也持有self,導致引用循環,解決方法在之前 加入
			 __weak typeof(self) weakSelf = self;
			 * 第二中,雖然成員變量block沒有直接調用self ,但其調用了成員變量_objc,所以也會造成引用循環:
			 解決方法: __weak id weakObjc = _objc;
		*/
		blk_ = ^{
			NSLog(@"self = %@, objc = %@",self,_objc);
		}	
		return self;
	}
``` 
實例二:

``` objc
	typedef void (^blk_t)(void);
	@interface Myobject : NSObject
	{
		blk_t blk_; //成員變量
	}
	@end
	@implementation MyObject
	- (id)init
	{
		self = [super init];
		/*
			此處使用__block修飾變量,是的block 持有__block變量,而__block變量持有MyObject對象,而MyObject持有block對象。出現引用循環:
			然而 當 block執行的時候,__block變量廢棄,從而消除引用循環
		*/
	__block id temp = self;
		blk_ = ^{
			NSLog(@"self = %@,,self);
			temp = nil;
		}	
		return self;
	}
	- (void)execBlock
	{
		blk_()
	 }
	 int main (){
		id o = [[MyObject alloc] init];
		[o execBlock];//必須執行 否則導致引用循環
		return 0;	 
	 }

總結下block 和 weak 之間的優缺點:
使用__block變量的優點:

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

使用__block變量的缺點如下:

  • 爲避免循環引用必須執行Block
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章