深入研究Block實現原理

kublia 8年前發布 | 8K 次閱讀 iOS開發 移動開發

摘要

Blocks是C語言的擴充功能, iOS 4中引入了這個新功能“Blocks”,那么block到底是什么東西呢。其實它就是一個閉包,一個帶有自動變量(局部變量)的匿名函數。很多語言也實現自己的閉包,比如C#的lamda表達式。這篇文章將從分析源碼的角度來分析下block到底是什么鬼。

研究工具:clang

為了研究編譯器的實現原理,我們使用clang(LLVM編譯器,和GCC類似),通過命令 clang -rewrite-objc main.m ,解析main.m,這樣我們就會得到對應的cpp文件main.cpp,就能看到block內部實現代碼(后面有源碼),借此可以研究 block 中各個特性的源碼實現方式。

一、 block捕獲外部變量

說到block怎么捕獲外部變量,我們要知道c語言中的5種變量:

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

今天主要對除函數參數變量之外的四種變量的捕獲情況進行研究

根據這四種變量寫出測試代碼如下:

測試代碼出錯,原因是變量d沒有加 __block 修飾,由于 __block 稍微復雜,我們后邊再講解,現在我們先對靜態變量、靜態全局變量、全局變量進行分析,代碼:

#import <Foundation/Foundation.h>
int global_a = 1;
static int static_global_b = 2;
int main(int argc, const char * argv[]) {

    static int static_c = 3;
    int d = 4;
    void(^TestBlock)() = ^{
        NSLog(@"block內部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d);
    };
    global_a ++;
    static_global_b ++;
    static_c ++;
    d ++;
    NSLog(@"block外部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d);
    TestBlock();

    return 0;
}

運行結果:

block外部:global_a = 2, static_global_b = 3, static_c = 4, d = 5
block內部:global_a = 2, static_global_b = 3, static_c = 4, d = 4

在這里有兩個問題:

1、為何不加__block就不能修改自動變量的值?

2、為何自動變量的值沒有增加,其他變量的值增加?自動變量什么情況下才能在block中增加修改?

為了弄清楚以上兩個疑問,我們用clang轉換一下源碼分析:

int global_a = 1;
static int static_global_b = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_c;
  int d;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_c, int _d, int flags=0) : static_c(_static_c), d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_c = __cself->static_c; // bound by copy
  int d = __cself->d; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d);
    }

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, const char * argv[]) {

    static int static_c = 3;
    int d = 4;
    void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));
    global_a ++;
    static_global_b ++;
    static_c ++;
    d ++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_1, global_a, static_global_b, static_c, d);
    ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

    return 0;
}

我們先簡單解釋下三個概念: __main_block_impl_0 、 __main_block_func_0 、 __main_block_desc_0

  • __main_block_func_0

    block內部的實現函數,其中__block_impl中的FuncPtr指向這個函數

  • __main_block_desc_0

    1、 reserved :保留字段默認為0

    2、 Block_size :為 sizeof(struct __main_block_impl_0) ,用來表示block所占內存大小。因為沒有持有變量,block大小為impl的大小加上Desc指針大小

    3、 __main_block_desc_0_DATA : __main_block_desc_0 的一個結構體實例

    這個結構體,用來描述block的大小等信息。如果持有可修改的捕獲變量時(即加__block),會增加兩個函數(copy和dispose),我們后面會分析

  • main_block_impl_0

    我們可以看到

    main_block_impl_0結構體中包含 __block_impl 、 __main_block_desc_0 兩個類型的結構體變量,其中 __block_impl 的內部實現源碼如下:
    struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    };
    1、 isa指針 ,如果我們對runtime了解的話,就明白isa指向Class的指針。
    2、 Flags ,當block被copy時,應該執行的操作
    3、 Reserved 為保留字段
    4、 FuncPtr指針 ,指向block內的函數實現
    __block_impl 保存block的類型isa(如&_NSConcreteStackBlock),標識(當block發生copy時,會用到),block的方法。
    接下來我們看下在main函數中block實現代碼:
    void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));
    去掉一些類型轉換代碼:
    void(*TestBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));
    調用block時的代碼:
    ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
    去掉類型轉換之后代碼:
    TestBlock->FuncPtr(TestBlock);
    可以看到以上代碼是調用 __main_block_impl_0 結構體中的構造函數,將變量傳入結構體內部保存,之后將這個結構體作為參數傳給FuncPtr指向的函數即 __main_block_func_0 , 其中靜態變量static_c傳入block內部的是地址,自動變量傳入的是值,而且在block外部執行 d++ 之前已經將d的值捕獲進入block內部, 這也就能說明為何block內部不能改變靜態變量的值的原因
    最終在block內部實現結果:
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = 0;
    impl.FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    static_c = 3;
    d = 4;

