深入研究Block实现原理

摘要

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_cd的值,由于结构体中捕获了变量的值,因此__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", &block_obj, &obj);
    void (^myBlock)(void) = ^{
        NSLog(@"block中 block_obj:%p;  obj:%p", &block_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 *)&block_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 *)&block_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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容