OC 闭包小结

2022/07/25 posted in  Apple
Tags:  #Objective-C

block 分类

block可以分配在上,也可以是全局的。分配到栈上的块可以拷贝到堆中,同标准的Objective-C对象一样,具备引用计数

类型 描述 环境
NSGlobalBlock 全局Block,保存在数据区(.data段) 定义在全局区或者没有访问自动局部变量
NSStackBlock 栈Block,保存在栈区 访问了自动局部变量
NSMallocBlock 堆Block, 保存在堆区 __NSStackBlock__调用了copy

block 修饰符

在ARC下 block 属性可以用 copy 或者 strong 来修饰。
为什么用copy来修饰?
因为block变量默认声明为栈变量,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆。
block本质是对象,可以retain和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作用域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃。在对block进行copy后,block存放在堆区。

Xcode 4.2之后,引入了ARC机制,在一些默认的情况下系统会帮你自动调用copy操作:

  • block作为函数返回值
  • 将block赋值给strong指针
  • block作为某些系统方法参数

系统的整体思路是如果block被释放,有潜在的异常风险时,手动“帮你”copy下

block 声明格式

标准格式

// block声明
returnType (^blockName)(parameters);
// block赋值
^returnType(parameters) {
  // do something;
};
// 示例
int (^sumBlock)(int a, int b) = ^int(int a, int b) {
  return a + b;
};
int sum = sumBlock(1, 1);

省略格式

赋值时可省略返回类型和block名称,甚至入参

// 有返回值有入参
int (^sumBlock)(int, int) = ^(int a, int b) {
    return a + b;
};
// 无返回值无入参
void (^printBlock)(void) = ^{
    NSLog(@"Hello World!");
};

typedef 简化声明

typedef returnType (^blockName)(parameters);

typedef int (^SumBlock)(int a, int b);
SumBlock block = ^(int a, int b) {
    return a + b;
};

变量捕获

OC block 变量捕获

  • 默认情况
    捕获基础值时,是将值拷贝到闭包内,闭包内外不相互影响。
    捕获指针时,是将指针值拷贝到闭包内,指针值不变所以指向同一对象,闭包内外可共同修改同一对象的内部值。
  • 添加__block修饰后
    捕获基础值时,是将值的指针传递到闭包内,闭包内外相互影响。
    捕获指针时,是将指针的指针传递到闭包内,闭包内外可共同修改同一对象的内部值(指针指向的对象内部),还能让指针指向新的对象(修改指针本身)

Swift 闭包变量捕获

  • 默认情况
    捕获引用,相当于 OC 添加__block修饰
  • 添加捕获列表
    拷贝值,相当于 OC 中默认情况

OC与Swift闭包对比总结

循环引用

类持有 block,block 通过 self 持有类,从而循环引用(因为 copy 修饰,block 本质是位于堆区的对象)
解决方法:block 外先 weak 持有,block 内再 strong 持有

// ViewController.m

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void (^completionBlock)(void);

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self;
    self.completionBlock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@", strongSelf.name);
    };
}

@end

隐式的内存泄漏,block内虽然没有直接引用self,但是_name表示viewController的变量,要找到它就需要持有self,因此形成了隐式的内存泄漏。

// ViewController.m

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void (^completionBlock)(void);

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.completionBlock = ^{
        NSLog(@"%@", _name);
    };
}

@end

链式调用写法

// Calculator.h
@interface Calculator : NSObject

@property (nonatomic) CGFloat result;

- (Calculator *(^)(CGFloat addend))add;

@end
// Calculator.m
#import "Calculator.h"

@implementation Calculator

- (Calculator *(^)(CGFloat addend))add {
    return ^(CGFloat addend) {
        self.result += addend;
        return self;
    };
}

@end