block 源码解析

2020-04-13  本文已影响0人  开了那么

什么是block?

首先,看一个极简的block:

int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
            NSLog(@"Hello, World!");
        };
        block();
}
return 0;
}
block编译转换结构

对其执行clang -rewrite-objc编译转换成C++实现,得到以下代码:

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;\
};

// 构造函数(类似于OC的init方法),返回结构体对象

struct __ViewController__viewDidLoad_block_impl_0 {

  struct __block_impl impl;

  struct __ViewController__viewDidLoad_block_desc_0* Desc;

  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//封装了block 的执行逻辑的函数. 参数 为block

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8v_w12n8yrs075g6wv21qzg72180000gn_T_ViewController_672106_mi_0);

    }

static struct __ViewController__viewDidLoad_block_desc_0 {

  size_t reserved;

  size_t Block_size;

} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

* * **************************************** * **************************************** * **
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {

    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void (*block)(void) = ((void (*)())&)__ViewController__viewDidLoad_block_func_0(

                                     (void *)__ViewController__viewDidLoad_block_func_0,

                                     &__ViewController__viewDidLoad_block_desc_0_DATA)

                                        );

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);   // 结构体的第一个变量地址即为结构体地址,所以block 可以直接强制转换为__block_impl ,然后调用FuncPtr 方法,
}
//移除强制转换代码
        // 定义block变量
        void (*block)(void) = &__ViewController__viewDidLoad_block_func_0(
                                                   __ViewController__viewDidLoad_block_func_0,
                                                   &__ViewController__viewDidLoad_block_desc_0_DATA
                                                   );
        // 执行block内部的代码
block->FuncPtr(block); //
}
}

不难看出其中的_ViewController__viewDidLoad_block_impl_0就是block的一个C++的实现(最后面的_0代表是main中的第几个block),也就是说block 的本质是结构体。

转换过程

1、首先将block 内部的方法封装到 static void __ViewController__viewDidLoad_block_func_0 中去

2、为block 生成一个结构体的数据结果 结构体内部会有两个成员

struct __block_impl impl;

struct __ViewController__viewDidLoad_block_desc_0* Desc;

3、调用结构体的构造函数创建出一个结构体对象,并且将结构体的地址赋值给对象

void (*block)(void) = &__ViewController__viewDidLoad_block_func_0(

                                                   __ViewController__viewDidLoad_block_func_0,

                                                   &__ViewController__viewDidLoad_block_desc_0_DATA

                                                   );

其中__block_impl的定义如下:

struct __block_impl {

void *isa; 指向所属类的指针,也就是block的类型

int Flags; 标志变量,在实现block的内部操作时会用到

int Reserved; 保留变量

void *FuncPtr; block执行时调用的函数指针

};

可以看出,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。

___ViewController__viewDidLoad_block_desc_0的定义如下:

static struct __ViewController__viewDidLoad_block_desc_0 {

  size_t reserved;      //保留字段

  size_t Block_size;    //block大小(sizeof(struct __main_block_impl_0))

} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, 

                                  sizeof(struct __ViewController__viewDidLoad_block_impl_0)

 };

以上代码在定义__ViewController__viewDidLoad_block_desc_0结构体时,同时创建了__ViewController__viewDidLoad_block_desc_0_DATA,并给它赋值,以供在main函数中对__ViewController__viewDidLoad_block_desc_0_0进行初始化。

__ViewController__viewDidLoad_block_impl_0定义了显式的构造函数,其函数体如下:

 __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

可以看出,

以上就是根据编译转换的结果,对一个简单block的解析,后面会将block操作不同类型的外部变量,对block结构的影响进行相应的说明。

block实际结构

接下来观察下Block_private.h文件中对block的相关结构体的真实定义:

/* Revised new layout. */

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就是一个里面存储了指向函数体中包含定义block时的代码块的函数指针,以及block外部上下文变量等信息的结构体。

block的类型

Block 本质类型:继承关系 NSGlobalBlock : NSBlock : NSObject

block的常见类型有3种:

image.png image.png

每一种类型的block调用copy后的结果如下所示

image.png

附上APUE的进程虚拟内存段分布图:

image.png

进程虚拟内存空间分布

其中前2种在Block.h种声明,后1种在Block_private.h中声明,所以最后1种基本不会在源码中出现。

