Block

作者: CoderGuogt | 来源:发表于2019-08-21 18:02 被阅读0次

Block 的本质是一个OC对象

首先Block是一个OC对象,可以通过打印父类信息得到

void (^block)(void) = ^{
    NSLog(@"block --- ");
};

block();

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

得到打印结果

Block[38376:1019280] block ---
Block[38376:1019280] _NSGlobalBlock_
Block[38376:1019280] __NSGlobalBlock
Block[38376:1019280] NSBlock
Block[38376:1019280] NSObject

由上打印信息可知,Block确实是一个继承于NSObject的OC对象

下面也可通过 Clang 将这段代码转换成 C++代码,探究编译器将这段OC代码转成C++代码

int main(int argc, const char * argv[]) {
    
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    return 0;
}

简化

int main(int argc, const char * argv[]) {
    
    void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
    block->FuncPtr;
    
    return 0;
}

编译器将这段OC代码转成以上的C++代码,可以看到Block转成了一个 __main_block_impl_0 这样的一个结构体,并且将__main_block_func_0&__main_block_desc_0_DATA这两个参数传入,首先先看下__main_block_impl_0这个结构体

struct __main_block_impl_0 {
  struct __block_impl impl; // __block_impl 结构体
  struct __main_block_desc_0* Desc; // __main_block_desc_0 结构体
  // C++构造方法
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock; // Block 也有一个 isa 指针,说明 block 是一个OC对象,这里指向了一个_NSConcreteStackBlock类
    impl.Flags = flags;
    impl.FuncPtr = fp; // 方法地址
    Desc = desc;
  }
};

这里直接将方法地址赋值给了 __block_impl 这个结构体的FuncPtr ,那么接下来继续看看 __block_impl 这个结构体

struct __block_impl {
  void *isa; // isa指针
  int Flags;
  int Reserved;
  void *FuncPtr; // 存放block实现的方法地址
};

Block的调用直接是调用FuncPtr

__main_block_func_0 方法

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_y3mvcz2d4_s228xbxgg4ky0h0000gp_T_main_b2a17e_mi_0);
}

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

以上就是一个简单的 Block 结构

Block 的类型

Block 有三种类型,分别为 _NSGlobalBlock_ 、 _NSStackBlock、 _NSMallocBlock

要探究Block的类型,首先要在 MRC 环境下,将 ARC关闭
在工程中 target-> Build Setting -> Obective-C Automatic Reference Counting 设置为NO

int age = 10;
 
 void (^block1)(void) = ^{
     NSLog(@"block1 --- ");
 };
 
 void (^block2)(void) = ^{
     NSLog(@"block2 --- %d", age);
 };
 
 void (^block3)(void) = [^{
     NSLog(@"block3 --- %d", age);
 } copy];
 
 block1();
 block2();
 block3();
 
 NSLog(@"%@", [block1 class]);
 NSLog(@"%@", [block2 class]);
 NSLog(@"%@", [block3 class]);

打印结果:

Block[39097:1064145] block1 ---
Block[39097:1064145] block2 --- 10
Block[39097:1064145] block3 --- 10
Block[39097:1064145] _NSGlobalBlock_
Block[39097:1064145] _NSStackBlock_
Block[39097:1064145] _NSMallocBlock_

block3经过copy操作变成了了 __NSMallocBlock__类型,那下面继续看看这三种类型经过 copy 操作会发生什么,在原来的代码中进行 copy 操作

int age = 10;
 
 void (^block1)(void) = [^{
     NSLog(@"block1 --- ");
 } copy];
 
 void (^block2)(void) = [^{
     NSLog(@"block2 --- %d", age);
 } copy];
 
 void (^block3)(void) = [[^{
     NSLog(@"block3 --- %d", age);
 } copy] copy];
 
 block1();
 block2();
 block3();
 
 NSLog(@"%@", [block1 class]);
 NSLog(@"%@", [block2 class]);
 NSLog(@"%@", [block3 class]);

Block[39644:1093505] block1 ---
Block[39644:1093505] block2 --- 10
Block[39644:1093505] block3 --- 10
Block[39644:1093505] _NSGlobalBlock_
Block[39644:1093505] _NSMallocBlock_
Block[39644:1093505] _NSMallocBlock_

对比上一次的打印结果可以总结出

类型 经过 copy 操作
_NSGlobalBlock_ _NSGlobalBlock_
_NSStackBlock_ _NSMallocBlock_
_NSMallocBlock_ _NSMallocBlock_

如果在 ARC 环境下,__NSStackBlock__ 系统会根据情况自动将栈上的Block 复制到堆上

block 作为函数返回值时(Masonry)
将 block 赋值给 __strong 指针时
block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时
block 作为 GCD API 的方法参数时

__weak 修饰 auto 变量

话不多说,直接上代码

