美文网首页
Block 浅析

Block 浅析

作者: 一梦十年ya | 来源:发表于2019-03-19 11:50 被阅读0次

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/

相关文章

  • iOS-2 Block

    block块 系列文章: iOS Block浅浅析 - 简书 iOS Block实现原理 iOS Block __...

  • Block 原理浅析

    Block 浅析 一、Block内存 (堆、栈、全局) 知识点: 栈区(stack)— 由编译器自动分配释放 ...

  • Block浅析

    先看一道关于block的面试题: 解答:arc下打印的是block内存地址,mrc下会crash原因:block默...

  • Block浅析

    参考了网上的一些Block的文档,加上自己对Block的使用、理解,对Block做一个简单的分析。 Block是C...

  • Block浅析

    block是语言的的扩展功能,它是一种带有自动变量(局部变量)的匿名函数. /******************...

  • block浅析

    Block 的本质是可以截取自动变量的匿名函数。 一.block的三种定义方式 1.返回值类型(^block的名字...

  • block浅析

    对block的了解不是特别多,平常用代理用的比较多; Block写法:^(传入参数列){行为主体};Block实体...

  • Block 浅析

    1.Block 的定义 In programming languages, a closure is a func...

  • iOS block浅析

    iOS开发中经常会使用block结合gcd来完成多线程编程,block也属于对象,主要有三种类型:1、 _NSCo...

  • block使用浅析

    实现目标,给键盘添加一个工具条 LZKeyboardTool.xib图: 效果图片: 笔者主要是想通过该示例程序来...

网友评论

      本文标题:Block 浅析

      本文链接:https://www.haomeiwen.com/subject/apkgmqtx.html