由于无法直接创建_NSConcreteMallocBlock类型的block,所以这里只对前面2种进行手动创建分析,最后1种通过源代码分析。

NSConcreteGlobalBlock和NSConcreteStackBlock

首先,根据前面两种类型,编写以下代码:

void (^globalBlock)() = ^{
};

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

void (^stackBlock1)() = ^{

};

}
return 0;
}

对其进行编译转换后得到以下缩略代码:

// globalBlock

struct __globalBlock_block_impl_0 {

struct __block_impl impl;

struct __globalBlock_block_desc_0* Desc;

__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {

impl.isa = &_NSConcreteGlobalBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

...

// stackBlock

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;

}

};

...

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

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

}

return 0;

}

可以看出globalBlock的isa指向了_NSConcreteGlobalBlock,即在全局区域创建,编译时具体的代码就已经确定在上图中的代码段中了,block变量存储在全局数据存储区;stackBlock的isa指向了_NSConcreteStackBlock,即在栈区创建。

NSConcreteMallocBlock

接下来是在堆中的block,堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。由于block的拷贝最终都会调用_Block_copy_internal函数,所以观察这个函数就可以知道堆中block是如何被创建的了:

static void *_Block_copy_internal(const void *arg, const int flags) {

struct Block_layout *aBlock;

...

aBlock = (struct Block_layout *)arg;

...

// Its a stack block. Make a copy.

if (!isGC) {

// 申请block的堆内存

struct Block_layout *result = malloc(aBlock->descriptor->size);

if (!result) return (void *)0;

// 拷贝栈中block到刚申请的堆内存中

memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

// reset refcount

result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed

result->flags |= BLOCK_NEEDS_FREE | 1;

// 改变isa指向_NSConcreteMallocBlock,即堆block类型

result->isa = _NSConcreteMallocBlock;

if (result->flags & BLOCK_HAS_COPY_DISPOSE) {

//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);

(*aBlock->descriptor->copy)(result, aBlock); // do fixup

}

return result;

}

else {

...

}

}

从以上代码以及注释可以很清楚的看出,函数通过memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock。

block主要的一些学问就出在栈中block向堆中block的转移过程中了。

捕捉变量对block结构的影响

接下来会编译转换捕捉不同变量类型的block,以对比它们的区别。

image.png
 局部变量
 void test()
 {
     int age = 10;   //auto 
     static int height = 10; 
     block = ^{

        // age的值捕获进来(capture)
         NSLog(@"age is %d, height is %d", age, height);
     };
     age = 20;
     height = 20;
 }
 int main(int argc, const char * argv[]) {
     @autoreleasepool {

        test,
        block();
     }
     return 0;
 }
当调用test() 的时候,age 会从内存中销毁,当block 再次调用的时候,只能调用封装在block 内部的值,在内存中找到不到,
但是static 修饰后会在常量区存在,不会被销毁,什么时候调用都会存在
前:
- (void)test

{

int a = 10;

^{a;};

a = 20;

}

后:

struct __Person__test_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

int a;
// a(_a)是构造函数的参数列表初始化形式,相当于a = _a。从_I_Person_test看,传入的就是a

__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

int a = __cself->a; // bound by copy

a;}

static struct __Person__test_block_desc_0 {

size_t reserved;

size_t Block_size;

} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};

static void _I_Person_test(Person * self, SEL _cmd) {

int a;

(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);

}

可以看到,block相对于文章开头增加了一个int类型的成员变量,他就是用来存储外部变量a的。可以看出,这次拷贝只是一次值传递。并且当我们想在block中进行以下操作时,将会发生错误

^{a = 10;};

编译器会提示

image.png

错误提示

。因为_I_Person_test函数中的a和__Person__test_block_func_0函数中的a并没有在同一个作用域,所以在block对a进行赋值是没有意义的,所以编译器给出了错误。我们可以通过地址传递来消除以上错误:

- (void)test

{

int a = 0;

// 利用指针p存储a的地址

int *p = &a;

^{

// 通过a的地址设置a的值

*p = 10;

};

}

但是变量a的生命周期是和方法test的栈相关联的,当test运行结束,栈随之销毁,那么变量a就会被销毁,p也就成为了野指针。如果block是作为参数或者返回值,这些类型都是跨栈的,也就是说再次调用会造成野指针错误。

