深入理解 Block

本文主要根據《Objective-C高級編程》這本書中的第二章來進行的一個總結,其中包含了查看其它文章后的總結和自己的一些理解,關于 block 的一些定義在這里就不說了,這里主要講一下 block 中的截獲自動變量和 __block 關鍵字的實現
文章主要內容如下:

  • 一個普通的 block
  • 截獲自動變量值
  • __block 修飾符
  • 循環引用

一個普通的 block

int main(int argc, char * argv[]) {
    void(^blk)() = ^{
        printf("Hello World");
    };
    blk();
    return 0;
}

一個最普通的 block,內部沒有使用到任何變量,讓我們來看一下它的內部結構,通過 clang -rewrite-objc 命令將上面的代碼解析為一份 C++ 代碼:

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("Hello World");
    }

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, char * argv[]) {
    void(*blk)() = ((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;
}

main 函數

對 main 函數進行分解處理,這樣可以更加直觀的查看

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

分解為

struct __main_block_impl_0 tempBlock = __main_block_impl_0(__main_block_func_0, &_main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tempBlock;

可以看出,在編譯之后 blk 是一個指向一個 __main_block_impl_0 類型結構體的指針,接下來根據函數調用順序進行進一步了解

結構體

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;
  }
};

從上面的 main 函數可以看到,創建的就是這樣一個類型的結構體,我們來看一下這個結構體的內部構造:

  • impl:__block_impl 類型,成員變量之一,內部含有函數指針
  • Desc:__main_block_desc_0 類型指針,成員變量之一,用來描述當前 block 的一些附加信息,例如:結構體的大小
  • __main_block_impl_0 :結構體初始化函數,在結構體中進行成員變量的初始化

接下來我們看一下 impl 所屬結構體的主要構造

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • isa:非常常見的指向類的指針
  • FuncPtr:函數指針,在 main 函數的初始化過程中,可以觀察到是將一個靜態函數 __main_block_func_0 對其賦值

接下來看一下 Desc 指針的結構

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)};
  • reserved:今后升級版本所需要區域
  • Block_size:block 的大小

調用結構體初始化函數

從源碼可以看出,在 main 函數中通過調用結構體的初始化函數來創建一個結構體

__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;
  }

我們可以看出,isa 指針保持所屬類的結構體的實例的指針,這也說明了 block 其實也就是一個 OC 對象,因為其包含了 isa 指針

截獲自動變量值

先來看一段代碼:

int a = 10;
void(^blk)() = ^{
    printf("%d\n", a);
};
a = 20;
blk();//輸出結果:10

