Block在開發中常用的,要想解決Block在開發中遇到的問題,我們需要了解Block的本質、截獲變量的特性、__block
修飾符、block
的內存管理和循環引用問題。通過clang
編譯器深度剖析block
底層實現。
為了便于理解使用了較多白話,原文在我的blockObjective-C Block的剖析中,查看demo
通過clang
編譯器剖析block
- 執行編譯
我們主要對XYBlock.m
中-method
方法中的block
進行剖析
以下為-method
方法實現:
@implementation XYBlock
- (void)method {
int multiplier = 6;
int (^block)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", block(2));
}
@end
通過clang
編譯器生成cpp
實現。
clang -rewrite-objc XYBlock.m
執行完成后,在當前目錄下生成同名的.cpp
文件
在XYBlock.cpp
中,我們找到編譯后_I_XYBlock_method
函數就是XYBlock
類中-method
的方法:
static void _I_XYBlock_method(XYBlock * self, SEL _cmd) {
int multiplier = 6;
int (*block)(int) = ((int (*)(int))&__XYBlock__method_block_impl_0((void *)__XYBlock__method_block_func_0, &__XYBlock__method_block_desc_0_DATA, multiplier));
multiplier = 4;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9n_2_q48v8j049bnt5m4k7c_4rh0000gq_T_XYBlock_69f118_mi_0, ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2));
}
- 編譯后結果剖析
-method
方法沒有參數的,所以_I_XYBlock_method
函數的兩個參數為objcetive-c
方法的默認隱士參數(self 和 _cmd選擇器)。
_I_XYBlock_method
函數內:
第一行代碼int multiplier = 6;
與XYBlock
中的-method
方法的第一行保持一致。
第二行代碼int (*block)(int) = ((int (*)(int))&__XYBlock__method_block_impl_0((void *)__XYBlock__method_block_func_0, &__XYBlock__method_block_desc_0_DATA, &multiplier));
為block
編譯后的結果,__XYBlock__method_block_impl_0
是一個結構體,其中第一個參數__XYBlock__method_block_func_0
是一個void *
類型的函數指針,第二個參數為block的描述,第三個參數為傳入block的變量multiplier
第三行代碼主要為block`的調用
編譯后block的結構體 __XYBlock__method_block_impl_0
為了了解block的本質,這里需要先了解XYBlock
中-method
方法中的定義的block
被clang
編譯后的 __XYBlock__method_block_impl_0
這個結構體。
struct __XYBlock__method_block_impl_0 {
struct __block_impl impl; // __block_impl 類型的結構體
struct __XYBlock__method_block_desc_0* Desc; // block的描述
int multiplier; // block 中使用外部的變量,由于block使用的`multiplier`變量為局部變量基本數據類型的變量,所以截獲的是其值
/// 構造函數
/// @param fp void * 類型的函數指針
/// @param desc block的描述
/// @param _multiplier block中使用外部的變量 _multiplier 對應`XYBlock`中`-method`方法中的`int multiplier = 6;`
/// @param flags 標記
__XYBlock__method_block_impl_0(void *fp, struct __XYBlock__method_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
impl.isa = &_NSConcreteStackBlock; // block 的類型
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
先看下這個結構體的成員變量:
struct __block_impl impl;
struct __XYBlock__method_block_desc_0* Desc;
int multiplier;
-
__XYBlock__method_block_impl_0(...): multiplier(_multiplier)
構造函數,該構造函數后面通過multiplier(_multiplier)
將參數_multiplier
賦值給了成員變量multiplier
。該構造函數內初始化了__block_impl
結構體,并將函數指針賦值給了該結構體的成員變量FuncPtr
。
-
__block_impl
結構體
- 聲明
在XYBlock.cpp
中找到__block_impl
結構體的定義
struct __block_impl {
void *isa; // isa 指針,通過這個isa 指針,由于`objc_class`和`objc_object`都具備這一特性,可以把Block理解是一個對象
int Flags; // 標記
int Reserved;
void *FuncPtr; // 是一個無類型的函數指針,在定義block的`{}`中的定義的執行體,最終就會產生這樣一個函數,`block`通過一個指針指向這樣的實現
};
block
的{}
定義的執行體
以下為編譯后產生的函數體實現
/// 根據定義的block生成的函數體實現
/// @param num 定義block時的參數
static int __XYBlock__method_block_func_0(struct __XYBlock__method_block_impl_0 *__cself, int num) {
int multiplier = __cself->multiplier; // block使用的外部變量`multiplier`為局部變量基本數據類型的變量,所以這里可以看到block將使用的外部變量的值copy到函數體內
// block的 {} 內執行的代碼
return num * multiplier;
}
什么是Block
Block
是一個對象,這個對象封裝了函數以及函數執行的上下文。
什么是Block
的調用
Block
的調用就是函數的調用的本質
要想了解為什么Block
的調用就是函數的調用這個問題,我們需要到XYBlock.cpp
(clang編譯XYBlock.m
的結果)文件中,并找到static void _I_XYBlock_method(XYBlock * self, SEL _cmd)
這個函數的實現,上面我們以及介紹過這個函數,它是對XYBlock.m
的-method
方法編譯后的實現:
static void _I_XYBlock_method(XYBlock * self, SEL _cmd) {
int multiplier = 6;
int (*block)(int) = ((int (*)(int))&__XYBlock__method_block_impl_0((void *)__XYBlock__method_block_func_0, &__XYBlock__method_block_desc_0_DATA, multiplier));
multiplier = 4;
// 調用block,實際上就是函數調用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9n_2_q48v8j049bnt5m4k7c_4rh0000gq_T_XYBlock_69f118_mi_0, ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2));
}
下面的函數實現是通過執行__block_impl
結構體中FuncPtr
函數指針執行block
((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2)
首先進行強制類型轉換,然后在執行FuncPtr
時,傳入了兩個參數,一個是block
本身,另一個是2
,實際上執行的__XYBlock__method_block_func_0
函數。
Block
截獲變量特性
要想了解Block
截獲變量的特性,我們先從一道筆試題開始著手:
int multiplier = 6;
int (^block)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", block(2));
答案:result is 12
當然要知道這道題為什么是12
,我們需要了解Block
截獲變量的類型,哈哈。
Block
對以下類型的變量截獲是不一樣的。
關于block的截獲特性你是否了解
- 局部變量
- 基本數據類型
- 對象數據類型
- 靜態局部變量
- 全局變量
- 靜態全局變量
針對不同變量類型,Block
是如何截獲的
- 對于基本數據類型的局部變量,截獲其值
- 對于對象類型的局部變量,連同所有權修飾符一起截獲
- 靜態局部變量以指針形式截獲
- 全局變量和靜態全局變量,
Block
不對其進行截獲
如果要理解什么是所有權一起截獲或者其他截獲的原理,需要查看使用clang
編譯block后的c++文件。
截獲變量的源碼解析
為了驗證以上類型的變量截獲區別,并了解不同變量類型block
是如何截獲的,我們使用clang -rewrite-objc -fobjc-arc xxx.m
編譯block的c++實現,將xxx.m替換后為需要編譯的.m
文件。比之前用的命令多了-fobjc-arc
參數
我們創建XYBlock1
,并在其-method
方法的block中使用以上類型的變量,以驗證block對變量的截獲。
@implementation XYBlock1
// 全局變量
int global_var = 4;
// 靜態全局變量
static int static_global_var = 5;
- (void)method
{
// 基本數據類型的變量
int var = 1;
// 對象類型的局部變量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
// 靜態局部變量
static int static_var = 3;
void (^ block)(void) = ^{
NSLog(@"局部變量<基本數據類型> var is %d", var);
NSLog(@"局部變量<__unsafe_unretained 對象類型> unsafe_obj is %@", unsafe_obj);
NSLog(@"局部變量<__strong 對象類型> strong_obj is %@", strong_obj);
NSLog(@"靜態局部變量 static_var is %d", static_var);
NSLog(@"全局變量 global_var is %d", global_var);
NSLog(@"靜態全局變量 static_global_var is %d", static_global_var);
};
block();
}
@end
- 使用
clang
將XYBlock.m
編譯為c++實現
clang -rewrite-objc -fobjc-arc XYBlock1.m
執行完成后,會生成XYBlock1.cpp
文件。
查看經過編譯之后block對應的結構體:
// 全局變量不會被截獲
int global_var = 4;
// 全局靜態變量不會被截獲
static int static_global_var = 5;
struct __XYBlock1__method_block_impl_0 {
struct __block_impl impl;
struct __XYBlock1__method_block_desc_0* Desc;
// var 是截獲局部變量的值
int var;
// unsafe_obj 是連同所有權修飾符一起截獲
__unsafe_unretained id unsafe_obj;
__strong id strong_obj;
// static_var 是局部靜態變量,截獲其指針
int *static_var;
__XYBlock1__method_block_impl_0(void *fp, struct __XYBlock1__method_block_desc_0 *desc, int _var, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int *_static_var, int flags=0) : var(_var), unsafe_obj(_unsafe_obj), strong_obj(_strong_obj), static_var(_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
通過以上可以結構體,看到在XYBlock1.m
的-method
方法中,block中的使用的外部變量是通過__XYBlock1__method_block_impl_0
結構體的構造函數的參數傳遞給這個block的結構體。
XYBlock1.m
中的變量是以什么樣的形式被block捕獲的:
- 局部基本數據類型的變量
var
的值被賦值給__XYBlock1__method_block_impl_0
這個block結構體的int var;
變量,就是簡單的賦值操作。 - 局部對象類型的
_unsafe_obj
和_strong_obj
本身也是指針,且block結構體中定義的這兩個變量與我們在-method
方法中定義的變量類型是一樣的(分別都是__unsafe_unretained id
和__strong id
), 所有是連同所有權修飾符一起截獲。 - 局部靜態變量
static_var
是將其地址傳遞給int *static_var;
指針變量,將來操作或使用的也是其指針指向內存的值。 - 全局變量和靜態全局變量,在
struct __XYBlock1__method_block_impl_0 {...)
結構體的外部而非此結構體的內部,所以并未對其進行捕獲哦。
當我們理解以上的變量類型在block中捕獲以后,就會對Block
產生循環引用有更好的理解。
__block
修飾符
當被截獲的變量在block內賦值時,需要使用__block
修飾符。
問題:下面兩段代碼,分別定義了array
變量,并在block中操作array
變量,哪段需要__block
修飾array
變量呢?
- 第一段
{
NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
[array addObject:@123];
};
block();
}
- 第二段
{
NSMutableArray *array = nil;
void (^block)(void) = ^{
array = [NSMutableArray array];
};
block();
}
答案:第一段在block內對array
變量的操作只是使用而非賦值,所以不需要__block
修飾。而第二段在block內對array
變量進行了賦值操作,所以需要使用__block
修飾符,不然編譯階段無法通過。
變量賦值時,__block
的特點
- 需要使用
__block
修飾符的變量- 局部變量
- 基本數據類型
- 對象類型
- 局部變量
- 不需要使用
__block
修飾符的變量- 靜態局部變量
- 靜態全局變量
- 全局變量
這與block截獲變量的特性有關,上面我們已經介紹了各種類型變量在block內截獲的區別。
-
靜態局部變量
是以指針的形式被block截獲的,所以在block內操作靜態變量就等于操作外部的變量,所以不需要__block
. -
靜態全局變量
和全局變量
不涉及block截獲,我們是直接對其使用的,所以需要使用__block
。
__block
的作用、機制、原理
創建XYBlock2
類,并在-method
方法中實現以下__block
的例子:
@implementation XYBlock2
- (void)method {
__block int multiplier = 6;
int (^block)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d", block(2));
}
@end
上面的_method
方法中,在block中對外部變量multiplier
和其內部變量num
執行乘法操作,而multiplier
是__block
修飾的整型變量。
輸出結果:result is 8
上面的代碼塊中使用__block
修飾了multiplier
變量,輸出的結果為8,如果不使用__block
修飾則輸出12。這是為什么呢?
這是因為__block
修飾的整型變量變成了對象。
我們可以通過clang
指令查看__block
修飾的變量在block結構體中發生了什么,以及它的作用。
- 使用
clang
將XYBlock2.m
編譯為c++實現進行驗證
clang -rewrite-objc -fobjc-arc XYBlock2.m
在編譯完成后XYBlock2.cpp
文件中,找到原-method
方法編譯后的函數實現_I_XYBlock2_method()
:
static void _I_XYBlock2_method(XYBlock2 * self, SEL _cmd) {
// __block int multiplier = 6; 使用__block修飾的整型變量使用clang編譯后變成了__Block_byref_multiplier_0類型的結構體
__attribute__((__blocks__(byref))) __Block_byref_multiplier_0 multiplier = {(void*)0,(__Block_byref_multiplier_0 *)&multiplier, 0, sizeof(__Block_byref_multiplier_0), 6};
int (*block)(int) = ((int (*)(int))&__XYBlock2__method_block_impl_0((void *)__XYBlock2__method_block_func_0, &__XYBlock2__method_block_desc_0_DATA, (__Block_byref_multiplier_0 *)&multiplier, 570425344));
(multiplier.__forwarding->multiplier) = 4;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9n_2_q48v8j049bnt5m4k7c_4rh0000gq_T_XYBlock2_620611_mi_0, ((int (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2));
}
從_I_XYBlock2_method()
函數內第一行代碼可以得知, 使用__block
修飾的multiplier
整型變量使用clang編譯后變成了__Block_byref_multiplier_0
類型的結構體。
第二行代碼就是multiplier = 4;
編譯后的代碼,最終變成了(multiplier.__forwarding->multiplier) = 4;
,就是找到multiplier
對象的__forwarding
,并對其multiplier
成員變量賦值為4。
-
__block
被clang編譯后的__Block_byref_multiplier_0
結構體
我們找到__Block_byref_multiplier_0
的聲明
struct __Block_byref_multiplier_0 {
void *__isa;
__Block_byref_multiplier_0 *__forwarding; // 指向同類型的指針
int __flags;
int __size;
int multiplier; // block 外部使用`__block`的變量
};
可以看到__Block_byref_multiplier_0
結構體內部有一個isa
指針,由于objc_object
和objc_class
結構體內都具備這一特性,所以我認為被__block
修飾的變量不管是基本數據類型還是對象,它都是一個對象。
- 怎么給
__block
修飾的變量賦值
當我們對__block
修飾的變量進行賦值操作時,會在block結構體中執行(multiplier.__forwarding->multiplier) = 4;
也就是說修改-method
中multiplier
變量,實際上是對clang編譯后的multiplier
這個對象的__forwarding
的multiplier
進行賦值。
我們找到clang編譯后的block結構體__XYBlock2__method_block_impl_0
:
struct __XYBlock2__method_block_impl_0 {
struct __block_impl impl;
struct __XYBlock2__method_block_desc_0* Desc;
__Block_byref_multiplier_0 *multiplier; // by ref
__XYBlock2__method_block_impl_0(void *fp, struct __XYBlock2__method_block_desc_0 *desc, __Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
通過__XYBlock2__method_block_impl_0
結構體的構造函數可以說明,multiplier(_multiplier->__forwarding)
將_multiplier->__forwarding
賦值給了該結構體的multiplier
結構體變量:
__Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {}
當block
在棧上時,__block
結構體中的__forwarding
指向__block
結構體自己
當我們給multiplier
賦值時,實際上是對__block
結構體中的__forwarding
指針,實際上還是這個__block
結構體本身,所有還是對這個__block
結構體的multiplier
成員進行賦值。
當block
在堆上時,__block
結構體中的__forwarding
指向其他地方。
這個問題在下面的【棧上__block
的copy操作】中會講到。
-
__forwarding
是用來做什么的
在棧上的__block
的__forwarding
指向它自己,即使沒有這個變量我們也可以直接對multiplier
進行賦值, 那__block
結構體的__forwarding
指針豈不是多余了。
Block
內存管理
- block都有哪幾種類型
我們在clang編譯后block結構體中,可以看到impl.isa = &_NSConcreteStackBlock;
這一句代碼中的isa
指針就是標記block是哪種類型的。
block有三種類型:_NSConcreteStackBlock
、_NSConcreteGlobalBlock
、_NSConcreteMallocBlock
。
- block在內存中是怎么被分配的
內存分區 | |
---|---|
內核區 | |
棧區(stack) |
_NSConcreteStackBlock 在棧上 |
堆區(heap) |
_NSConcreteMallocBlock 放在堆區 |
未初始化數據區(.bss) | |
已初始化數據區(.data) |
_NSConcreteGlobalBlock 放在已初始化代碼區。 |
代碼段(.txt) | |
保留 |
- block 的copy操作
Block類別 | 源 | Copy結果 |
---|---|---|
_NSConcreteStackBlock |
棧區 | 堆區 |
_NSConcreteMallocBlock |
堆區 | 增加引用計數 |
_NSConcreteGlobalBlock |
數據區 | 什么都不做 |
棧上block的銷毀
假如在棧區有一個__block
變量,并且有一個block,在變量作用域結束后block
和__block
變量都會被銷毀。棧上block的copy
假如在棧上有一個block,并且block中使用了__block
變量(block內需要對外部局部變量進行賦值操作時使用__block
),當對此block進行copy操作時,會在堆區產生一個與棧區一模一樣的block和__block
變量,但是它們是分在兩塊內存空間中,隨著變量作用域的結束棧區block和__block
變量會被銷毀。但是堆區的block和__block
變量仍然存在。
由此就產生一個問題:在MRC
環境下當我們對棧上的block進行copy操作之后是否會引起內存泄露?
答案是肯定的,這與我們手動通過alloc
方法創建一個對象,沒有調用release
的效果是一樣的。
- 棧上
__block
的copy操作
假如在棧上有一個block,并且有__block
變量,這個__block
有一個__forwarding
指針,當block在棧區時,__forwarding
指針指向__block
對象本身。當對棧區的block進行copy之后,在堆區會產生一個完全一致的block和__block
變量。此時棧上的__block
結構體中的__forwarding
指針指向堆區的__block
的__forwarding
,而堆區的__forwarding
指針指向__block
本身。
當我們對multiplier
這樣一個__block
修飾的整型變量修改時,轉換出來的都是同一行代碼
(multiplier.__forwarding->multiplier) = 4;
當我們對棧上的block已經做完copy操作后,實際上我們修改的不是棧上__block
變量的值,而是通過棧上__block
結構體里面的__forwarding
指針找到堆上的__block
變量,然后對堆上的multiplier
進行賦值比如4。
那么同樣的,如果__block
變量由于被成員變量block所持有的話,當我們在另一個方法或者其他的地方調用這個__block
的修改的情況下,那實際上是通過自身的__forwarding
指針來進行修改的。
經過編譯器的編譯,(multiplier.__forwarding->multiplier) = 4;
代碼不管是出現在棧上還是堆區的調用,實際上都是針對堆上的__block
進行修改的。
如果我們沒有對棧上的block進行copy操作,(multiplier.__forwarding->multiplier) = 4;
修改的就是棧上block的__block
變量
__forwarding
的總結
- 先看一道百度的筆試題,這道題實際上是考驗我們對
__forwarding
的理解:
@interface XYBlock3 ()
@property (nonatomic, copy) int(^blk)(int num);
@end
@implementation XYBlock3
- (void)method {
__block int multipliter = 10;
_blk = ^int(int num) {
return num * multipliter;
};
multipliter = 6;
[self executeBlock];
}
- (void)executeBlock {
// 這是百度的筆試的真題,這道題實際上是考驗我們對`__forwarding`的理解
int result = _blk(4);
NSLog(@"result is %d", result);
// 輸出結果 result is 24
}
@end
輸出結果 result is 24
-method
方法中的代碼分析:
第一行代碼__block int multipliter = 10;
初始化了multipliter
是一個__block
修飾的局部變量,那么實際上在clang編譯后,他就變成了對象。所以實際上multipliter = 6;
對multipliter
的賦值不是對這個局部變量的賦值,而是通過multipliter
的__forwarding
指針,然后對其成員變量multipliter
進行賦值。
第二行代碼創建一個block并賦值給_blk
,_blk
是XYBlock3
對象的成員變量,并且屬性修飾符為copy,當對他進行賦值操作時,實際上會對其進行copy,那么_blk
這個block就會在堆區有另一份副本。
第三行代碼multipliter = 6;
代表的含義就是通過棧上的multipliter
的__forwarding
指針找到堆上的所對應的__block
變量,然后對堆上的__block
結構體的multiplier
屬性進行賦值比如6。
在-executeBlock
方法中執行_blk(4)
并傳入參數為4,在block代碼塊內執行了num * multipliter;
,實際上這里使用的multipliter
變量是堆上的__block
變量,所以之后block之后是4乘以6的,結果就是24。
-
__forwarding
存在的意義
無論block在內存的哪個區域,都可以順利的訪問同一個__block
的變量。
Block循環引用的問題
- 先看示例,看下下面的代碼有什么問題
@interface XYBlock4 ()
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, copy) NSString *(^strBlock)(NSString *num);
@end
@implementation XYBlock4
- (void)method {
_array = [NSMutableArray arrayWithObject:@"block"];
_strBlock = ^NSString *(NSString *num) {
// 在此block內使用成員變量`_array`,會產生警告: Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
return [NSString stringWithFormat:@"helloOC_%@", _array[0]];
};
_strBlock(@"hello");
}
@end
分析以上代碼產生循環引用的原因:
以上代碼中,XYBlock4
類有兩個成員變量strong
特性的array
和copy
特性的strBlock
,此時XYBlock4
對象持有了array
變量和strBlock
變量,而在strBlock
的block代碼塊中又持有了XYBlock4
對象的array
成員變量。
這樣會產生循環引用,并且是自循環形式的循環引用,由于XYBlock4
是通過copy關鍵字聲明的strBlock
成員,所以當前對象對這個block是強引用的,而block的表達式中又使用了當前對象的array
成員變量,那么通過block截獲變量的特性,關于block中使用對象類型的局部變量或成員變量,會連同其所有權及關鍵字一同截獲,而array
屬性在當前對象中是使用strong
修飾的,所以在block的結構體中有一個strong類型的指針指向原來的對象或當前對象,由此就產生了一個循環引用。
- 如何解決循環引用
使用__weak
修飾符創建一個弱引用的變量并將_array
賦值給它,在block表達式內使用這個__weak
修飾的變量,即可打破自循環引用
@interface XYBlock4 ()
@property (nonatomic, strong) NSMutableArray *array;
@property (nonatomic, copy) NSString *(^strBlock)(NSString *num);
@end
@implementation XYBlock4
- (void)method {
_array = [NSMutableArray arrayWithObject:@"block"];
// 解決循環引用
__weak NSMutableArray *weakArray = _array;
_strBlock = ^NSString *(NSString *num) {
// 在此block內使用成員變量`_array`,會產生警告: Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
return [NSString stringWithFormat:@"helloOC_%@", weakArray[0]];
};
_strBlock(@"hello");
}
@end
為什么通過__weak
修飾對于的成員變量就可以達到避免循環引用的目的呢?
這個答案實際在講block截獲變量的特性時就給出了答案,由于block對截獲的變量,如果這個變量是對象類型的,連同其所有權修飾符一起截獲,當我們在block使用的外部變量是__weak
修飾符的,那么在block當中所 產生的結構體中的變量也是__weak
修飾的。
__block
引發的循環引用
先看示例,看看以下代碼有什么問題:
@interface XYBlock5 ()
@property (nonatomic, assign) int var;
@property (nonatomic, copy) int (^ blk)(int num);
@end
@implementation XYBlock5
- (void)method {
__block XYBlock5 *blockSelf = self;
_blk = ^int (int num) {
return num * blockSelf.var;
};
_blk(3);
}
@end
- 在MRC環境下,以上代碼不會產生循環引用,沒有任何問題。
- 在ARC下,會產生循環引用,引發內存泄露。
由于原對象持有了block,block持有了__block
變量,而__block
又持有了原對象,導致大環引用,在ARC下可以采用斷環的形式,解決這種循環引用,斷開__block
變量對原對象的持有,就可以規避循環引用。
解決方案:
在block表達式內部加入blockSelf = nil;
的賦值操作,就可以規避循環引用,也就是說當我們調用_blk
之后就會斷開這個環,然后就可以得到內存的釋放和銷毀,這種解決方案有一個弊端,如果這個block未被調用時,這個環就一直存在,導致無法釋放該對象。
@interface XYBlock5 ()
@property (nonatomic, assign) int var;
@property (nonatomic, copy) int (^ blk)(int num);
@end
@implementation XYBlock5
- (void)dealloc {
NSLog(@"%s", __func__);
}
- (void)method {
__block XYBlock5 *blockSelf = self;
_blk = ^int (int num) {
int ret = num * blockSelf.var;
// 在block表達式內部加入`blockSelf = nil;`的賦值操作,就可以規避循環引用
blockSelf = nil;
return ret;
};
_blk(3);
}
@end