###### 局部静态变量

前

- (void)test

{

static int a;

^{

a = 10;

};

}

后:

struct __Person__test_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

int *a;

__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

int *a = __cself->a; // bound by copy

// 这里通过局部静态变量a的地址来对其进行修改

(*a) = 10;

}

static struct __Person__test_block_desc_0 {

size_t reserved;

size_t Block_size;

} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};

static void _I_Person_test(Person * self, SEL _cmd) {

static int a;

// 传入a的地址

(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);

}
需要注意一点的是静态局部变量是存储在静态数据存储区域的,也就是和程序拥有一样的生命周期,也就是说在程序运行时,都能够保证block访问到一个有效的变量。但是其作用范围还是局限于定义它的函数中,所以只能在block通过静态局部变量的地址来进行访问。
关于变量的存储我原来的这篇博客有提及:c语言臆想--全局---局部变量
全局变量
前:

// 全局静态

static int a;

// 全局

int b;

- (void)test

{

^{

a = 10;

b = 10;

};

}

后:

static int a;

int b;

struct __Person__test_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int flags=0) {

impl.isa = &_NSConcreteStackBlock;

impl.Flags = flags;

impl.FuncPtr = fp;

Desc = desc;

}

};

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

a = 10;

b = 10;

}

static struct __Person__test_block_desc_0 {

size_t reserved;

size_t Block_size;

} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};

static void _I_Person_test(Person * self, SEL _cmd) {

(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);

}

可以看出,因为全局变量都是在静态数据存储区,在程序结束前不会被销毁,所以block直接访问了对应的变量,而没有在__Person__test_block_impl_0结构体中给变量预留位置。

###### 当block内部访问了对象类型的auto变量时

###### 如果block是在栈上,将不会对auto变量产生强引用 

###### 如果block被拷贝到堆上 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数 _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用 

###### 如果block从堆上移除 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量(release) 

###### __block修饰的变量

前:

- (void)test

{

__block int a;

^{

a = 10;

};

}

后:

struct __Block_byref_a_0 {

void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};

struct __Person__test_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

__Block_byref_a_0 *a; // by ref

__Person__test_block_impl_0(void *fp, struct __Person__test_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 __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

__Block_byref_a_0 *a = __cself->a; // bound by ref

// 注意,这里的_forwarding用来保证操作的始终是堆中的拷贝a,而不是栈中的a

(a->__forwarding->a) = 10;

}

static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __Person__test_block_desc_0 {

size_t reserved;

size_t Block_size;

void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);

void (*dispose)(struct __Person__test_block_impl_0*);

} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

static void _I_Person_test(Person * self, SEL _cmd) {

// __block将a包装成了一个对象

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

;

(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);

}

可以看到,对比上面的结果,明显多了__Block_byref_a_0结构体,这个结构体中含有isa指针,所以也是一个对象,它是用来包装局部变量a的。当block被copy到堆中时,__Person__test_block_impl_0的拷贝辅助函数__Person__test_block_copy_0会将__Block_byref_a_0(__block 变量)拷贝至堆中,所以即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作。其中__Block_byref_a_0成员指针__forwarding用来指向它在堆中的拷贝,其依据源码如下:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {

struct Block_byref **destp = (struct Block_byref **)dest;

struct Block_byref *src = (struct Block_byref *)arg;

...

// 堆中拷贝的forwarding指向它自己

copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)

// 栈中的forwarding指向堆中的拷贝

src->forwarding = copy; // patch stack to point to heap copy

...

}

这样做是为了保证操作的值始终是堆中的拷贝,而不是栈中的值。(处理在局部变量所在栈还没销毁,就调用block来改变局部变量值的情况,如果没有__forwarding指针,则修改无效)

__block 的内存管理

当block在栈上时,并不会对__block变量产生强引用

当block被copy到堆时

image.png

__block变量 在 block0 的时候已经被拷贝到堆上,当Block1 调用__Block变量时,不在重复拷贝,直接调用

当block从堆中移除时

image.png

至于block如何实现对局部变量的拷贝,下面会详细说明。

self隐式循环引用

前:

@implementation Person

{

int _a;

void (^_block)();

}

- (void)test

