iOS中blcok的本质详解

如果您希望拥有更好的阅读体验,欢迎访问 我的开发笔记


blcok的本质就是一个对象

main.m中的代码

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

    int age = 18;
    static int height = 55;
    void (^block)(int) = ^(int pa) {
        NSLog(@"--block-局部auto变量的捕获--%d",age);
        NSLog(@"--block-局部static变量的捕获--%d",height);
    };

    NSLog(@"---%@",[block class]);
    NSLog(@"---%@",[[block class] superclass]);
    NSLog(@"---%@",[[[block class] superclass] superclass]);
    NSLog(@"---%@",[[[[block class] superclass] superclass] superclass]);

    block(10);
}

打印可见block也是一个oc对象

2019-06-08 11:38:23.353777+0800 Tes[41864:3485021] —NSStackBlock

2019-06-08 11:38:23.354053+0800 Tes[41864:3485021] —__NSStackBlock

2019-06-08 11:38:23.354091+0800 Tes[41864:3485021] —NSBlock

2019-06-08 11:38:23.354134+0800 Tes[41864:3485021] —NSObject

2019-06-08 11:38:23.354275+0800 Tes[41864:3485021] --block-局部auto变量的捕获–18

2019-06-08 11:38:23.354314+0800 Tes[41864:3485021] --block-局部static变量的捕获–56

将以上代码通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转化为c++代码,可以看到block的结构

//block的结构体
struct __main_block_impl_0 {

    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age;
    int *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;
    }
};

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

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

blcok的实现作为第一个参数传过去,捕获的变量在后边作为参数,调用__main_block_impl_0结构体的构造函数,实现block的创建

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

    int age = 18;
    static int height = 55;
    void (*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));

    //把上边转化的代码去掉强制转换
    void (*block)(int) = &__main_block_impl_0(
    __main_block_func_0,
    &__main_block_desc_0_DATA,
    age,
    &height);
}

block的具体实现被封装为的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int pa) {
    int age = __cself->age; // bound by copy
    int *height = __cself->height; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_vnxk1kl519z1ks1hddc4mr0h0000gn_T_main_53cf21_mi_0,age);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_vnxk1kl519z1ks1hddc4mr0h0000gn_T_main_53cf21_mi_1,(*height));
}

block的调用:block(10);因为block__main_block_impl_0类型,所以通过取出__block_impl类型的imp,然后再通过imp取出FuncPtr进行调用.但是这里为什么是直接把blcok强制转为__block_impl类型?因为impl作为__main_block_impl_0结构体的首元素,而__main_block_impl_0变量的地址就是它的首元素的地址,所以这里强制转换得以实现

((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10);
//把上边转化的代码去掉强制转换
(block->FuncPtr)(block, 10);

//理论上应该转为
(block->imp->FuncPtr)(block, 10);

blcok的变量捕获

  • 局部变量的捕获
    通过查看__main_block_impl_0结构体,看到多了个int age和int *height
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age;
    int *height;
}

//生成的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int pa) {

    //使用的时候,会从本block变量内部获取
    int age = __cself->age; // bound by copy
    int *height = __cself->height; // bound by copy
}

auto修饰的局部变量(局部变量,默认就是auto): 因为它是局部变量,会自动销毁,而如果在block调用前就销毁了,那么block调用时,获取到的值就会出问题,所以实行值传递.

static修饰的局部变量: 虽然它也是局部变量,但是因为被static修饰,所以出了变量作用域,不会自动销毁,那么block调用时,依然可以获取到正确的值,所以指针传递不会造成问题.

  • 全局变量不会捕获
int age_ = 18;
static int height_ = 55;

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

    void (^block)(int) = ^(int pa) {
        NSLog(@"--block-局部auto变量的捕获--%d",age_);
        NSLog(@"--block-局部static变量的捕获--%d",height_);
    };

    NSLog(@"---%@",[block class]);
    NSLog(@"---%@",[[block class] superclass]);
    NSLog(@"---%@",[[[block class] superclass] superclass]);
    NSLog(@"---%@",[[[[block class] superclass] superclass] superclass]);

    block(10);
}