我們可以發現,當我們定義一個變量之后,又定義一個 block,并且在 block 內部引用了這個變量,然后在外面修改變量,然后調用 block,得到的變量還是修改前的變量,這是為什么呢?好像是我們定義 block 的時候將使用的變量 copy 了一份然后自己存起來,當調用 block 的時候,使用的變量是我們 copy 的那一份,所以不管外面對變量如何進行修改,都不會影響我們保存的那一份,當然這只是猜想,帶著問題走進源碼看一看.

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;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

        printf("%d\n", a);
    }

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, char * argv[]) {
    int a = 10;
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

對比前面那個普通的 block,我們發現幾個不同點

  • __main_block_impl_0 結構體中多了一個成員變量 a, 并且在初始化函數中多了一個參數 a,這個 a 就是 block 截獲的自動變量值
  • 在靜態函數 __main_block_func_0 使用截獲的自動變量的時候,用到了一個指針 _cself, 這是什么呢?在 main 可以發現這個參數是在進行 block 調用的時候傳入的,傳入的就是 block 本身,所以說在靜態函數中操作的變量也就是在 block 創建的時候的那個成員變量,而不是操作的外部變量,也就是說,block 只是截獲的自動變量的值,而并不是截獲了其地址,所以外部變量在 block 創建之后進行修改在這里也沒什么效果,前面的猜想得證.

這里需要注意,block 只會截獲使用到的變量,而對于沒有使用到的變量是不會進行截獲的.

那是不是所有的變量都是如此呢?我們做測試的是局部變量,但是我們知道,在 C 語言中變量還有全局變量、全局靜態變量和局部靜態變量,我們使用這些來嘗試一下,以下使用局部靜態變量做測試,至于全局變量和全局靜態變量留給讀者自己探索,后面也會進行解釋:

static int a = 10;
void(^blk)() = ^{
    printf("%d\n", a);
};
a = 20;
blk();//輸出20

我們發現輸出變了,怎么回事呢?還是看源碼

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;
  int *a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy

        printf("%d\n", (*a));
    }

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, char * argv[]) {
    static int a = 10;
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

和上面的局部變量作對比,可以發現,在 __main_block_impl_0 結構體中,變成了存儲變量 a 的指針,那么我們訪問的時候訪問的是指針 a 指向的變量,那么就可以解釋輸出為 20 的原因.

__block 修飾符

當我們修改截獲的變量的時候,示例如下:

int a = 10;
void(^blk)() = ^{
    a = 20;//報錯:Variable is not assignable(missing,__block type specifier)
    printf("%d\n", a);
};
blk();

因為在底層實現中,即使在 block 內部對對象進行改變,外部的對象沒有任何改變,原因也就是前面的解釋的自動變量的截獲,所以可能蘋果就在編譯階段就告訴開發者這樣是無效的,也就是直接編譯報錯。
那么,現在可以得出一個結論:被截獲的自動變量值不能直接進行修改,但是,有兩種方法可以解決這個問題:

  • 改變存儲于特殊存儲區域的變量
  • 使用 __block 修飾符

我們先來說一下第一種,在 C 語言中變量一般分為五種,分別是:

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

這里需要去除函數參數這一項,那么寫一個測試代碼,因為前面已經做過自動變量和靜態變量的測試,所以這里只進行全局變量和靜態全局變量的測試,如下:

int globalA = 10;
static int intGlobalA = 20;
int main(int argc, char * argv[]) {
    void(^blk)() = ^{
        globalA += 10;
        intGlobalA += 20;
        printf("%d %d\n", globalA, intGlobalA);//輸出:20 40
    };
    blk();
    return 0;
}

查看輸出,發現可以進行改變,我們來看一下源碼:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
int globalA = 10;
static int intGlobalA = 20;

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) {

        globalA += 10;
        intGlobalA += 20;
        printf("%d %d\n", globalA, intGlobalA);
    }

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, char * argv[]) {
    void(*blk)() = ((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;
}

通過源碼我們發現,block 并未對兩個變量進行截獲,而進行訪問和修改的時候,并未經過 block,也就是并未使用到 __cself 指針,而是直接進行訪問和修改,如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        globalA += 10;
        intGlobalA += 20;
        printf("%d %d\n", globalA, intGlobalA);
    }

至此,可以得出一個結論:

  • 全局變量:可以直接進行訪問,修改
  • 靜態全局變量:可以直接進行訪問,修改
  • 靜態變量:指針引用,可以進行訪問,修改
  • 自動變量:值引用,不可進行修改

然后,我們再來看一下第二種,使用 __block 修飾符,使用 __block 修飾符修飾的變量稱為 block 變量

測試代碼如下:

int main(int argc, char * argv[]) {
    __block int a = 10;
    void(^blk)() = ^{
        a = 20;
        printf("%d\n", a);//輸出:20
    };
    blk();
    printf("%d\n", a);//輸出:20
    return 0;
}

可以發現,在使用 __block 修飾符之后 block 內可以修改自動變量了,同樣的,我們來查看一下源碼的實現:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 20;
        printf("%d\n", (a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    printf("%d\n", (a.__forwarding->a));
    return 0;
}

可以發現,在使用 __block 修飾符之后,源碼中多了很多東西,我們來一個個進行解析

首先,還是從 __main_block_impl_0 這個結構體開始,可以發現,此時的 block 并不是將截獲的 a 直接作為成員變量,而是使用一個結構體 __Block_byref_a_0 類型的同名變量來代替,我們來看一下這個結構體:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

可以看到,這個結構體中含有一個成員變量 a,即是截獲的自動變量,也就是 __block 修飾的自動變量,這里可以看出,當一個自動變量使用 __block 修飾之后,原來的自動變量被生成一個結構體,而結構體中的一個成員變量持有原來的自動變量
在這個結構體中,可以看到一個 __forwarding 指針,這個指針是指向自己的,它的作用就是無論當前 block 是處于棧中,還是堆中,都能夠準確的進行訪問,所以后面就是通過 __forwarding 指針來訪問成員變量 a 的,這里引用《Objective-C 高級編程》中的一張圖片來解釋一下:

取自《Objective-C 高級編程》
  • 最初 block 在棧上,那么 forwarding 指針指向自身的 __block 變量結構體
  • 在 block 被復制到堆上時,會將 forwarding 的值替換為堆上的目標 block 變量用結構體實例的地址,而在堆上的目標 block 變量自己的 forwarding 的值就指向它自己

而在使用變量的時候,如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 20;
        printf("%d\n", (a->__forwarding->a));
    }

可以看出,在調用 block 之后,會生成一個 __Block_byref_a_0 類型的指針指向 block 結 構體中的封裝截獲變量的結構體成員變量,然后通過 __Block_byref_a_0 結構體中的 __forwarding 指向目標 block 變量進行修改

從源碼中我們看到多了兩個函數:

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

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

在 OC 中,C 語言結構體不能含有附有 __strong 修飾符的變量,因為編譯器不知道應該什么時候進行 C 語言結構體的初始化和廢棄操作,不能很好的管理內存,但是 OC 中的運行時庫能夠準確把握 block 從棧復制到堆以及堆上的 block 被廢棄的時機,實際上就是使用了 __main_block_copy_0__main_block_dispose_0 這兩個函數.
其中,_Block_object_assign 相當于 retain 操作,將對象賦值在對象類型的結構體成員變量中。
_Block_object_dispose相當于 release 操作。

調用 copy 函數和 dispose 函數的時機:

函數 調用時機
copy 棧上的 block 復制到堆上時
dispose 堆上的 block 被廢棄時

棧上的 block 復制到堆上的情況

  • 調用 block 的 copy 函數時
    在調用 block 的 copy 實例方法時,如果此時 block 在棧上,那么會被復制到堆上
  • Block 作為函數返回值返回時
    調用 _Block_copy 函數
  • 將 Block 賦值給附有 __strong 修飾符 id 類型的類或者 Block 類型成員變量時
    調用 _Block_copy 函數
  • 方法中含有 usingBlock 的 Cocoa 框架方法或者 GCD 的 API 中傳遞 Block 時
    調用 _Block_copy 函數或者 block 的 copy 實例方法

堆上的 block 被廢棄的情況

  • 堆上的 Block 被釋放后,誰都不再持有Block時調用 dispose 函數

block 的存儲域

前面說到,block 可以看做是一個 OC 對象,從源碼中,我們可以看到 block 中的 isa 指針指向的是 &_NSConcreteStackBlock, 那么就可以說,此時 block 的類為 _NSConcreteStackBlock,和這個類相似的還有:

  • _NSConcreteStackBlock(棧區)
  • _NSConcreteMallocBlock(堆區)
  • _NSConcreteGlobalBlock(數據區)

NSConcreteGlobalBlock

在兩種情況下,block 會存在于數據區,即isa 指針指向_NSConcreteGlobalBlock:

  • 記述全局變量的地方有 block 語法時
    測試代碼:
void(^globalBlk)() = ^{};
int main(int argc, char * argv[]) {
    return 0;
}
  • block 的語法表達式中不使用應該截獲的自動變量時

NSConcreteStackBlock

除了以上兩種情況外生成的 block 語法生成的 block 都是 NSConcreteStackBlock 類對象

NSConcreteMallocBlock

配置在全局變量上的 block,從變量作用域外也可以通過指針安全的使用,但是設置在棧上的 block,如果其所屬的變量作用域結束,該 block 就會被廢棄,由于 __block 變量也是在棧上的,同理,如果其所屬的變量作用域結束,該變量就會被廢棄.

為了解決這個問題,通過將 block 和 __block 變量復制到堆上來解決,這樣即使其所屬的變量作用域結束,堆上的 block 依然存在,復制到堆上的 block 的 isa 變為 _NSConcreteMallocBlock

三種類型 block 被復制之后:

類型 存儲位置 復制之后
NSConcreteGlobalBlock 數據區 什么也不做
NSConcreteStackBlock 棧上 從棧上復制到堆上
NSConcreteMallocBlock 堆上 引用計數增加

在 block 中使用對象類型自動變量時,除以下幾種情況外,推薦手動調用 block 的 copy 方法:

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

當一個 block 被復制到堆上的時候,__block 變量也會被復制到堆上,并且被這個 block 所持有,如果又有 block 被復制到堆上,那么 __block 變量的引用計數會增加,然后如果 block 被廢棄,那么就不再持有 __block 變量.

block 循環引用

如果在 block 中使用附有 __strong 修飾符的對象類型自動變量,那么當 block 從棧上復制到堆上的時候,該對象被 block 所持有,這樣就容易發生循環引用

例如下面這份代碼:

typedef void(^blk)(void);

interface BlockTest : NSObject
{
    blk blk_;
}

@implementation BlockTest

- (instancetype)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"self----------------%@",self);
    };
    return self;
}