到此,__main_block_impl_0結構體就是這樣把自動變量捕獲進來的。也就是說,在執行Block語法的時候,Block語法表達式所使用的自動變量的值是被保存進了Block的結構體實例中,也就是Block自身中。

這里值得說明的一點是,如果Block外面還有很多自動變量,靜態變量,等等,這些變量在Block里面并不會被使用到。那么這些變量并不會被Block捕獲進來,也就是說并不會在構造函數里面傳入它們的值。

Block捕獲外部變量僅僅只捕獲Block閉包里面會用到的值,其他用不到的值,它并不會去捕獲。

我們再來看一下__main_block_func_0函數

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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d);
   }

可以看到,在函數內部通過 __cself->static_c 、 __cself->d 來獲取 static_c 和 d 的值,由于結構體中捕獲了變量的值,因此 __main_block_impl_0 類型 __cself 能夠獲取到內部保存的變量值,但是在函數內部只能修改捕獲地址值的 static_c 變量,不能修改傳入值變量的 d 的值。

到此為止,上面提出的二個問題就解開答案了。首先全局變量global_a和靜態全局變量static_global_b的值增加,以及它們被Block捕獲進去,這一點很好理解,因為是全局的,作用域很廣,所以Block捕獲了它們進去之后,在Block里面進行++操作,就像局部函數一樣,可以成功修改全局變量的值,Block結束之后,它們的值依舊可以得以保存下來。自動變量是以值傳遞方式傳遞到Block的構造函數里面去的。Block只捕獲Block中會用到的變量。由于只捕獲了自動變量的值,并非內存地址,所以Block內部不能改變自動變量的值。Block捕獲的外部變量可以改變值的是靜態變量,靜態全局變量,全局變量。

總結一下:在Block中改變變量值有2種方式,一是傳遞內存地址指針到Block中,二是改變存儲區方式(__block)。

我們先試一下第一種方式:傳遞內存地址,代碼:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {

    NSMutableString *str = [NSMutableString stringWithString:@"123"];
    void(^TestBlock)() = ^{
        [str appendString:@" 456"];
        NSLog(@"block中 %@", str);
    };
    NSLog(@"block前 %@", str);
    TestBlock();

    return 0;
}

控制臺輸出:

block前 123
block中 123 456

內部實現源碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSMutableString *str;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSMutableString *str = __cself->str; // bound by copy

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_2, str);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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, const char * argv[]) {

    NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_0);
    void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_3, str);
    ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

    return 0;
}

__main_block_impl_0構造函數

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str

可以看出傳遞的是NSMutableString *類型,即傳遞的地址,進而可以改變str的值。

上邊代碼中我們可以看到 __main_block_copy_0 和_ _main_block_dispose_0 概念,接下來對這兩個概念進行初步講解.

二、Block的copy和dispose

OC中,一般Block就分為以下3種,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock

先來說明一下3者的區別。

