Block底层学习

2018-11-11  本文已影响0人  朝夕向背

Block底层本质

我们来看一段代码

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void(^block)(int, int) = ^(int b,int c){
            NSLog(@"%d",a);
            NSLog(@"Hello World!");
        };       
        block(10,10);
    }
    return 0;
}

把上面这段代码转化为C++底层语言,转化后:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        //定义block变量
        void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        //执行block内部的代码
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

在C++代码中,block代码块底层调用__main_block_impl_0。这句代码调用以下代码

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

我们发现,block的底层也是一个结构体。搜索struct __block_impl

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

block的第一个结构体成员是一个isa指针。这说明,block也是一个OC对象。
__main_block_desc_0结构体成员包括两个参数,如下:

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

__main_block_func_0函数内部封装了block执行逻辑的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int b, int c) {
  int a = __cself->a; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2n_ksb7n0n131n2y7v9xcf411fm0000gn_T_main_7cbb05_mi_0,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2n_ksb7n0n131n2y7v9xcf411fm0000gn_T_main_7cbb05_mi_1);
        }

struct __block_impl结构体内,有一个成员void *FuncPtr,

Block变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制。
先来看一段代码

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void (^block)(void) = ^{
            NSLog(@"%d",a);
        };
        a = 20;
        block();
    }
    return 0;
}

执行上面这段代码,打印值是10,而不是20。之所以执行block(),结果是10,而不是20,这个就使用了变量捕获
我们来看下C++底层代码

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        void (*block)(void) = ((void (*)())&__main_block_impl_0(
                                                                (void *)__main_block_func_0,
                                                                &__main_block_desc_0_DATA,
                                                                a));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

在上面的代码中,编译时,在block内部已经捕获到a值。然后传递到__main_block_impl_0

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

在上面代码中,根据传递过来的值,赋值给NSLog(@"%d",a);。当a = 20;时,仅仅是改变int a = 10;的值。而block内部获取不到a的值。

变量捕获

Block类型

因为block是一个对象,所以block也是有类型的。block有三种类型,可以通过class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型。

ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:

对象类型的auto变量以及Block的内存管理

类似于局部变量,有auto修饰的对象在block内部,也会存在block类型。来看一段代码

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,  assign) int age;
@end

#import "Person.h"

@implementation Person
- (void)dealloc{
    NSLog(@"delloc--Person");
}
@end

main.m
#import <Foundation/Foundation.h>
#import "Person.h"
typedef void(^HYBlock) (void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HYBlock myBlock;
        {
            Person *p = [[Person alloc] init];
            p.age = 10;
            myBlock = ^{
                NSLog(@"%d",p.age);
            };
            myBlock();
        }
    }
    return 0;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

✔️_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe unretained)做出相应的操作,形成强引用(retain)或者弱引用。

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

✔️_Block_object_dispose函数会自动释放引用的auto变量。

Block修饰符

我们知道不能再block内部修改外部变量的值,我们来看下原因:

#import <Foundation/Foundation.h>
typedef void(^HBlock) (void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        HBlock  block= ^{
            NSLog(@"%d",a);
        };
        block();
    }
    return 0;
}

转化为C++代码

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        HBlock block= ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

在上面的代码中,定义了int a = 10;。而输出这个变量值是在下面这个函数中

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2n_ksb7n0n131n2y7v9xcf411fm0000gn_T_main_5b923f_mi_0,a);
        }

因为变量a不是全局变量,只是局部变量,所以不能在另外一个函数,修改变量值。

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

在上面的代码中,int *a;传递的是变量的地址值,在block内部,先找到变量的地址值,直接修改变量a的值,而不是直接修改变量值。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
...
};

从上面的代码可以看出,使用__block修饰变量,在__main_block_impl_0内部,变量a为__Block_byref_a_0 *a; // by ref。而__Block_byref_a_0是一个对象(内部有isa指针)。在这个结构体内部,有成员变量,存储变量值。

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

当修改变量值时,利用__Block_byref_a_0指针先找到结构体,通过变量名找到__forwarding,在通过__forwarding找到变量,来修改变量值。

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) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2n_ksb7n0n131n2y7v9xcf411fm0000gn_T_main_b8c75c_mi_0,(a->__forwarding->a));
        }

block循环引用

循环引用是指两个或以上对象互相强引用,导致所有对象无法释放的现象。这是内存泄露的一种情况。

#import <Foundation/Foundation.h>

typedef void(^HYBlock)(void);
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) HYBlock block;
@end

#import "Person.h"

@implementation Person
- (void)dealloc{
    NSLog(@"%s",__func__);
}
@end


#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 20;
        person.block = ^{
            NSLog(@"%d", person.age);
        };
        person.block();
    }
    return 0;
}

在上面的代码中,当执行person.block();时,Person对象并没有释放,产生循环引用。
我们来看下,产生循环引用的原因,首先转化为C++代码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
...
};

在ARC环境下,HYBlock被拷贝到堆上,当内部调用person时,则在函数__main_block_impl_0内部,Person对象生成Person *__strong person;也即是强引用这个对象。Person对象强引用HYBlockHYBlock又强引用Person对象,则HYBlock不释放,Person对象也不会释放。

解决循环引用

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
...
};

使用weak修饰对象,则在函数__main_block_impl_0内部,不在强引用Person对象。__unsafe_unretained同理,也不在强引用Person对象。

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       __block Person *person = [[Person alloc] init];
        person.age = 20;
        person.block = ^{
            NSLog(@"%d", person.age);
            person = nil;
        };
        person.block();
    }
    return 0;
}
上一篇 下一篇

猜你喜欢

热点阅读