block的原理探索-底层结构

/ 0

概述

block 本质上也是一个 OC 对象,它内部也有个 isa 指针。

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

block 的底层结构:

证明 blockOC 对象:

void (^block)(void) = ^{};
Class blockClass = [block class];
NSLog(@"%@ -> %@ -> %@", blockClass, [blockClass superclass], [[blockClass superclass] superclass]); // __NSGlobalBlock__ -> NSBlock -> NSObject

block基本使用

定义 block 语法:

// 定义block
返回值类型 (^block变量名)(参数列表) = ^代码段返回值类型(参数列表) {
    代码段;
};

// 执行block
block变量名();

利用类型别名简化 block

// 定义block类型别名
typedef 返回值类型 (^Block类型名)(参数列表);

// 定义block
Block类型名 block变量名 = ^代码段返回值类型(参数列表) {
    代码段;
};

// 执行block
block变量名();

block底层结构初探

定义一个简单的 block

int age = 10;
void (^block)(void) = ^{
    NSLog(@"This is a block! %d", age);
};

然后转成 C++ 代码,可以拿到下面结构(暂时忽略了函数定义,只保留结构)。可以发现捕获的 age 变量,也定义到了这个结构体中:

struct __block_impl {
    void *isa; // isa指针,可以看出是OC对象
    int Flags;
    int Reserved;
    void *FuncPtr; // block代码块里的代码封装的函数地址
};

struct __main_block_desc_0 {
   size_t reserved;
   size_t Block_size; // block大小
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age; // 捕获的age变量
};

在block代码块里打个断点,通过反汇编工具可以看到这个函数地址确实就是 FuncPtr 指针指向的地址:

block变量捕获

为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制:

原始代码:

int no = 10;
static int size = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        static int height = 10;

        void (^block)(void) = ^{
            NSLog(@"%d, %d, %d, %d", age, height, no, size); // 10, 20, 20, 20
        };

        age = 20;
        height = 20;
        no = 20;
        size = 20;

        block();
    }
    return 0;
}

转为 C++ 代码:

int no = 10;
static int size = 10;

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;
    int age; // 捕获了age
    int *height; // 捕获了height

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    int *height = __cself->height; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_main_b67dee_mi_0, age, (*height), no, size);
}

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 age = 10;
        static int height = 10;

        // 声明block
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));

        age = 20;
        height = 20;
        no = 20;
        size = 20;

        // 调用block
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

从上面代码可以看出来,变量捕获是通过 __main_block_impl_0 结构体的构造函数传递的,其中自动局部变量值传递静态局部变量指针传递

而全局变量,则不会捕获,因为全局变量是可以直接访问的,没有必要捕获。

对象和成员变量捕获

原始代码:

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

@implementation Person
- (void)test {
    void (^block)(void) = ^{
        NSLog(@"%@, %d", self, self.age); // 20
    };
    self.age = 20;
    block();
}
@end

转为 C++ 代码:

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

struct __Person__test_block_impl_0 {
    struct __block_impl impl;
    struct __Person__test_block_desc_0* Desc;
    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

    // 只捕获了对象,而对象的成员变量是通过对象直接访问的
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_25_pb1crnzn29gcfxnjh75vhb900000gn_T_Person_06fde7_mi_0, self, ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}

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

// test方法第一个参数是对象本身引用,第二个参数是方法SEL。都是局部变量
static void _I_Person_test(Person * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));

    ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setAge:"), 20);

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

static int _I_Person_age(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; }

从上面代码可以看出在 block 中调用了对象和对象中的成员变量,block 只捕获了对象,因为对象可以直接访问到本身的成员变量。

如果是在类方法中,self 则表示类对象,也是作为方法的第一个隐式参数,也会被 block 捕获。