Blocks深刻理解和詳解

介紹

Block是C級語法和運行時特性。它們相似於標準C函數,可是除了可執行代碼以外,它們還可能包含對自動(堆棧)或託管(堆)內存的變量綁定。所以,Block能夠維護一組狀態(數據),它能夠用來在執行時影響行爲。c++

您可使用Blocks來組合函數表達式,這些表達式能夠被傳遞給API,可選地存儲,並由多個線程使用。Block對於回調來講特別有用,由於塊包含在回調上執行的代碼和執行過程當中須要的數據。app

能夠在GCC和Clang中使用OS X v10.6 Xcode開發工具。您可使用OS X v10.6和以後的模塊,以及隨後的iOS 4.0。Block運行時是開源的,能夠在LLVM的編譯器-rt子項目存儲庫中找到。block也被提交給C標準工做組做爲N1370:蘋果對C的擴展,由於Objective-C和c++都是從C派生出來的,block被設計用於與全部三種語言(以及Objective-C++)一塊兒工做。語法反映了這個目標。iphone

概念普及

  • Block本質是Objective-C的對象,不是函數指針(這個有點混淆,網上廣泛都說是函數指針)
  • Block類型變量本質是函數指針(以下聲明其實在編譯成C語言源碼的時候就是一個函數指針)
  • 截獲自動變量 全部的變量都會截獲嗎?好比全局變量、靜態變量。其實截獲的只是局部變量而已
  • __block變量加了它爲啥就能被修改了?不少人也許會說是傳入了地址,這種說法很片面的,若是是這樣的其實徹底不用加__block,蘋果幫你作了就好了呀。其實__block以後翻譯成C語言源碼以後,變量會變成一個對象,該對象存儲了原來變量的地址,函數傳入的是一個對象。

探尋Block本質

經過LLVM的編譯器-rt子項目存儲庫中找到runtime下的Block源碼svn

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
複製代碼

當咱們聲明一個Block的時候,編譯器其實會將block轉換成以上struct結構體。 其中isa指向的是Block具體的類。有以下6中,不過其中StackBlockMallocBlockGlobalBlock是比較常見的函數

/* the raw data space for runtime classes for blocks */
/* class+meta used for stack, malloc, and collectable based blocks */
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
複製代碼

invoke函數指針則是對應Objective-C中代碼的具體實現工具

看到這裏不知你們有沒有想到runtime中objc_object的isa呢? 其實兩個原理是同樣的。這裏就不具體介紹objc_object開發工具

因此Block即爲Objective-C的對象spa

Block類型變量

Block的類型變量聲明以下:線程

int (^blk) (int);翻譯

咱們聲明一個Block類型變量而且賦值

int (^blk) (int) = ^(int count){return count+1};
複製代碼

而後咱們經過以下

xcrun -sdk iphonesimulator11.2 clang -rewrite-objc -F /Applications/Xcode\ 2.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks ViewController.m

咱們能夠獲得如下源碼:

//對應的Block具體struct結構體
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//Block方法的具體實現
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {

        return count+1;
    }

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 (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
    ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
}
複製代碼

其中int (*blk) (int)則就是對應咱們的Block類型變量,這裏就就能夠看到了Block類型變量的本質了,其實就是C語言的函數指針

截獲自動變量

說截獲自動變量以前咱們先看如下代碼

int tmp = 2;
int (^blk) (int) = ^(int count){
    return count+tmp;
};
tmp = 3;
int result = blk(2);
NSLog(@"%d",result);
複製代碼

以上代碼會打印出多少呢?5仍是4?? 正確答案是4

爲何是4呢?其實就是由於Block截獲自動變量的緣由

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

        return count+tmp;
    }

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 tmp = 2;
    int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, tmp));
    tmp = 3;
    int result = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6n_mdf6rn0d5f5cw6r1h2620h3w0000gn_T_ViewController_b80e84_mi_0,result);
}`__ViewController__viewDidLoad_block_impl_0`
複製代碼

經過如下代碼能夠看出,當咱們在給Block類型變量賦值的時候,tmp變量同時被傳入,而且被保存到了__ViewController__viewDidLoad_block_impl_0的struct中。這時候其實就是截獲了自動變量,因爲已經在struct類中保存了一份,即便後邊更改,也不會影響Block截獲的值。

int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, tmp));
複製代碼

爲何要對局部變量進行截獲呢?而全局變量和靜態變量不須要截獲,而且修改的的時候也不須要加__block呢?

主要緣由就是變量的生命週期。局部變量在代碼塊執行結束以後就會被釋放,可是Block不必定在此時釋放。因此就會出現變量超過生命週期的現象,此時對局部變量進行截獲,即便局部變量被釋放,可是Block一樣仍是能夠正常使用的。由於全局變量和靜態變量的釋放時間確定不會在Block以前,因此沒必要對他們進行截獲。

全局變量和靜態變量存儲在全局數據區;局部變量存儲在棧中

__block變量存儲域探究

不知道你們有沒有想過一個問題,爲何須要__block呢?若是沒有__block難道就修改不了變量了嗎?

咱們先看一下加了__block編譯器給咱們作了啥

struct __Block_byref_tmpBlock_0 {
  void *__isa;
__Block_byref_tmpBlock_0 *__forwarding;
 int __flags;
 int __size;
 int tmpBlock;
};

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __Block_byref_tmpBlock_0 *tmpBlock; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_tmpBlock_0 *_tmpBlock, int flags=0) : tmpBlock(_tmpBlock->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {
  __Block_byref_tmpBlock_0 *tmpBlock = __cself->tmpBlock; // bound by ref

        (tmpBlock->__forwarding->tmpBlock) = 100;
        return count+(tmpBlock->__forwarding->tmpBlock);
    }
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->tmpBlock, (void*)src->tmpBlock, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->tmpBlock, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_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"));
    __attribute__((__blocks__(byref))) __Block_byref_tmpBlock_0 tmpBlock = {(void*)0,(__Block_byref_tmpBlock_0 *)&tmpBlock, 0, sizeof(__Block_byref_tmpBlock_0), 2};
    int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_tmpBlock_0 *)&tmpBlock, 570425344));
    int result = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6n_mdf6rn0d5f5cw6r1h2620h3w0000gn_T_ViewController_a1c583_mi_0,result);
}

複製代碼

這時候你們能夠與前邊沒有加__block的代碼進行比較,二者的差異在哪裏。 其實你們應該很容易發現加了__block以後,變量造成了一個struct,這個struct中保存了變量的值,同時還有一個__forwarding。這個__forwarding其實就是爲何須要__block的關鍵。

Block從棧複製到堆的時候,__block變量也會受到影響。以下:

__block變量的配置存儲域 Block從棧到堆時的影響
從棧複製到堆並被Block持有
被Block持有

ARC下,Block若是是棧的話,默認會copy到堆上。此時所使用的__block變量同時也會從棧被複制到堆上以下圖

存儲

那若是Block在堆上了,咱們在Block中修改了變量,怎麼讓棧上的變量也同時能正確訪問呢?這其實就是__forwarding功勞了。

__block變量初始化的時候__forwarding是指向自己本身的。當__block變量從棧複製到堆上的時候,此時會將__forwarding的值替換爲複製到目標堆上的__block變量用結構體實例的地址。以下圖:

經過此功能,不管是在Block語法中、Block語法外使用__block變量,仍是__block變量配置在棧上或堆上,均可以順利地訪問一個__block變量。 到這裏你們應該明白了"__block加了以後,是把變量的地址傳入Block"的說法是很片面的吧啦

個人博客

FlyOceanFish

相關文章
相關標籤/搜索