{

void (^_block)() = ^{

_a = 10;

};

}

@end

后:

struct __Person__test_block_impl_0 {

struct __block_impl impl;

struct __Person__test_block_desc_0* Desc;

// 可以看到,block强引用了self

Person *self;

__Person__test_block_impl_0(void *fp, struct __Person__test_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__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

Person *self = __cself->self; // bound by copy

(*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;

}

static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __Person__test_block_desc_0 {

size_t reserved;

size_t Block_size;

void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);

void (*dispose)(struct __Person__test_block_impl_0*);

} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

static void _I_Person_test(Person * self, SEL _cmd) {

void (*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);

}

如果在编译转换前,将_a改成self.a,能很明显地看出是产生了循环引用(self强引用block,block强引用self)。那么使用_a呢?经过编译转换后,依然可以在__Person__test_block_impl_0看见self的身影。且在函数_I_Person_test中,传入的参数也是self。通过以下语句,可以看出,不管是用什么形式访问实例变量,最终都会转换成self+变量内存偏移的形式。所以在上面例子中使用_a也会造成循环引用。

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

Person *self = __cself->self; // bound by copy

// self+实例变量a的偏移值

(*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;

}

## 循环引用

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

##   @autoreleasepool {

##     MJPerson *person = [ [MJPerson alloc] init];

##     person.age = 10;

##     person.block = rf

##     NSLog(@"age is %d", person.age);

##   };

##     NSLog(@"11111111111" );

##     return 0;

## }

image.png 上述代码的内存图

image.png

当程序结束局部运行的时候,person 变量销毁,但是block 和MIJPerson 还存在相互引用

上文提及到了block辅助copy与dispose处理函数,这里分析下这两个函数的内部实现。在捕获变量为__block修饰的基本类型,或者为对象时,block才会有这两个辅助函数。

block捕捉变量拷贝函数为_Block_object_assign。在调用复制block的函数_Block_copy_internal时,会根据block有无辅助函数来对捕捉变量拷贝函数_Block_object_assign进行调用。而在_Block_object_assign函数中,也会判断捕捉变量包装而成的对象(Block_byref结构体)是否有辅助函数,来进行调用。

__block修饰的基本类型的辅助函数

编写以下代码:

typedef void(^Block)();

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

@autoreleasepool {

__block int a;

Block block = ^ {

a;

};

}

转换成C++代码后:

typedef void(*Block)();

// __block int a

struct __Block_byref_a_0 {

void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};

// block

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;

}

};

// block函数体

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

}

// 辅助copy函数

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

// 辅助dispose函数

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;

// 这里创建了,并将a的flags设置为0

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

;

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

}

return 0;

}

从上面代码中,被__block修饰的a变量变为了__Block_byref_a_0类型,根据这个格式,从源码中查看得到相似的定义:

struct Block_byref {

void *isa;

struct Block_byref *forwarding;

int flags; /* refcount; */

int size;

void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);

void (*byref_destroy)(struct Block_byref *);

/* long shared[0]; */

};

// 做下对比

struct __Block_byref_a_0 {

void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};

// flags/_flags类型

enum {

/* See function implementation for a more complete description of these fields and combinations */

// 是一个对象

BLOCK_FIELD_IS_OBJECT = 3, /* id, NSObject, __attribute__((NSObject)), block, ... */

// 是一个block

BLOCK_FIELD_IS_BLOCK = 7, /* a block variable */

// 被__block修饰的变量

BLOCK_FIELD_IS_BYREF = 8, /* the on stack structure holding the __block variable */

// 被__weak修饰的变量,只能被辅助copy函数使用

BLOCK_FIELD_IS_WEAK = 16, /* declared __weak, only used in byref copy helpers */

// block辅助函数调用(告诉内部实现不要进行retain或者copy)

BLOCK_BYREF_CALLER = 128 /* called from __block (byref) copy/dispose support routines. */

};

可以看出,__block将原来的基本类型包装成了对象。因为以上两个结构体的前4个成员的类型都是一样的,内存空间排列一致,所以可以进行以下操作:

// 转换成C++代码

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

// _Block_object_assign源码

void _Block_object_assign(void *destAddr, const void *object, const int flags) {

...

else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {

// copying a __block reference from the stack Block to the heap

// flags will indicate if it holds a __weak reference and needs a special isa

_Block_byref_assign_copy(destAddr, object, flags);

}

...

}