1.從捕獲外部變量的角度上來看

  • _NSConcreteStackBlock:

    只用到外部局部變量、成員屬性變量,且沒有強指針引用的block都是StackBlock。

    StackBlock的生命周期由系統控制的,一旦返回之后,就被系統銷毀了。

  • _NSConcreteMallocBlock:

    有強指針引用或copy修飾的成員屬性引用的block會被復制一份到堆中成為MallocBlock,沒有強指針引用即銷毀,生命周期由程序員控制

  • _NSConcreteGlobalBlock:

    沒有用到外界變量或只用到全局變量、靜態變量的block為_NSConcreteGlobalBlock,生命周期從創建到應用程序結束。

沒有用到外部變量肯定是_NSConcreteGlobalBlock,這點很好理解。不過只用到全局變量、靜態變量的block也是_NSConcreteGlobalBlock。舉例如下:

#import <Foundation/Foundation.h>
int global_a = 1;
static int static_global_b = 2;

int main(int argc, const char * argv[]) {

    static int static_c = 3;
    void (^myBlock)(void) = ^{
        NSLog(@"Block中 變量 = %d %d %d",static_global_b ,static_c, global_a);
    };

    NSLog(@"%@",myBlock);
    myBlock();

    return 0;
}

控制臺結果:

<__NSMallocBlock__: 0x100203980>
Block中 變量 = 2 3 1

可見,只用到全局變量、靜態變量的block也可以是_NSConcreteGlobalBlock。

所以在ARC環境下,3種類型都可以捕獲外部變量。

2. 從持有對象的角度上來看:

  • _NSConcreteStackBlock是不持有對象的

    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
    
      NSObject *obj = [[NSObject alloc]init];
      void (^myBlock)(void) = ^{
           NSLog(@"block中 %ld",obj.retainCount);
      };
    
      NSLog(@"block外 %ld",obj.retainCount);
      myBlock();
    
      return 0;
    }

    輸出結果:

    block外 1
    block中 1
  • _NSConcreteMallocBlock是持有對象的

    //以下是在MRC下執行的
    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
    
      NSObject *obj = [[NSObject alloc]init];
      NSLog(@"block-1 %ld",obj.retainCount);
      void (^myBlock)(void) = [^{
           NSLog(@"block-3 %ld",obj.retainCount);
      } copy];
    
      NSLog(@"block-2 %ld",obj.retainCount);
      myBlock();
    
      return 0;
    }

    輸出結果:

    block-1 1
    block-1 2
    block-1 2
  • _NSConcreteGlobalBlock也不持有對象

    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
    
      void (^myBlock)(void) = ^{
           NSObject *obj = [[NSObject alloc]init];
           NSLog(@"block %ld",obj.retainCount);
      };
      myBlock();
    
      return 0;
    }

    輸出結果:

    block 1

    由于_NSConcreteStackBlock所屬的變量域一旦結束,那么該Block就會被銷毀。在ARC環境下,編譯器會自動的判斷,把Block自動的從棧copy到堆。比如當Block作為函數返回值的時候,肯定會copy到堆上。

  • 手動調用copy
  • Block是函數的返回值
  • Block被強引用,Block被賦值給__strong或者id類型
  • 調用系統API入參中含有usingBlcok的方法

以上4種情況,系統都會默認調用copy方法把Block賦復制

但是當Block為函數參數的時候,就需要我們手動的copy一份到堆上了。這里除去系統的API我們不需要管,比如GCD等方法中本身帶usingBlock的方法,其他我們自定義的方法傳遞Block為參數的時候都需要手動copy一份到堆上。

copy函數把Block從棧上拷貝到堆上,dispose函數是把堆上的函數在廢棄的時候銷毀掉。

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

上面是源碼中2個常用的宏定義和4個常用的方法,一會我們就會看到這4個方法。