转为c++查看blcok的结构体,发现并没有age_height_,因为他们是全局变量,随时,哪里都可以访问到,所以没必要捕获

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
}
//生成的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int pa) {

    //使用的时候,直接调用全局变量
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_vnxk1kl519z1ks1hddc4mr0h0000gn_T_main_ccc8d0_mi_2,age_);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_vnxk1kl519z1ks1hddc4mr0h0000gn_T_main_ccc8d0_mi_3,height_);
}

全局变量不会捕获

blcok的类型

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

    int age = 18;

    //__NSStackBlock__
    NSLog(@"----%@",[^(int pa) {
        NSLog(@"--block---%d",age);
    } class]);

    //__NSGlobalBlock__
    void (^block)(int) = ^(int pa) {
        NSLog(@"---");
    };
    NSLog(@"----%@",[block class]);

    //__NSMallocBlock__
    void (^block1)(int) = ^(int pa) {
        NSLog(@"--block---%d",age);
    };
    NSLog(@"----%@",[block1 class]);

}

打印:

2019-06-08 11:51:08.351813+0800 Tes[42036:3500881] ----NSStackBlock

2019-06-08 11:51:08.352169+0800 Tes[42036:3500881] ----NSGlobalBlock

2019-06-08 11:51:08.352201+0800 Tes[42036:3500881] ----NSMallocBlock

内存中从地地址到高地址:

  • NSGlobalBlock: 没有访问auto类型的局部变量(是GlobalBlock),存放在数据段

  • NSMallocBlock: (是MallocBlock),存放在堆里

  • NSStackBlock : 访问auto类型的局部变量,存放在栈里

__NSStackBlock__类型的blcok不会对oc对象有强引用

typedef void(^RTBlock)(int);
int main(int argc, const char * argv[]) {

    RTBlock block;
    {
        RTPerson *person = [[RTPerson alloc] init];

        NSLog(@"--block---%@",[^(int pa) {
            NSLog(@"--block---%@",person);
        } class]);
    }

    NSLog(@"--block---%@",[block class]);

}

打印:

2019-06-10 13:04:08.687481+0800 Tes[42877:3569172] --block—NSStackBlock

2019-06-10 13:04:08.687816+0800 Tes[42877:3569172] dealloc

2019-06-10 13:04:08.687839+0800 Tes[42877:3569172] --block—(null)

因为block的类型是stack所以并不会对person进行强引用

只有__NSMallocBlock__类型的blcok对oc对象有强引用

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

RTPerson *person = [[RTPerson alloc] init];

    //__NSMallocBlock__
    void (^block1)(int) = ^(int pa) {
        NSLog(@"--block---%@",person);
    };
    NSLog(@"----%@",[block1 class]);

}

转为c++代码后发现,__main_block_desc_0结构体中多了两个函数指针

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

__main_block_copy_0,这个函数会根据这个block捕获的oc对象的类型是弱引用还是强引用是否来对它进行强引用

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

__main_block_dispose_0,这个函数用来释放强引用

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

Block内部变量的修改

  1. 局部static修饰的变量和全局变量可以在block内部修改,根据上边的blcok结构可以看出
  • 局部static变量用 *age = 10 修改
  • 全局变量直接修改
  1. __block修饰的变量可以在block内部修改
typedef void(^RTBlock)(int);
int main(int argc, const char * argv[]) {

    __block int age = 18;
    RTBlock block = ^(int pa) {
        NSLog(@"--block---%d",age);
        age = pa;
        NSLog(@"--block---%d",age);
    };

    block(1);
}

转为c++代码后发现和以前的结构有所区别,以前的变量类型就是变量的本身类型,age变成了__Block_byref_age_0类型

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_age_0 *age; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

__Block_byref_age_0结构体的代码为

struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

__block int age = 18;转为c++代码为:

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 18};

//去掉强转
__Block_byref_age_0 age = {
0,
&age, //__forwarding 指针指向自己
0, 
sizeof(__Block_byref_age_0), 
18  //数据
}; 

block的创建的时候把上边生成的__Block_byref_age_0类型的age的地址赋值给自己内部的age

RTBlock block = (
(void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, 
&__main_block_desc_0_DATA,
(__Block_byref_age_0 *)&age,
570425344));

