探索Block內部構造

1. Block的底層結構

以下是一個沒有參數和返回值的最簡單的Block:

#include <stdio.h>
int main () {
    void(^blk)() = ^{
        printf("Block");
    };
    blk();
    return 0;
}

為了探索Block的底層結構,需要將main.m文件轉化為C++的源碼,打開終端命令行。cd到包含main.m文件的文件夾,然后輸入:clang -rewrite-objc main.m(以上內容放在main.m內),這個時候在該文件夾的目錄下會生成main.cpp文件。

(如果執行:clang -rewrite-objc main.m報錯,首先cd到該文件的目錄,然后
在終端將clang -rewrite-objc main.m的命令替換成為:

$ clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
然后, 你會發現, 你的文件夾中多了一個.cpp的文件, 證明解決了這個問題。
如果你覺得這個命令很繁瑣不易記, 你可以采用 alias來起一個別名來代替這個命令。

1.打開終端, 鍵入命令 vim ~/.bash_profile
2.在vim界面輸入i進入編輯編輯狀態并且鍵入:
alias rewriteoc=’clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk’
3.鍵入完畢后, esc退出編輯狀態, 再鍵入:wq退出vim并保存
4.鍵入命令source ~/.bash_profile)

好了接下來繼續探索block
這個文件非常長,我們直接拉到文件的最下面會看到這些函數:
1.__main_block_impl_0
2.__main_block_func_0
3.__main_block_desc_0
4.main

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

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 (*)())&__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;
}

這幾個函數名不是一直這樣的,在不同的文件中會有不同的函數前綴,如在ViewController.m的viewDidLoad方法中顯示是以下這樣的,但是函數內容是一樣的

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int a;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v8_yfvtg6s17zq2xn62sgjsf6b00000gn_T_ViewController_d37495_mi_0, a);
    }

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));


    int a = 1100;
    void (*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}

文件靠上部分還有這樣一個結構體:__block_impl

#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

首先分析main函數:

int main () {
    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。這兩行代碼看起來非常復雜。但是我們可以去簡化一下,怎么簡化呢?
變量前面的()一般是做強制類型轉換的,比如在調用block這一行,block前面有一個()是(__block_impl *),這就是進行了一個強制類型轉換,將其轉換為一個_block_impl類型的結構體指針,那像這樣的強制類型轉換非常妨礙我們理解代碼,我們可以暫時將這些強制類型轉換去掉,這樣可以幫助我們理解代碼。

化簡后的代碼如下:

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

以上內容看著應該就比較清楚了。
接下來逐行分析,首先第一行:

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

這句代碼的意思就是調用_main_block_impl_0這個函數,給這個函數傳進兩個參數_main_block_func_0和&_main_block_desc_0_DATA,然后得到這個函數的返回值,取函數返回值的地址,賦值給blk這個指針。

接下來看函數:__main_block_impl_0

這是一個C++的結構體。而且在這個結構體內還包含一個函數,這個函數的函數名和結構體名稱一致,這在C語言中是沒有的,這是C++特有的。

在C++的結構體包含的函數稱為結構體的構造函數,它就相當于是OC中的init方法,用來初始化結構體。OC中的init方法返回的是對象本身,C++的結構體中的構造方法返回的也是結構體對象。

那么我們就知道了,__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);返回的就是_main_block_impl_0這個結構體對象,然后取結構體對象的地址賦值給blk指針。換句話說,blk指向的就是初始化后的_main_block_impl_0結構體對象。
我們再看一下初始化_main_block_impl_0結構體傳進去的參數:

第一個參數是_main_block_func_0,定義如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("Block");
    }

這個函數其實就是把我們Block中要執行的代碼封裝到這個函數內部了。我們可以看到這個函數內部就一行代碼,就是一個printf函數;這句代碼。
把這個函數指針傳給_main_block_impl_0的構造函數的第一個參數,然后用這個函數指針去初始化_main_block_impl_0這個結構體的第一個成員變量impl的成員變量FuncPtr。也就是說FuncPtr這個指針指向_main_block_func_0這個函數。

