第八篇:Objective-C 知识回顾Block

Block知识点大纲

8.1.基本概念

问题一:什么是 Block?
  • Block 是将函数及其执行上下文封装起来的对象
  • 其本身其实就是函数指针。

首先看下这段代码,我们要分析 Block 具体干了些啥,可以从 Block 编译后的代码分析。使用 xcrun -sdk iphonesimulator clang -rewrite-objc BDBlock.m 可以生成 BDBlock.cpp 文件,借助这个文件可以查看 Block 内部干了些啥。

//#import "BDBlock.h"
@implementation BDBlock
- (void)myMethod
{
    int multiplier = 6;
    int(^MyBlock)(int) = ^int(int num) {
        return num * multiplier;
    };
    MyBlock(2);
}
@end

截取其中一段代码

static void _I_BDBlock_myMethod(BDBlock * self, SEL _cmd) {
   int multiplier = 6;
   
   /*
    int(^MyBlock)(int) = ^int(int num) {
        return num * multiplier;
    };
    */
   int(*MyBlock)(int) = ((int (*)(int))&__BDBlock__myMethod_block_impl_0((void *)__BDBlock__myMethod_block_func_0, &__BDBlock__myMethod_block_desc_0_DATA, multiplier));
   
   /*
    MyBlock(2);
    */
   ((int (*)(__block_impl *, int))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock, 2);
}
问题二:Block 调用的本质是什么?
  • Block 调用的本质就是函数调用

8.2.Block 对变量的截获

问题三:请回答 Block 对下面的变量截获都有什么异同?
请回答 Block 对下面的变量截获都有什么异同?
- (void)myMethod2
{
    //基本数据类型的局部变量
    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(@"全局变量 %d", global_var);
        NSLog(@"全局静态变量 %d", global_static_var);
    };
    
    Block();
}

这段代码被编译的代码可以用命令 xcrun -sdk iphonesimulator clang -rewrite-objc -fobjc-arc BDBlock.m 来生成 cpp 文件查看。

static void _I_BDBlock_myMethod2(BDBlock * self, SEL _cmd) {

    int var = 1;

    __attribute__((objc_ownership(none))) id unsafe_obj = __null;
    __attribute__((objc_ownership(strong))) id strong_obj = __null;


    static int static_var = 3;
    void(*Block)(void) = ((void (*)())&__BDBlock__myMethod2_block_impl_0((void *)__BDBlock__myMethod2_block_func_0, &__BDBlock__myMethod2_block_desc_0_DATA, var, unsafe_obj, strong_obj, &static_var, 570425344));

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

从编译后的代码可以看出

  • 对于 基本数据的局部变量 Block 采用值传递的截获方式。
  • 对于 对象型的局部变量 Block 采用 连同所有权修饰符 一起截获的方式
  • 对于 局部静态变量 Block 以指针形式截获变量地址
  • 对于 全局变量静态全局变量 Block 不截获

8.3. __block 修饰符

问题四:请问下面代码能正常运行吗?是否需要 __block 关键字的协助?
    NSMutableArray *array = [[NSMutableArray alloc] init];
    void(^Block)(void) = ^ {
        [array addObject:@"111"];
    };
    [array addObject:@"222"];
    NSLog(@"%@",array);
    Block();
    NSLog(@"%@",array);
  • 上述代码能正常运行
问题五:请问下面代码能正常运行吗?是否需要 __block 关键字的协助?
    NSMutableArray *array = nil;
    void(^Block)(void) = ^ {
        array = [NSMutableArray new];
    };
    [array addObject:@"222"];
    NSLog(@"%@",array);
    Block();
    NSLog(@"%@",array);
  • 上述代码会编译报错,错误信息:Variable is not assignable (missing __block type specifier)
问题六:请回答什么情况下会使用到 __block 修饰符?
  • 一般情况下,对被截获的变量进行 赋值 操作需添加__block 修饰符
  • 请千万理解 赋值 不等于 使用 ,因此上一个问题只是使用了 array,不需要 __block 关键字的使用,能正常运行。
问题七:请回答对哪些变量在 Block 代码块里面赋值需要添加 __block 修饰词?
  • 局部基本数据类型
  • 局部对象数据类型
问题八:请回答对哪些变量在 Block 代码块里面赋值不需要添加 __block 修饰词?
  • 全局变量
  • 静态全局变量
  • 静态局部变量
问题九:思考如下代码的输出?
    __block int multiplier = 6;
    int(^Block)(int) = ^int(int num) {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"Result is %d",Block(2));
  • 输出 8
问题十:思考 __block 修饰基本数据类型,对基本数据类型做什么事情?
  • __block 修饰基本数据变量,会把变量转变成对象。
  • 并且有一个__forwarding 指针,(如果在栈上创建的)指向基本数据类型对象自己。

8.4.Block 的内存管理

问题一:思考 __block 在内存方面有哪几种类型?
  • _NSConcreteGlobalBlock 全局 Block
  • _NSConcreteStackBlock 栈 Block
  • _NSConcreteMallocBlock 堆 Block
三种 Block 在内存存储位置
问题二:思考我们在对 Block 进行 copy 操作会产生什么效果?
对 Block 进行 copy 的结果
问题三:思考我们在何时需要对 Block 进行 copy 操作?
栈上的 block 如果不 copy 会随着作用域的结束,而被释放
问题四:思考既然栈上的__forwarding指向自己,为什么还需要呢?
  • 因为栈上的 Block 经过 copy 之后,__forwarding 会指向对上的 __block 对象。
    -这样就保证了,无论在任何内存位置,都可以顺利的访问同一个 __block 变量。

8.5.Block 的循环引用问题

问题一:思考下面的代码存在什么问题?如何解决?
    _mutableArray = [NSMutableArray arrayWithObject:@"block"];
    _blk = ^NSString*(NSString* num) {
        return [NSString stringWithFormat:@"%@", _mutableArray[0]];
    };
    _blk(@"hello");
  • 会造成循环引用,并且编译器会提示: Capturing 'self' strongly in this block is likely to lead to a retain cycle
    // 解决方案
    _mutableArray = [NSMutableArray arrayWithObject:@"block"];
    __weak NSArray *weakArray = _mutableArray;
    _blk = ^NSString*(NSString* num) {
        return [NSString stringWithFormat:@"%@", weakArray[0]];
    };
    _blk(@"hello");
  • 由于 Block 截获变量的时候,如果是对象类型的,那么会连同对象的所有权 一起截获。我们给他一个 weak 的变量,就会截获 weak 所有权的变量,从而避免了两边强引用而导致的循环引用问题。
  • 这是一种自循环引用,我们通过避免自循环引用的方式解决。
问题一:思考下面的代码存在什么问题?如何解决?
__block ViewController* blockSelf = self;
    _blk = ^NSString*(NSString* num) {
        return [NSString stringWithFormat:@"%@", blockSelf.mutableArray[0]];
    };
    _blk(@"hello");
  • 上述代码中,self 被 blockSelf 持有,blockSelf 被 _blk 持有,_blk 被 self 持有,这就形成了循环引用中的大环引用。
  • 我们可以通过断环的方式避免循环应用。
  • 但是这种方式有一个弊端,就是 block 如果不被调用,那么循环引用就会一直存在。
    __block ViewController* blockSelf = self;
    _blk = ^NSString*(NSString* num) {
        NSString* temp = [NSString stringWithFormat:@"%@", blockSelf.mutableArray[0]];
        blockSelf = nil;
        return temp;
    };
    _blk(@"hello");

推荐阅读更多精彩内容