// _Block_byref_assign_copy源码

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {

// 这里因为前面4个成员的内存分布一样,所以直接转换后,使用Block_byref的成员变量名,能访问到__Block_byref_a_0的前面4个成员

struct Block_byref **destp = (struct Block_byref **)dest;

struct Block_byref *src = (struct Block_byref *)arg;

...

else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {

// 从main函数对__Block_byref_a_0的初始化,可以看到初始化时将flags赋值为0

// 这里表示第一次拷贝,会进行复制操作,并修改原来flags的值

// static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;

// 可以看出,复制后,会并入BLOCK_NEEDS_FREE,后面的2是block的初始引用计数

...

copy->flags = src->flags | _Byref_flag_initial_value;

...

}

// 已经拷贝到堆了,只增加引用计数

else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {

latching_incr_int(&src->forwarding->flags);

}

// 普通的赋值,里面最底层就*destptr = value;这句表达式

_Block_assign(src->forwarding, (void **)destp);

}

主要操作都在代码注释中了,总体来说,__block修饰的基本类型会被包装为对象,并且只在最初block拷贝时复制一次,后面的拷贝只会增加这个捕获变量的引用计数。

###### 对象的辅助函数

*   没有__block修饰

typedef void(^Block)();

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

@autoreleasepool {

NSObject *a = [[NSObject alloc] init];

Block block = ^ {

a;

};

}

return 0;

}

首先,在没有__block修饰时,对象编译转换的结果如下,删除了一些变化不大的代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

NSObject *a = __cself->a; // bound by copy

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, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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),

对象在没有__block修饰时,并没有产生__Block_byref_a_0结构体,只是将标志位修改为BLOCK_FIELD_IS_OBJECT。而在_Block_object_assign中对应的判断分支代码如下:

...

else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {

_Block_retain_object(object);

_Block_assign((void *)object, destAddr);

}

...

可以看到,block复制时,会retain捕捉对象,以增加其引用计数。

*   有__block修饰

typedef void(^Block)();

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

@autoreleasepool {

__block NSObject *a = [[NSObject alloc] init];

Block block = ^ {

a;

};

}

return 0;

}

在这种情况下,编译转换的部分结果如下:

struct __Block_byref_a_0 {

void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

void (*__Block_byref_id_object_copy)(void*, void*);

void (*__Block_byref_id_object_dispose)(void*);

NSObject *a;

};

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, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....};

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

}

// 以下的40表示__Block_byref_a_0对象a的位移(4个指针(32字节)+2个int变量(8字节)=40字节)

static void __Block_byref_id_object_copy_131(void *dst, void *src) {

_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);

}

static void __Block_byref_id_object_dispose_131(void *src) {

_Block_object_dispose(*(void * *) ((char*)src + 40), 131);

}

可以看到,对于对象,__Block_byref_a_0另外增加了两个辅助函数__Block_byref_id_object_copy、__Block_byref_id_object_dispose,以实现对对象内存的管理。其中两者的最后一个参数131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT,BLOCK_BYREF_CALLER表示在内部实现中不对a对象进行retain或copy;以下为相关源码:

if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {

...

else {

// do *not* retain or *copy* __block variables whatever they are

_Block_assign((void *)object, destAddr);

}

}

_Block_byref_assign_copy函数的以下代码会对上面的辅助函数(__Block_byref_id_object_copy_131)进行调用;570425344表示BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR,所以会执行以下相关源码:

if (src->flags & BLOCK_HAS_COPY_DISPOSE) {

// Trust copy helper to copy everything of interest

// If more than one field shows up in a byref block this is wrong XXX

copy->byref_keep = src->byref_keep;

copy->byref_destroy = src->byref_destroy;

(*src->byref_keep)(copy, src);

}

ARC中block的工作

image.png

苹果说明

苹果文档提及,在ARC模式下,在栈间传递block时,不需要手动copy栈中的block,即可让block正常工作。主要原因是ARC对栈中的block自动执行了copy,将_NSConcreteStackBlock类型的block转换成了_NSConcreteMallocBlock的block。

block试验

下面对block做点实验:

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