@end

上面這份代碼中就出現了循環引用,即BlockTest 類對象持有 block,而在 block 中使用持有 __strong 修飾符的 id 類型變量 self,二者互相持有,導致都無法釋放,這就是循環引用
如下圖所示:

block 循環引用.png

在編譯器進行代碼編譯的時候,可以檢測出這種循環引用,并且發出警告,但是并不是任何情況下都能有警告,這里需要注意

為了避免循環引用,這里有幾種方法,我們一個一個看

__weak 修飾符

即如下代碼:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    blk_ = ^{
        NSLog(@"self---------------%@",weakSelf);
    };
    return self;
}

加上 __weak 修飾符之后,block 不再是持有 self,而是持有對對象的弱引用,也就是 self 強引用 block,而 block 弱引用 self,這樣就不會發生循環引用

__block 修飾符

先看一下如下代碼:

- (instancetype)init
{
    self = [super init];
    __block id tempSelf = self;
    blk_ = ^{
        NSLog(@"self = %@",tempSelf);
        tempSelf = nil;
    };
    return self;
}

- (void)dellocBlk
{
    blk_();
}

在上面的代碼中,乍一看二者還是互相引用,但是在 block 內部執行完畢之后有了一個將 tempSelf 置為 nil 的操作,這樣就是將 self 對 block 的強引用切斷,那么循環引用被破壞,但是這樣有一個缺點,那就是如果不執行 blk_ 這個 block,這個循環引用將會永遠存在,只有執行了這個 block,tempSelf 才會被置為 nil,循環引用才會被破壞,但是這種方案并非沒有優點,很明顯的一個優點就是我們可以依靠這個 block 來控制對象的持有時間,這樣可以控制對象的釋放時間.