第二個參數是&_main_block_desc_0_DATA。

我們看一下這個結構:

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

在結構體的構造函數中,0賦值給了reserved,sizeof(struct __main_block_impl_0)是賦值給了Block_size,可以看出這個結構體存放的是_main_block_impl_0這個結構體的信息。在_main_block_impl_0的構造函數中我們可以看到,_main_block_desc_0這個結構體的地址被賦值給了_main_block_impl_0的第二個成員變量Desc這個結構體指針。也就是說Desc這個結構體指針指向_main_block_desc_0_DATA這個結構體。
那么我們總結一下:

void (* blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
blockblock->FuncPtr(blk);

1.創建一個函數_main_block_func_0,這個函數的作用就是將我們block中要執行的代碼封裝到函數內部,方便調用。
2.創建一個結構體_main_block_desc_0,這個結構體中主要包括 _main_block_impl_ 這個結構體占用的存儲空間大小等信息。
3.將1中創建的_main_block_func_0這個函數的地址,和2中創建的_main_block_desc_0這個結構體的地址傳給_main_block_impl_0的構造函數。
4.利用_main_block_func_0初始化_main_block_impl_0結構體的第一個成員變量impl的成員變量FuncPtr。這樣_main_bck_impl_0這個結構體也就得到了block中那個代碼塊的地址。
5.利用_mian_block_desc_0_DATA去初始化_mian_block_impl_0的第二個成員變量Desc。

下面我們再看第二步調用block:

blk->FuncPtr(blk);

我們知道,block實質上就是指向_main_block_impl_0這個結構體的指針,而FuncPtr是_main_block_impl_0的第一個成員變量impl的成員變量,正常來講,block想要調用自己的成員變量的成員變量的成員變量,應該像下面這樣調用:

blk->impl->FuncPtr

然而事實卻不是這樣,這是為什么呢?

原因就在于之前我們把所有的強制類型轉換給刪掉了,之前block前面的()是(__block_impl *),為什么可以這樣強制轉換呢?因為block指向的是_main_block_impl_0這個結構體的首地址,而_main_block_impl_0 的第一個成員變量是struct __block_impl impl;,所以impl和_main_block_impl_0的首地址是一樣的,因此指向_main_block_impl_0的首地址的指針也就可以被強制轉換為指向impl的首地址的指針。

之前說過,FuncPtr這個指針在構造函數中是被初始化為指向_mian_block_func_0這個函數的地址。因此通過blk->FuncPtr調用也就獲取了_main_block_func_0這個函數的地址,然后對_main_block_func_0進行調用,也就是執行block中的代碼了。這中間block又被當做參數傳進了_main_block_func_0這個函數。
現在還沒有用到static void __main_block_func_0(struct __main_block_impl_0 *__cself)函數里邊的參數,接下來就要用到了

2.變量捕獲-auto變量

auto變量是聲明在函數內部的變量,比如int a = 0;這句代碼聲明在函數內部,那a就是auto變量,等價于auto int a = 0;auto變量時分配在棧區,當超出作用域時,其占用的內存會被系統自動銷毀并生成。下面看一段代碼:

int main () {
 int a = 10;
 void (^block)(void) = ^{
     printf("%d", a);
 };
 a = 20;
 block();
}

這是一個很簡單的Block捕獲自動變量的例子,我們看一下打印結果:

2019-02-19 09:18:14.088934+0800 Test[761:36119] 10

自動變量a的值明明已經變為了20,為什么輸出結果還是10呢?我們把這段代碼轉化為C++的源碼看看。

int main () {
    int a = 10;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

對比一下上面分析的沒有捕獲自動變量的源代碼,我們發現這里_main_block_impl_0中傳入的參數多了一個a。然后我們往上翻看看_main_block_impl_0的結構:

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

在_main_block_impl_0這個結構體中我們發現多了一個int類型的成員變量a,在結構體的構造函數中多了一個參數int _a,并且用這個int _a去初始化成員變量a。
所以在void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);中傳入了自動變量a用來初始化_main_block_impl_0的成員變量a。那這個時候_main_block_impl_0的成員變量a就被賦值為10了。
由于上面這一步是值傳遞,所以當執行a = 20時,_main_block_impl_0結構體的成員變量a的值是不會隨之改變的,仍然是10。
然后我們再來看一下_main_block_func_0的結構有何變化:

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

        printf("%d", a);
    }