__main_block_impl_0函数内部修改age的值,然后就实现修改age的值了

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int pa) {
__Block_byref_age_0 *age = __cself->age; // bound by ref

...

(age->__forwarding->age) = pa;

...
}

但是为什么修改__Block_byref_age_0里的age的值,外边的值也变化了呢,它们的地址一样?下面通过打印地址证明

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*);
//    这两个函数是内存管理相关的,这里参数先写为void,不影响使用
    void (*copy)(void);
    void (*dispose)(void);
};

struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

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;
    struct __Block_byref_age_0 *age; // by ref
};

typedef void(^RTBlock)(int);
int main(int argc, const char * argv[]) {

    __block int age = 18;
    RTBlock block = ^(int pa) {
        NSLog(@"--block---%d",age);
        age = pa;
        NSLog(@"--block---%d",age);
    };

    struct __main_block_impl_0 *s_block = (__bridge struct __main_block_impl_0 *) block;

    NSLog(@"--外部的age地址---%p",&age);

    NSLog(@"--结构体内部的age地址---%p",&s_block->age->age);
}

打印结果:

2019-06-08 14:30:48.880799+0800 Tes[43886:3665093] --外部的age地址—0x10180cd28

2019-06-08 14:30:48.881041+0800 Tes[43886:3665093] --结构体内部的age地址—0x10180cd28

  1. 注意点1
    由于变量用了__block修饰,__main_block_desc_0结构体中也出现了copydispose指针
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};

所以会把_block__Block_byref_age_0 *age这个结构体指针复制到堆上去

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

  1. 注意点2
    当用__block修饰OC对象时,__Block_byref_person_0这个结构体,会多出来两个函数指针__Block_byref_id_object_copy__Block_byref_id_object_dispose,对OC对象进行内存管理
struct __Block_byref_person_0 {
    void *__isa;
    __Block_byref_person_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    RTPerson *person;
};

__Block_byref_id_object_copy_131,这个函数会根据这个block捕获的oc对象的类型是弱引用还是强引用是否来对它进行强引用

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

    /*__isa: 8字节,
    __forwarding: 8字节,
    int __flags: 4字节,
    int __size: 4字节,
    __Block_byref_id_object_copy: 8字节
    __Block_byref_id_object_dispose: 8字节
    总共40字节

    所以dst + 40 就是person的地址
    */
}

__Block_byref_id_object_dispose_131,这个函数用来释放强引用

static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

例子:

没使用weak修饰

RTBlock block;
{
    __block RTPerson *person = [[RTPerson alloc] init];

    block = ^(int pa) {
        NSLog(@"--block---%@",person);
    };
}
NSLog(@"--block类型---%@",[block class]);

打印:

2019-06-08 14:12:13.198105+0800 Tes[45184:3784877] --block类型—NSMallocBlock

2019-06-08 14:12:17.398071+0800 Tes[45184:3784877] RTPerson dealloc

使用weak修饰

RTBlock block;
{
    RTPerson *person = [[RTPerson alloc] init];

    __block __weak RTPerson *weakP = person;
    block = ^(int pa) {
        NSLog(@"--block---%@",weakP);
    };
}
NSLog(@"--block类型---%@",[block class]);

打印:

2019-06-08 14:13:22.198105+0800 Tes[45184:3784877] RTPerson dealloc

2019-06-08 14:13:26.398071+0800 Tes[45184:3784877] --block类型—NSMallocBlock

注意:

当MRC时__Block_byref_id_object_copy_131并不会对OC对象进行强引用!!!

RTBlock block;
{
    __block RTPerson *person = [[RTPerson alloc] init];

    block = [^(int pa) {
        NSLog(@"--block---%@",person);
    } copy] ;

    [person release];
}

NSLog(@"--block类型---%@",[block class]);

打印:

2019-06-08 14:17:43.198105+0800 Tes[45184:3784877] RTPerson dealloc

2019-06-08 14:17:47.398071+0800 Tes[45184:3784877] --block类型—NSMallocBlock


如果您希望拥有更好的阅读体验,欢迎访问 我的开发笔记


你可能感兴趣的:(iOS)