static void *_Block_copy_internal(const void *arg, const int flags) { 
   struct Block_layout *aBlock; 
   const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; 
   if (!arg) return NULL;
   aBlock = (struct Block_layout *)arg;
   if (aBlock->flags & BLOCK_NEEDS_FREE) {
     latching_incr_int(&aBlock->flags); 
      return aBlock; 
    } else if (aBlock->flags & BLOCK_IS_GLOBAL) { 
      return aBlock; 
    } 
   struct Block_layout *result = malloc(aBlock->descriptor->size);
   if (!result) return (void *)0;
   memmove(result, aBlock, aBlock->descriptor->size);
   result->flags &= ~(BLOCK_REFCOUNT_MASK);
  result->flags |= BLOCK_NEEDS_FREE | 1; 
   result->isa = _NSConcreteMallocBlock; 
   if (result->flags & BLOCK_HAS_COPY_DISPOSE) { 
    (*aBlock->descriptor->copy)(result, aBlock); 
   }
   return result;
}

上面這一段是Block_copy的一個實現,實現了從_NSConcreteStackBlock復制到_NSConcreteMallocBlock的過程.

void _Block_release(void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    int32_t newCount;
    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
    if (newCount > 0) return;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
        _Block_deallocator(aBlock);
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        ;
    }
    else {
        printf("Block_release called upon a stack Block: %p, ignored\\\\n", (void *)aBlock);
    }
}

上面這一段是Block_release的一個實現,實現了怎么釋放一個Block。

因為在C語言的結構體中,編譯器沒法很好的進行初始化和銷毀操作。這樣對內存管理來說是很不方便的。所以就在 __main_block_desc_0 結構體中間增加成員變量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*) 和 void (*dispose)(struct __main_block_impl_0*) ,利用OC的Runtime進行內存管理。

相應的增加了2個方法。

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

這里的 _Block_object_assign 和 _Block_object_dispose 就對應著retain和release方法。

三.Block中__block實現原理

1.普通非對象的變量

先來看看普通變量的情況

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {

    __block int a = 0;
    void (^myBlock)(void) = ^{
        a ++;
    };
    myBlock();

    return 0;
}

轉化后的代碼:

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) ++;
    }
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, const char * argv[]) {

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
    void (*myBlock)(void) = ((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 *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

從以上代碼看出

  • 被__block修飾的變量,內部實現多了一個 __Block_byref_a_0 類型的結構體,這個結構體有5個成員變量。第一個是 isa指針 ,第二個是指向自身類型的 __forwarding 指針,第三個是一個標記 flag ,第四個是它的大小,第五個是變量值。
  • 被__block修飾的變量a轉化成 __Block_byref_a_0 類型的變量a
  • 將 __Block_byref_a_0 類型的變量a的地址傳入 __main_block_impl_0 內部的 __Block_byref_a_0 修飾的變量a
  • 在 __main_block_func_0 函數中通過 __cself->a 取到 __Block_byref_a_0 結構體變量,在通過 (a->__forwarding->a) ++ 實現被__block修飾的變量a的值加一

ARC環境下,一旦Block賦值就會觸發copy, block就會copy到堆上,Block也是 NSMallocBlock。ARC環境下也是存在 NSStackBlock的時候,這種情況下, block就在棧上。

MRC環境下,只有copy, block才會被復制到堆上,否則, block一直都在棧上,block也只是 NSStackBlock,這個時候 forwarding指針就只指向自己了。

2.對象的變量

//ARC環境下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {

    __block NSObject *block_obj = [[NSObject alloc] init];
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"block外 block_obj:%p;  obj:%p", █_obj, &obj);
    void (^myBlock)(void) = ^{
        NSLog(@"block中 block_obj:%p;  obj:%p", █_obj, &obj);
    };
    myBlock();
    NSLog(@"%@", myBlock);
    return 0;
}

結果輸出:

block外 block_obj:0x7fff5fbff758;  obj:0x7fff5fbff728
block中 block_obj:0x100300578;  obj:0x1003001e0
<__NSMallocBlock__: 0x1002004d0>

以上代碼轉換后:

struct __Block_byref_block_obj_0 {
  void *__isa;
__Block_byref_block_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *block_obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
  NSObject *obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_1, &(block_obj->__forwarding->block_obj), &obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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, const char * argv[]) {

    __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)█_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_0, &(block_obj.__forwarding->block_obj), &obj);
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)█_obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

