Block 浅析

1.Block 的定义

In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

意思就是,在编程语言中,闭包是一个引用外部变量(有时候也称作自由变量)的,函数(或指向函数的指针)。

Objective-C 中的 block 其实就是对于闭包的实现。

2.Block 的结构

block 的数据结构定义如下:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

通过该数据结构,我们可以知道,一个 block 实例实际上由 6 部分构成:

  1. isa 指针,所有对象都有该指针,用于实现对象相关的功能。
  2. flags,用于按 bit 位表示一些 block 的附加信息, block copy 的实现代码可以看到对该变量的使用。
  3. reserved,保留变量。
  4. invoke,函数指针,指向具体的 block 实现的函数调用地址。
  5. descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
  6. variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。

Objective-C中,有3种 block 类型:

  1. _NSConcreteGlobalBlock 全局的静态 block。
  2. _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
  3. _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。

3 种 block 示例如下:

typedef void (^Block)(id info);
static NSString * const kTestDemo = @"TestDemo";

- (void)blockTest1 {
    
    int i = 0;
    Block block1 = ^(id info) {
        NSLog(@"Block1 %d", i);
    };
    
    NSLog(@"%@", ^{NSLog(@"Hello, world!");});                  //__NSGlobalBlock__
    NSLog(@"%@", ^{NSLog(@"%@.Hello, world!", kTestDemo);});    //__NSGlobalBlock__
    NSLog(@"%@", ^{NSLog(@"%d.Hello, world!", i);});            //__NSStackBlock__
    NSLog(@"%@", [^{NSLog(@"%d.Hello, world!", i);} copy]);     //__NSMallocBlock__
    NSLog(@"%@", block1);                                       //__NSMallocBlock__

}


然后,我们先来研究下 NSGlobalBlock 的底层实现,先新建一个名为 block.c 的源文件,然后用

clang -rewrite-objc block.c

将其转化为 C++ 实现,生成 block.cpp 文件。

block.c 源代码如下:

#include <stdio.h>
int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

block.cpp 文件中的关键代码如下:

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

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 (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
    return 0;
}

从中我们可以看出,__main_block_impl_0 就是 block 的实现,从 block 的结构体我们可以看出:

  1. 它主要由一个 impl 、 一个 descriptor 和一个构造函数 __main_block_impl_0 组成。
  2. 该结构体的前两个参数也都是结构体,第二个参数Desc中,是关于结构体的大小、版本升级所需保留参数,暂不做过多解析。
  3. 第一个结构体参数的结构体中,
    1. 看到了 isa 指针,说明 block 也是 Objective-C 中的对象,isa 指向对象的一个 8 字节;
    2. Flags 和 Reserved 是某些标记字段,暂不过多解释;
    3. FuncPtr 函数指针,其实就是block所需执行的代码段,存放的是地址
  4. 结构体的构造函数中,
    1. 传入的第一个参数,就是函数指针,impl.FuncPtr = fp;
    2. block 的 isa 指向 _NSConcreteStackBlock 指针,也就是说,当一个 block 被声明的时候,它都是一个 _NSConcreteStackBlock 类的对象;
    3. 可以看出,构造函数是对block的初始化
  5. __main_block_impl_0 函数中其实存储着我们block中写下的代码。

block 的底层数据结构可以用下面一张图来展示:

image

我们再来看 main() 函数实现,把其中的类型都去掉,如下