可以看到__main_block_func_0函數中傳進來的block使用上了,他就是_cself,里邊包含block_impl_0的成員變量,所以這里可以打印a

3.變量捕獲-static變量

上面講的捕獲的是自動變量,在函數內部聲明的變量默認為自動變量,即默認用auto修飾。那么如果在函數內部聲明的變量用static修飾,又會帶來哪些不同呢?static變量和auto變量的不同之處在于變量的內存的回收時機。auto變量在其作用域結束時就會被系統自動回收,而static變量在變量的作用域結束時并不會被系統自動回收。
先看一段代碼:

int main () {
    static int a = 10;
    void (^block)(void) = ^{
        printf("%d", a);
    };
    a = 20;
    block();
}

我們看一下打印結果:

2019-02-19 09:18:14.088934+0800 Test[761:36119] 20

結果是20,這個和2中的打印結果不一樣,為什么局部變量從auto變成了static結果會不一樣呢?我們還是從源碼來分析:

int main () {
    static int a = 10;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

和2不一樣的是,這里傳入_main_block_impl_0的是&a,也即是a這個變量的地址值。那么這個&a是賦值給誰了呢?我們上翻找到_main_block_impl_0的結構:

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

這里我們可以看到結構體多了一個指針類型的成員變量int *a,然后在構造函數中,將傳遞過來的&a,賦值給這個指針變量。也就是說,在_main_block_impl_0這個結構體中多了一個成員變量,這個成員變量是指針,指向a這個變量。所以當a變量的值發生變化時,能夠得到最新的值。

4.變量捕獲-全局變量

2和3分析了兩種類型的局部變量,auto局部變量和static局部變量。這一部分則分析全局變量。全局變量會不會像局部變量一樣被block所捕獲呢?我們還是看一下實例:

#include <stdio.h>
static int a = 10;
int main () {
    void (^block)(void) = ^{
        printf("%d", a);
    };
    a = 20;
    block();
}

打印結果:

2019-02-19 09:18:14.088934+0800 Test[761:36119] 20

我們還是查看一下源碼:

static int a = 10;

int main () {
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    a = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

這里我們可以看到,a這個全局變量沒有作為參數傳入_main_block_impl_0中去。然后我們再查看一下_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_block_impl_0中并沒有增加成員變量。然后我們再看_main_block_func_0的結構:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("%d", a);
    }

可以看到,這個地方在調用的時候是直接調用的全局變量a。
所以我們可以得出結論,block并不會捕獲全局變量。
總結:

變量類型 是否捕獲到block內部 訪問方式
局部變量auto 值傳遞
局部變量static 指針傳遞
全局變量 直接訪問

思考 為什么對于不同類型的變量,block的處理方式不同呢?
這是由變量的生命周期決定的。對于自動變量,當作用域結束時,會被系統自動回收,而block很可能是在超出自動變量作用域的時候去執行,如果之前沒有捕獲自動變量,那么后面執行的時候,自動變量已經被回收了,得不到正確的值。對于static局部變量,它的生命周期不會因為作用域結束而結束,所以block只需要捕獲這個變量的地址,在執行的時候通過這個地址去獲取變量的值,這樣可以獲得變量的最新的值。而對于全局變量,在任何位置都可以直接讀取變量的值。

5.變量捕獲-self變量

直接上代碼:

@implementation Person

- (void)test{
    void(^block)(void) = ^{
        NSLog(@"%@", self);
    };
    
    block();
}

@end

這個Person類中只有一個東西,就是test這個函數,那么這個block有沒有捕獲self變量呢?
要搞清這個問題,我們只需要知道搞清楚這里self變量是局部變量還是全局變量,如果是局部變量,那么是一定會捕獲的,而如果是全局變量,則一定不會被捕獲。
我們把這個Person.m文件轉化為c++的源碼,然后找到test函數在c++中的表示:

static void _I_Person_test(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

我們可以看到,本來Person.m中,這個test函數我是沒有傳任何參數的,但是轉化為c++的代碼后,這里傳入了兩個參數,一個是self參數,一個是_cmd。self很常見,_cmd表示test函數本身。所以我們就很清楚了,self是作為參數傳進來,也就是局部變量,那么block應該是捕獲了self變量,事實是不是這樣呢?我們只需要查看一下_Person_test_block_impl_0的結構就可以知道了。
_Person_test_block_impl_0的結構:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
可以看到,self確實是作為成員變量被捕獲了。

6.Block的類型

前面已經說過了,Block的本質就是一個OC對象,既然它是OC對象,那么它就有類型。
在搞清楚Block的類型之前,先把ARC關掉,因為ARC幫我們做了太多的事,不方便我們觀察結果。關掉ARC的方法在Build Settings里面搜索Objective-C Automatic Reference Counting,把這一項置為NO。

5-1MRC捕獲變量
int b = 10;
- (void)test{
    int a = 10;
    void (^block)(void) = ^{
        NSLog(@"%d %d", b, a);
    };
    NSLog(@"\n %@\n %@\n %@", [[block class] superclass], [[[block class] superclass] superclass], [[[[block class] superclass] superclass] superclass]);
}

上面的代碼的打印結果是:

 __NSStackBlock
 NSBlock
 NSObject

這說明上面定義的這個block的類型是NSStackBlock,并且它最終繼承自NSObject,也說明Block的本質是OC對象。

5-2MRC不捕獲變量

再看一下沒有捕獲局部變量的block

- (void)test{
    void (^block)(void) = ^{
        NSLog(@"test");
    };
    NSLog(@"\n %@\n %@\n %@", [[block class] superclass], [[[block class] superclass] superclass], [[[[block class] superclass] superclass] superclass]);
}

上面的代碼的打印結果是:

 __NSGlobalBlock
 NSBlock
 NSObject

接下來切換回ARC模式,再次打印以上兩次結果:

5-1MRC捕獲變量修改為ARC以后打印結果為:
  __NSMallocBlock
 NSBlock
 NSObject
5-2MRC不捕獲變量修改為ARC以后打印結果為:
  __NSGlobalBlock
 NSBlock
 NSObject
5-3不論是ARC還是MRC下手動copy
void (^block)(void) = [^{
        NSLog(@"test");
    } copy];

結果如下:

  __NSGlobalBlock
 NSBlock
 NSObject
由此可見Block有三種類型,分別是NSGlobalBlock,NSMallocBlock,NSStackBlock。

這三種類型的Block對象的存儲區域如下:

對象的存儲域
NSStackBlock
NSGlobalBlock 程序的數據區域(.data區)
NSMallocBlock

截獲了自動變量的Block是NSStackBlock類型,沒有截獲自動變量的Block則是NSGlobalBlock類型,NSStackBlock類型的Block進行copy操作之后其類型變成了NSMallocBlock類型。

Block的類型 副本的配置存儲域 復制效果
NSStackBlock 從棧復制到堆
NSGlobalBlock 程序的數據區域(.data區) 什么也不做
NSMallocBlock 引用計數增加

下面我們一起分析一下NSStackBlock類型的Block進行copy操作后Block對象從棧復制到了堆有什么道理,我們首先來看一段代碼:

此時環境為MRC
void (^block)(void);
void test() {
    int age = 10;
    block = ^{
        NSLog(@"age=%d", age);
    };
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        test();
        block();
        return 0;
    }
}

不出意外的話,打印結果應該是10,那么結果是不是這樣呢?我們打印看一下:

2019-02-19 13:43:29.705085+0800 Test[4256:623794] age=-436931384

很奇怪,打印了一個這么奇怪的數字。這是為什么呢?
block使用了自動變量age,所以它是NSStackBlock類型的,因此block是存放在棧區,age是被捕獲作為結構體的成員變量,其值也是被保存在棧區。所以當test這個函數調用完畢后,它棧上的東西就有可能被銷毀了,一旦銷毀了,age值就不確定是多少了。通過打印結果也可以看到,確實是影響到了block的執行。
如果我們對block執行copy操作,結果會不會不一樣呢?

void (^block)(void);
void test() {
    int age = 10;
    block = [^{
        NSLog(@"age=%d", age);
    } copy];
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        test();
        block();
        return 0;
    }
}