首先需要說明的一點是對象在OC中,默認聲明自帶__strong所有權修飾符的,所以main開頭我們聲明的

__block  NSObject *block_obj = [[NSObject alloc] init];
NSObject *obj = [[NSObject alloc] init];

等價于:

__block __strong  NSObject *block_obj = [[NSObject alloc] init];
__strong NSObject *obj = [[NSObject alloc] init];

從以上轉化代碼看出:

  • 被__block 修飾的變量block_obj轉化成 __Block_byref_block_obj_0 的變量,名稱相同
  • __main_block_impl_0 構造方法中將 __Block_byref_block_obj_0 類型的 block_obj 變量的地址傳入給結構體的 __Block_byref_block_obj_0 類型 block_obj`變量,將變量obj的地址傳入給結構體的變量obj
  • 在 __main_block_func_0 函數中通過 __cself->block_obj 獲取 __Block_byref_block_obj_0 類型變量 block_obj ,再通過 block_obj->__forwarding->block_obj 獲取外部捕獲的變量;通過 __cself->obj 獲取obj變量
  • Block捕獲了 block,并且強引用了,因為在 Block_byref_block_obj_0結構體中,有一個變量是id block_obj,這個默認也是帶__strong所有權修飾符的
  • ARC環境下,Block捕獲外部對象變量,是都會copy一份的,地址都不同。只不過帶有__block修飾符的變量會被捕獲到Block內部持有

我們再來看看MRC環境下的情況,還是將上述代碼的例子運行在MRC中:

block外 block_obj:0x7fff5fbff758;  obj:0x7fff5fbff728
block中 block_obj:0x7fff5fbff758;  obj:0x7fff5fbff700
<__NSStackBlock__: 0x7fff5fbff6e0>

這個時候block在棧上, NSStackBlock ,可以打印出來retainCount值都是1。當把這個block copy一下,就變成 NSMallocBlock ,對象的retainCount值就會變成2了。

總結:

在MRC環境下,

block根本不會對指針所指向的對象執行copy操作,而只是把指針進行的復制。

而在ARC環境下,對于聲明為

block的外部對象,在block內部會進行retain,以至于在block環境內能安全的引用外部對象,所以才會產生循環引用的問題!

  • 對于非對象的變量
    自動變量的值,被copy進了Block,不帶 block的自動變量只能在里面被訪問,并不能改變值;帶 block的自動變量 和 靜態變量 就是直接地址訪問。所以在Block里面可以直接改變變量的值
  • 靜態全局變量,全局變量,函數參數
    可以在直接在Block中改變變量值的,但是他們并沒有變成Block結構體 __main_block_impl_0 的成員變量,因為他們的作用域大,所以可以直接更改他們的值
    值得注意的是,靜態全局變量,全局變量,函數參數他們并不會被Block持有,也就是說不會增加retainCount值
  • 對于對象
    在MRC環境下,

    block根本不會對指針所指向的對象執行copy操作,而只是把指針進行的復制。

    而在ARC環境下,對于聲明為

    block的外部對象,在block內部會進行retain,以至于在block環境內能安全的引用外部對象。

最后

在ARC環境下,Block也是存在 __NSStackBlock 的時候的,平時見到最多的是 _NSConcreteMallocBlock ,是因為我們會對Block有賦值操作,所以ARC下,block 類型通過=進行傳遞時,會導致調用 objc_retainBlock->_Block_copy->_Block_copy_internal 方法鏈。并導致 __NSStackBlock__ 類型的 block 轉換為 __NSMallocBlock__ 類型。

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {

    __block int temp = 10;
    NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});
    return 0;
}
<__NSStackBlock__: 0x7fff5fbff768>

參考:

http://www.jianshu.com/p/ca6ac0ae93ad

http://www.jianshu.com/p/ee9756f3d5f6

 

來自:http://www.jianshu.com/p/710026d5bcfb

 

 本文由用戶 kublia 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!