typedef void(^YXCBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        YXCBlock block;
        
        {
            YXCPerson *person = [YXCPerson new];
            person.age = 10;
            __weak YXCPerson *weakPerson = person;
            block = ^{
                NSLog(@"----%d", weakPerson.age);
            };
        }
        
        block();
        
        NSLog(@"%@", [block class]);
    }
    return 0;
}

输出结果

Block[10400:227395] -[YXCPerson dealloc]
Block[10400:227395] ----0
Block[10400:227395] _NSMallocBlock_

转换C++代码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  YXCPerson *__weak weakPerson; // 拥有一个 __weak 的 person 对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YXCPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    YXCPerson *__weak weakPerson = __cself->weakPerson; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_y3mvcz2d4_s228xbxgg4ky0h0000gp_T_main_8f3cfc_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
}

// 通过调用这个函数,将block进行copy操作,_Block_object_assign 会根据 auto 变量的修饰符做出相应的操作,形成强/弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 调用这个函数,自动释放 block 中引用的 auto 变量
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 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[]) {
    {
        __AtAutoreleasePool __autoreleasepool;

        YXCBlock block;

        {
            YXCPerson *person = ((YXCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YXCPerson"), sel_registerName("new"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
            __attribute__((objc_ownership(weak))) YXCPerson *weakPerson = person;
            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakPerson, 570425344));
        }

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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8f_y3mvcz2d4_s228xbxgg4ky0h0000gp_T_main_8f3cfc_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
    }
    return 0;
}

block在定义的时候默认是一个 __strong 类型的对象,而在内部引用了 auto 变量,所以在 ARC 环境下会经过 copy操作,由本来的 __NSStackBlock__类型变成__NSMallocBlock__类型的block,引用的auto变量person,经过 __weak 修饰引用到block中,_Block_object_assign函数对 person 产生弱引用,所以person出了作用域就被释放了。

下面不经过 __weak 修饰

typedef void(^YXCBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        YXCBlock block;
        
        {
            auto YXCPerson *person = [YXCPerson new];
            person.age = 10;
            block = ^{
                NSLog(@"----%d", person.age);
            };
        }
        
        block();
        
        NSLog(@"%@", [block class]);
    }
    return 0;
}

打印结果

Block[10464:231221] ----10
Block[10464:231221] _NSMallocBlock_
Block[10464:231221] -[YXCPerson dealloc]

此时的_Block_object_assign函数对 person 产生强引用,只有等block被释放了,才会调用block内部的 _Block_object_dispose 释放引用的 person 对象。

__block 修饰 auto 变量

在日常使用block中,有需要在block中修改外面auto变量,众所周知,block是不可以直接修改外面的auto变量,这时候需要用到 __block

__block int age = 20;
        
void (^block)(void) = ^{
    age = 50;
};
        
block();
NSLog(@"age = %d, block的类型:%@", age, [block class]);

打印结果:

Block[10665:243061] age = 50, block的类型:NSMallocBlock

接下来,转换成 C++代码,看看 __block做了什么操作

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // 存放age
  __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中没有修改 auto 变量,多了一个 __Block_byref_age_0 类型的结构体,在这里将外面 auto变量

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

下面看 __block int age = 20; 这句代码转换之后的C++代码

/*
 0 赋值给 age 的 __isa
 age本身的地址赋值给 age 的 __forwarding
 0 赋值给 age 的 __flags
 sizeof(__Block_byref_age_0) 将age所需要的内存的大小 赋值给 age 的 __size
 0 赋值给 age 的 age int变量
*/
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};

紧接着看block里面的 age = 50;

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 拿到 age
    __Block_byref_age_0 *age = __cself->age; // bound by ref
    // 通过 age 的 __forwarding 将 50 赋值给 int age 变量
    (age->__forwarding->age) = 50;
}

可以得出一个结论:编译器会将 __block 修饰的变量包装成一个对象,__block 可以用于解决无法在block内部修改值的问题,而且 __block 不能修饰全局变量、静态(static)变量。

相关文章

  • iOS开发之Block原理探究

    Block概述 Block本质 Block调用 Block分类 Block循环引用 Block原理探究 Block...

  • block的使用

    定义block 返回类型 (^block名称)(参数) = ^(){block内容}; 调用block block...

  • Block 02 - __block

    Block 02 - __block __block 的作用 __block 可以解决 Block 内部无法修改 ...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS Block

    Block的分类 Block有三种类型:全局Block,堆区Block,栈区Block 全局Block 当Bloc...

  • iOS block 为什么官方文档建议用 copy 修饰

    一、block 的三种类型block 三种类型:全局 block,堆 block、栈 block。全局 block...

  • iOS开发block是用copy修饰还是strong

    Block分为全局Block、堆Block和栈Block1、在定义block没有引用外部变量的时候,block为全...

  • block 初探

    全局block, 栈block, 堆block

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

      本文标题:Block

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