打印結果:

2019-02-19 13:45:19.065669+0800 Test[4312:632135] age=10

這個時候得出了正確的輸出。
因為對block進行copy操作后,block從棧區被復制到了堆區,它的成員變量age也隨之被復制到了堆區,這樣test函數執行完之后,它的棧區被銷毀并不影響block,因此能得出正確的輸出。

7.ARC環境下自動為Block進行copy操作

因為在ARC環境下編譯器為我們做了很多copy操作。其中有一個規則就是如果Block被強指針指著,那么編譯器就會對其進行copy操作。我們看到這里:

 ^{
        NSLog(@"age=%d", age);
    };

這個Block塊是被強指針指著,所以它會進行copy操作,由于其使用了自動變量,所以是棧區的Block。經過復制以后就到了堆區,這樣由于Block在堆區,所以就不受Block執行完成的影響,隨時可以獲取age的正確值。

總結一下ARC環境下自動進行copy操作的情況一共有以下幾種:

1.block作為函數返回值時。
2.將block賦值給__strong指針時。
3.block作為Cocoa API中方法名含有usingBlock的方法參數時。
4.GCD中的API。

block作為函數返回值時
typedef void(^Block)(void);
Block test() {
    int age = 10;
    return ^{
        NSLog(@"age=%d", age);
    };
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        Block block = test();
        block();
        return 0;
    }
}