@autoreleasepool {

int i = 10;

void (^block)() = ^{i;};

__weak void (^weakBlock)() = ^{i;};

void (^stackBlock)() = ^{};

// ARC情况下

// 创建时,都会在栈中

// <__NSStackBlock__: 0x7fff5fbff730>

NSLog(@"%@", ^{i;});

// 因为block为strong类型,且捕获了外部变量,所以赋值时,自动进行了copy

// <__NSMallocBlock__: 0x100206920>

NSLog(@"%@", block);

// 如果是weak类型的block,依然不会自动进行copy

// <__NSStackBlock__: 0x7fff5fbff728>

NSLog(@"%@", weakBlock);

// 如果block是strong类型,并且没有捕获外部变量,那么就会转换成__NSGlobalBlock__

// <__NSGlobalBlock__: 0x100001110>

NSLog(@"%@", stackBlock);

// 在非ARC情况下,产生以下输出

// <__NSStackBlock__: 0x7fff5fbff6d0>

// <__NSStackBlock__: 0x7fff5fbff730>

// <__NSStackBlock__: 0x7fff5fbff700>

// <__NSGlobalBlock__: 0x1000010d0>

}

return 0;

}

可以看出,ARC对类型为strong且捕获了外部变量的block进行了copy。并且当block类型为strong,但是创建时没有捕获外部变量,block最终会变成NSGlobalBlock类型(这里可能因为block中的代码没有捕获外部变量,所以不需要在栈中开辟变量,也就是说,在编译时,这个block的所有内容已经在代码段中生成了,所以就把block的类型转换为全局类型)

block作为参数传递

再来看下使用在栈中的block需要注意的情况:

NSMutableArray *arrayM;

void myBlock()

{

int a = 5;

Block block = ^ {

NSLog(@"%d", a);

};

[arrayM addObject:block];

NSLog(@"%@", block);

}

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

@autoreleasepool {

arrayM = @[].mutableCopy;

myBlock();

Block block = [arrayM firstObject];

// 非ARC这里崩溃

block();

}

// ARC情况下输出

// <__NSMallocBlock__: 0x100214480>

// 非ARC情况下输出

// <__NSStackBlock__: 0x7fff5fbff738>

// 崩溃,野指针错误

可以看到,ARC情况下因为自动执行了copy,所以返回类型为NSMallocBlock,在函数结束后依然可以访问;而非ARC情况下,需要我们手动调用[block copy]来将block拷贝到堆中,否则因为栈中的block生命周期和函数中的栈生命周期关联,当函数退出后,相应的堆被销毁,block也就不存在了。

如果把block的以下代码删除:

NSLog(@"%d", a);

那么block就会变成全局类型,在main中访问也不会出崩溃。

block作为返回值

在非ARC情况下,如果返回值是block,则一般这样操作:

return [[block copy] autorelease];

对于外部要使用的block,更趋向于把它拷贝到堆中,使其脱离栈生命周期的约束。

block属性

这里还有一点关于block类型的ARC属性。上文也说明了,ARC会自动帮strong类型且捕获外部变量的block进行copy,所以在定义block类型的属性时也可以使用strong,不一定使用copy。也就是以下代码:

/** 假如有栈block赋给以下两个属性 **/

// 这里因为ARC,当栈block中会捕获外部变量时,这个block会被copy进堆中

// 如果没有捕获外部变量,这个block会变为全局类型

// 不管怎么样,它都脱离了栈生命周期的约束

@property (strong, nonatomic) Block *strongBlock;

// 这里都会被copy进堆中

@property (copy, nonatomic) Block *copyBlock;


面试题

封装了函数调用以及调用环境的OC对象

变量会被包装成一个block 对象,解决block 无法修改内部auto 变量的问题,需要注意内存管理,

block一旦没有进行copy操作,就不会在堆上 使用注意:循环引用问题 block在,在ARC下,用strong 和copy 没有区别,在MRC 下有区别,copy 会拷贝到堆上,strong不会拷贝,引用计数会加1

image.png
  1. Strong 修饰weakself 为了编译器编译,weakSelf 无法直接调用_age,

  2. 为了防止self 提前释放,

参考博文

Block技巧与底层解析https://www.jianshu.com/p/51d04b7639f1


上一篇下一篇

猜你喜欢

热点阅读