/* 调用结构体函数初始化 
 __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);

由此,可以看出,__main_block_func_0 函数的地址和 __main_block_desc_0_DATA (也就是 { 0, sizeof(struct __main_block_impl_0) } )的地址,传入到了 __main_block_impl_0 的构造函数里,被保存到block的结构体中。

3.Block 的变量获取

1) 获取局部变量的 block 结构体

首先,用 clang 命令将 Objective-C 代码转换为 C++ 实现,其中 Objective-C 源码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 0;
        static int b = 1;
        void (^Block)(void) = ^{
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        a = 2;
        b = 3;
        
        Block();
        
    }
    return 0;
}

//打印结果为:a = 0; b = 3;

转换的 C++ 实现如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //值
  int *b; //指针
  
  //1.
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_e0fd9d_mi_0, a, (*b));
        }

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 0;
        static int b = 1;
        
        //3.
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));

        a = 2;
        b = 3;

        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

从上面可以看出,__main_block_impl_0 结构体中多了两个变量,用来保存block获取的外部的变量,其中 a 是 int 类型,b 是 int * 类型,也就是 a 保存的是变量的值,b 保存的是变量的指针;从 3. 处代码就可以看出 b 静态局部变量传入的是变量的地址。

2)获取全局变量的 block 结构体

同样,用 clang 命令将 Objective-C 代码转换为 C++ 实现,其中 Objective-C 源码如下:

int a = 0;
static int b = 1;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^Block)(void) = ^{
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        a = 2;
        b = 3;
        
        Block();
        
    }
    return 0;
}

//打印结果为:a = 2; b = 3;

转换的 C++ 实现如下:

int a = 0;
static int b = 1;


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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_eaada3_mi_0, a, b);
        }

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        a = 2;
        b = 3;

        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

由上可以看出,block 根本就没有 capture 全局变量保存到自己的结构体中,而是直接调用并赋值,这是因为局部变量因为跨函数访问所以需要捕获,全局变量在哪里都可以访问,所以不用捕获。

总结:

变量类型 捕获到 block 内部 访问方式
局部变量 值传递
静态局部变量 指针传递
全局变量 直接访问

3)block 是怎么获取捕获的 self 的

以下代码中 block 是否会捕获变量呢?

#import "Person.h"

@interface Person ()

@property (nonatomic, copy) NSString *name;

@end

@implementation Person

- (instancetype)initWithName:(NSString *)name {
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}

- (void)test1 {
    void(^block)(void) = ^{
        NSLog(@"实例方法:%@",self);
    };
    block();
}

+ (void)test2 {
    NSLog(@"类方法.");
}

@end

同样,转换为 C++ 代码查看其内部结构

static instancetype _I_Person_initWithName_(Person * self, SEL _cmd, NSString *name) {
    if (self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
    }
    return self;
}


struct __Person__test1_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test1_block_desc_0* Desc;
  Person *self; //捕获到self指针
  __Person__test1_block_impl_0(void *fp, struct __Person__test1_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __Person__test1_block_func_0(struct __Person__test1_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy  取出block中 self 的值

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_e56487_mi_0,self); //使用上面取到的 self 的值
    }
    
static void __Person__test1_block_copy_0(struct __Person__test1_block_impl_0*dst, struct __Person__test1_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test1_block_dispose_0(struct __Person__test1_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test1_block_impl_0*, struct __Person__test1_block_impl_0*);
  void (*dispose)(struct __Person__test1_block_impl_0*);
} __Person__test1_block_desc_0_DATA = { 0, sizeof(struct __Person__test1_block_impl_0), __Person__test1_block_copy_0, __Person__test1_block_dispose_0};

static void _I_Person_test1(Person * self, SEL _cmd) { //test1实例方法默认传入两个参数,一个是 self ,一个是 _cmd (函数具体实现的函数指针)
    void(*block)(void) = ((void (*)())&__Person__test1_block_impl_0((void *)__Person__test1_block_func_0, &__Person__test1_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

static void _C_Person_test2(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_e56487_mi_1);
}

从上面可以看出,不论对象方法还是类方法都会默认将 self 作为参数传递给方法内部,既然是作为参数传入,那么 self 肯定是局部变量。就如上面得到的结论局部变量肯定会被 block 捕获。

那么,接下来我们来看一下如果在 block 中使用成员变量或者调用实例的属性会有什么不同的结果。

- (void)test1 {
    void(^block)(void) = ^{
        NSLog(@"%@",self.name);
        NSLog(@"%@",self->_name);
    };
    block();
}
struct __Person__test1_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test1_block_desc_0* Desc;
  Person *self;//这里同样只是捕获了self
  __Person__test1_block_impl_0(void *fp, struct __Person__test1_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test1_block_func_0(struct __Person__test1_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_2772af_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")));//属性这里是调用 objc_msgSend 发送消息
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_Person_2772af_mi_1,(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)));//成员变量这里是直接访问内存
    }

3)block 的类型

从上面的 C++ 实例代码中发现,block 构造函数中的 isa 都是指向
_NSConcreteStackBlock 类对象地址,而在 Objective-C 中打印,能得出三种类型的 block ,这是为什么呢?

首先,我们先来看一下 block 的类型打印

int a = 0;
static int b = 1;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^Block)(void) = ^{
            NSLog(@"a = %d, b = %d", a, b);
        };
        
        a = 2;
        b = 3;
        
        Block();
        
        NSLog(@"%@", [Block superclass]);
        NSLog(@"%@", [[Block superclass] superclass]);
        NSLog(@"%@", [[[Block superclass] superclass] superclass]);
        NSLog(@"%@", [[[[Block superclass] superclass] superclass] superclass]);
        NSLog(@"%@", [[[[Block superclass] superclass] superclass] class]); // NSObject
        
    }
    return 0;
}

//打印结果如下:
//__NSGlobalBlock
//NSBlock
//NSObject
//(null)
//NSObject

从上,可以看出 block 本质其实就是 Objective-C 对象。

block 的类型又是如何定义呢?如下图所示:

block类型 环境 内存区域
NSGlobalBlock 没有访问 auto 变量 数据段
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBLock 调用了copy

经分析可知,ARC 条件下,编译器会根据情况自动将栈上的 block 进行一次 copy 操作,将 block 复制到堆上,那么什么情况下 ARC 会自动将 block 进行一次 copy 操作呢?

  1. block 作为函数返回值时;
typedef void (^Block)(void);
Block myblock() {
    int a = 10;
    // 上文提到过,block中访问了auto变量,此时block类型应为__NSStackBlock__
    Block block = ^{
        NSLog(@"---------%d", a);
    };
    return block;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = myblock();
        block();
       // 打印block类型为 __NSMallocBlock__
        NSLog(@"%@",[block class]);
    }
    return 0;
}
  1. 将 block 赋值给 __strong 指针时;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // block内没有访问auto变量
        Block block = ^{
            NSLog(@"block---------");
        };
        NSLog(@"%@",[block class]);
        
        int a = 10;
        // block内访问了auto变量,但没有赋值给__strong指针
        NSLog(@"%@",[^{
            NSLog(@"block1---------%d", a);
        } class]);
        
        // block赋值给__strong指针
        Block block2 = ^{
          NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@",[block1 class]);
    }
    return 0;
}

//打印如下:
// __NSGlobalBlock__
// __NSStackBlock__
// __NSMallocBlock__
  1. block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时;
例如:遍历数组的 block 方法,将 block 作为参数的时候。

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"array = %@, idx = %lu, value = %@", array, (unsigned long)idx, obj);

}];
  1. block 作为 GCD API 的方法参数时;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    self = [[YYFileManager alloc] init];
});        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
});

那么又回到上面的问题,为什么 C++ 中指向的都是 _NSConcreteStackBlock 呢?大胆的猜测一下,其是不是在 runtime 运行时过程中对 block 类型进行了转变,最终 block 类型以 runtime 运行时类型,也就是我们打印出的类型为准。

来看一下 block 被 copy 的示例代码:

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1.
    if (!arg) return NULL;
    // 2.
    aBlock = (struct Block_layout *)arg;
    // 3.
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4.
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5.
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6.
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7.
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8.
    result->isa = _NSConcreteMallocBlock;
    // 9.
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

从中可以看出,在第 8 步,目标的 block 类型被修改为 _NSConcreteMallocBlock。(代码来自这里<html>http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy</html>)

那 block 怎么释放呢,这里分 6 个步骤:

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

4)__block 实现原理

我们都知道,要想在 block 内修改外部基础变量(float、int、long等),需要在前面加修饰符 __block ,那么它是如何实现修改呢,我们来看源码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int a = 0;
        NSLog(@"Block 外:a = %d , &a = %p", a, &a);

        NSLog(@"Block 外:a = %d , &a = %p", a, &a);
        void (^Block)(void) = ^{
            a = 1;
            NSLog(@"Block 内:a = %d , &a = %p", a, &a);
        };
        
        a = 2;
        
        Block();
        
    }
    return 0;
}

//打印结果:
//Block 外:a = 0 , &a = 0x7ffeefbff508
//Block 内:a = 1 , &a = 0x10072c0a8

从打印结果看出,a 的地址变了,而且变化挺大的,从地址和上面分析可以看出,a 变量从栈区被 copy 了一份到堆区,我们再来看看 C++ 源码:

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) = 1;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_bb7d34_mi_0, (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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};

        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

        (a.__forwarding->a) = 2;

        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

1)从第一个结构体 struct __Block_byref_a_0 可以看出,__block 修饰的变量 a 被转化为了一个结构体,这个结构体有5个成员变量:

  1. isa 指针,说明它由基础变量变成了OC对象
  2. forwarding 指针,这个后面再详细解释
  3. flags,标记用的
  4. size,该结构体的大小
  5. a,变量值

2)从构造函数 __main_block_func_0 中 (a->__forwarding->a) = 1 和 mian 函数中 (a->__forwarding->a) = 2 可以看出,栈区的变量 a 的 __forwarding 指针指向堆区的 a 变量的结构体,而堆中 a 变量的结构体的 __forwrding 指针指向自己,如图:


image

而在 MRC 中,即便 blcok 捕获了__block 变量,也不会从栈上 copy 到堆上,需要自己手动处理,那么这时候变量结构体的 __forwarding 指向自己,如图:


image

如果,__block 修饰 OC 对象呢,它会是什么样的呢?源码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
            
        __block id block_obj = [[NSObject alloc]init];
        id obj = [[NSObject alloc]init];
        
        NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        
        void (^myBlock)(void) = ^{
            NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        };
        
        myBlock();
    }
    return 0;
}

//打印结果:

//block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]

//Block****中********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]

编译后的 C++ 代码如下:

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*);
 id block_obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _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
  id obj = __cself->obj; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_64161b_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __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"))};
        id 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_kx_sk4tzwgx06qdcxsv6kcrmyv8bq68lb_T_main_64161b_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , 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 中,ARC 默认声明自带 __strong 所有权修饰符,所以 main 函数的默认声明如如下:

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

综上,可以看出ARC环境下,block 捕获外部对象变量,是会 copy 一份的,可以看出他们的地址不同。block 内会保留强引用修饰的对象,如果不做处理,就会产生循环引用。


参考文献:

clang命令相关:https://cotin.tech/iOS/clang-rewrite-objc/

Block底层原理:https://juejin.im/post/5b0181e15188254270643e88

__block实现原理:https://www.jianshu.com/p/ee9756f3d5f6

谈Objective-C block的实现:https://blog.devtang.com/2013/07/28/a-look-inside-blocks/

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

推荐阅读更多精彩内容