【iOS开发】Block 核心原理

126 阅读4分钟

目标:

  1. 熟悉Block底层原理
  2. 熟悉内存拷贝相关的知识

如何深入学习相关概念呢? 将OC代码转化成C++代码,查看其内部实现。 面试过程中,每道题都是有对应分数的,一定要尽可能答的漂亮。

什么是Block

常见面试题:

  1. 什么是Block?
  2. 你对Block的调用是怎么理解的?
  3. 截获变量是Block的一大特性,系统对于Block的截获是怎么实现的呢?
  4. 我们添加__block修饰符做什么事情呢?
  5. 什么时候需要对Block 进行一个copy操作,栈Block和堆Block是否了解呢? 6.Block的循环引用

1. 什么是Block?

Block是一个对象,封装了执行函数和上下文。

源码解析

+ (void)testBlock1{
    int multiplier = 6;
    int (^Block)(int) = ^int(int num) {
        return num *multiplier;
    };
    Block(2);

}

使用: clang -rewrite-objc file.m 查看编译后的文件内容

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

struct __TestObject__testBlock1_block_impl_0 {
  struct __block_impl impl;
  struct __TestObject__testBlock1_block_desc_0* Desc;
  int multiplier;
  __TestObject__testBlock1_block_impl_0(void *fp, struct __TestObject__testBlock1_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __TestObject__testBlock1_block_func_0(struct __TestObject__testBlock1_block_impl_0 *__cself, int num) {
  int multiplier = __cself->multiplier; // bound by copy


        return num *multiplier;
    }

static struct __TestObject__testBlock1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __TestObject__testBlock1_block_desc_0_DATA = { 0, sizeof(struct __TestObject__testBlock1_block_impl_0)};

// _C 表示类方法,_I 实例方法 ,TstObject 表示类名,testBlock1表示函数名
static void _C_TestObject_testBlock1(Class self, SEL _cmd) {
    int multiplier = 6;

    int (*Block)(int) = ((int (*)(int))&__TestObject__testBlock1_block_impl_0((void *)__TestObject__testBlock1_block_func_0, &__TestObject__testBlock1_block_desc_0_DATA, multiplier));

    ((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);


}

Block调用即是函数的调用。 从Block中取出函数指针传递两个参数Block和需要的参数。

截获变量

在这里插入图片描述

  • 对于基本数据类型的局部变量截获其值。
  • 对于对象类型的局部变量连同所有权修饰符一起截获(强引用)。
  • 以指针形式截获局部静态变量。
  • 不截获全局变量、静态全局变量。

使用

- (void)method {
    // 基本类型的局部变量
    int var =1;
    // 对象类型的局部变量
    __unsafe_unretained id unsafe_obj = nil;
    __strong id strong_obj = nil;
    
    //静态局部变量
    static int static_var = 3;
    
    void(^Block)(void) = ^{
        NSLog(@"局部变量<基本数据类型> var %d",var);
        NSLog(@"局部变量<__unsafe_unretained 对象类型> var %@",unsafe_obj);
        NSLog(@"局部变量<__strong 对象类型> var %@",strong_obj);
        
        NSLog(@"静态变量 %d",static_var);
        
        NSLog(@"全局变量<基本数据类型> var %d",global_var);
        NSLog(@"静态全局变量<基本数据类型> var %d",static_global_var);
        
        // 使用对象类型的  会防止循环引用  从而进行 提示
//        NSLog(@"TestObject 对象类型成员 num %d",self.num);
    };
    Block();
    
}


clang -rewrite-objc -fobjc-arc file.m

struct __TestObject__method_block_impl_0 {
  struct __block_impl impl;
  struct __TestObject__method_block_desc_0* Desc;
  int var;
  __unsafe_unretained id unsafe_obj;
  __strong id strong_obj;
  int *static_var;
  __TestObject__method_block_impl_0(void *fp, struct __TestObject__method_block_desc_0 *desc, int _var, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int *_static_var, int flags=0) : var(_var), unsafe_obj(_unsafe_obj), strong_obj(_strong_obj), static_var(_static_var) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __TestObject__method_block_func_0(struct __TestObject__method_block_impl_0 *__cself) {
  int var = __cself->var; // bound by copy
  __unsafe_unretained id unsafe_obj = __cself->unsafe_obj; // bound by copy
  __strong id strong_obj = __cself->strong_obj; // bound by copy
  int *static_var = __cself->static_var; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_0,var);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_1,unsafe_obj);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_2,strong_obj);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_3,(*static_var));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_4,global_var);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_5,static_global_var);



    }

__block修饰符

使用场景: 一般情况下,对被截获变量进行赋值操作需要添加__bloc修饰符。

__block修饰的变量变成了对象。

+ (void)testBlock1{
    __block int multiplier = 6;
    int (^Block)(int) = ^int(int num) {
        return num *multiplier;
    };
    Block(2);

}

// 编译成C++文件后
struct __Block_byref_multiplier_0 {
  void *__isa;
__Block_byref_multiplier_0 *__forwarding;
 int __flags;
 int __size;
 int multiplier;
};

struct __TestObject__testBlock1_block_impl_0 {
  struct __block_impl impl;
  struct __TestObject__testBlock1_block_desc_0* Desc;
  __Block_byref_multiplier_0 *multiplier; // by ref
  __TestObject__testBlock1_block_impl_0(void *fp, struct __TestObject__testBlock1_block_desc_0 *desc, __Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void _C_TestObject_testBlock1(Class self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_multiplier_0 multiplier = {(void*)0,(__Block_byref_multiplier_0 *)&multiplier, 0, sizeof(__Block_byref_multiplier_0), 6};
    
    int (*Block)(int) = ((int (*)(int))&__TestObject__testBlock1_block_impl_0((void *)__TestObject__testBlock1_block_func_0, &__TestObject__testBlock1_block_desc_0_DATA, (__Block_byref_multiplier_0 *)&multiplier, 570425344));
    
    ((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);

}

使用 不等于 赋值

笔试题

    // 问题:此处是否需要对array 使用__block进行修饰呢
    NSMutableArray * array = [[NSMutableArray alloc] init];
    void (^Block)(void) = ^{
        [array addObject:@123];
    };
    Block();
    NSLog(@"array = %@",array);

此处不需要,仅仅为使用。 下面这个呢: 局部变量不管对象类型是基本数据类型还是对象类型,进行赋值操作都需要添加__block修饰符。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LyQwDFqe-1598110556261)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p295)]

对变量进行赋值时, 在这里插入图片描述 需要__block修饰符 在这里插入图片描述

Block内存管理

Block的Copy操作 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9sQGbNX-1598110556266)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p299)]

在这里插入图片描述

不论在任何内存位置,都可以顺利的访问同一个__block变量。

循环引用

    _array = [NSMutableArray arrayWithObject:@"block"];
    _strBlk = ^NSString *(NSString *num) {
        return [NSString stringWithFormat:@"helloc_%@",_array[0]];
    };
    
    _strBlk(@"hello");
    
    // 解决方式
    _array = [NSMutableArray arrayWithObject:@"block"];
    __weak NSArray * weakArray = _array;
    _strBlk = ^NSString *(NSString *num) {
        return [NSString stringWithFormat:@"helloc_%@",weakArray[0]];
    };
    _strBlk(@"hello");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q08bQNJh-1598110556268)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p300)]

在MRC下,不会产生循环引用。 在ARC下,会产生循环引用,引起内存泄漏。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OToxJaSr-1598110556269)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p301)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onf8YuR6-1598110556270)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p302)]

此时如果不调用或者是长时间不调用的话,环就会一直存在。