Strong-Weak Dance

在解決 block 的循環引用時,除了使用 __weak 修飾符外,還有一個被蘋果稱為:“Strong-Weak Dance” 的方法,那這個是什么呢?先看一下代碼:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    _blk = ^{
        id __strong strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"self---------------%@", strongSelf);
        }
    };
    return self;
}

是不是很奇怪,我們好不容易使用 __weak 消除的強引用,為什么在里面又進行了強引用呢?也就是強引用->弱引用->強引用這個過程的作用是什么?我們先來看一下普通的 __weak 有什么問題

__weak BlockTest *weakSelf = self;
self.completionHandler = ^(NSInteger result) {
    [weakSelf.property removeObserver: weakSelf forKeyPath:@"pathName"];
};

在上面的代碼中,__weak 確實解決了循環引用的問題,但是假設當前 block 在子線程中執行,但是在執行的時候 self 被釋放了,這時候 weakSelf 也被置為 nil,但是我們使用 KVO 來進行移除操作,這時候就會發生崩潰
這時候就需要 Strong-Weak Dance 登場了,還是先來看代碼:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    _blk = ^{
        id __strong strongSelf = weakSelf;
        NSLog(@"self---------------%@", strongSelf);
    };
    return self;
}

在 block 內部對 self 又進行一次強引用之后,self 所指對象的引用計數變為 2 ,即使主線程中的 self 被釋放,那么對象的引用計數變為1,此時對象并沒有銷毀

這時候,可能會有人有問題,在里面對 self 的對象進行了強引用,這樣豈不是又變成了互相持有,循環引用?
這個時候需要注意,block 只有截獲外部變量時,才會引用它。如果是內部新建一個,則沒有任何問題。

但是這樣真的能夠解決多線程下,weakSelf 指向的對象在 block 執行之前被釋放的問題嗎?如果在執行 id __strong strongSelf = weakSelf 之前 weakSelf 指向的對象就已經被釋放了,那么這個語句就沒有任何意義了,在開始的那個 KVO 的例子中,崩潰還是依然會發生的,所以此時的 Strong-Weak Dance 沒有任何作用,所以為了保證安全性,我們可以在 block 內部進行 strongSelf 的 nil 檢測,即:

- (instancetype)init
{
    self = [super init];
    id __weak weakSelf = self;
    _blk = ^{
        id __strong strongSelf = weakSelf;
        if (strongSelf) {
            NSLog(@"self---------------%@", strongSelf);
        }
    };
    return self;
}

參考資料

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

推薦閱讀更多精彩內容