Objective-C Block的剖析

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方法中的定義的blockclang編譯后的 __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;
  }
};

先看下這個結構體的成員變量:

    1. struct __block_impl impl;
    1. struct __XYBlock__method_block_desc_0* Desc;
    1. int multiplier;
    1. __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

  • 使用clangXYBlock.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結構體中發生了什么,以及它的作用。

  • 使用clangXYBlock2.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_objectobjc_class結構體內都具備這一特性,所以我認為被__block修飾的變量不管是基本數據類型還是對象,它都是一個對象。

  • 怎么給__block修飾的變量賦值
    當我們對__block修飾的變量進行賦值操作時,會在block結構體中執行(multiplier.__forwarding->multiplier) = 4;
    也就是說修改-methodmultiplier變量,實際上是對clang編譯后的multiplier這個對象的__forwardingmultiplier進行賦值。

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

推薦閱讀更多精彩內容