test函數的返回值是一個block,那這種情況的時候,在棧區的

^{
        NSLog(@"age=%d", age);
    };

這個block會被復制到堆區

將block賦值給強指針時

7中第一個例子就是將block賦值給強指針時,進行了copy操作的情況。

block作為Cocoa API中方法名含有usingBlock的方法參數時

比如說遍歷數組的函數:

NSArray *array = [[NSArray alloc] init];
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {            NSLog(@"%d", idx);
    }];

enumerateObjectsUsingBlock:這個函數中的block會進行copy操作

GCD中的API

GCD中的很多API的參數都有block,這個時候都會對block進行一次copy操作,比如下面這個dispatch_after函數:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{            
            NSLog(@"wait");
        });

下一篇 __weak、__strong、__block修飾Block的內部實現原理

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

推薦閱讀更多精彩內容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,718評論 0 3
  • iOS學習之深入理解程序編譯過程 https://juejin.im/post/5a352bb0f265da433...
    111浪子111閱讀 1,758評論 0 2
  • http://www.starming.com/index.php?v=index&view=107 http:/...
    111浪子111閱讀 3,176評論 0 11
  • 大道行。四維時空宇宙法,穿梭宇宙間。會有五維時空宇宙法,三維空間,二維時間,主宰世事變遷;有六維時空宇宙法。三維空...
    圣海乾坤閱讀 185評論 0 0
  • 前幾日,在朋友圈看到一位研究生同學辭了家人眼里的那份社會地位不錯,薪水不錯,前途不錯的令人欣羨的工作,原因是干得心...
    彩英cherish閱讀 283評論 0 0