三方库Bolts源码阅读

简介

老的项目中有用到Bolts,为Facebook 开发的轻量级iOS库,核心定位是解决异步编程中的 “回调地狱” 问题,提供统一的异步任务管理框架,支持跨应用跳转等扩展能力

(核心功能已被 系统原生 API 覆盖,Combine 框架(iOS 13+)、async/await(iOS 15+)完全替代 BFTask 的链式调用能力)

Bolts主要包括2个部分

  • Tasks,有点像JavaScript Promise,使复杂异步代码的组织更易于管理。
  • App Links协议的实现,可以链接到其他应用程序中的内容并处理传入的深度链接。

本文只了解一下Tasks相关代码

核心是 BFTask 类,将异步操作(如下载、网络请求)封装成 “任务对象”,支持链式调用(continueWithBlock),替代多层嵌套的回调函数,大幅简化代码逻辑

基本用法

  • 创建任务 + 链式调用

// 1. 创建一个“模拟网络请求”的异步任务

- (BFTask *)fetchDataFromNetwork {
    // 创建任务控制器,用于手动控制任务状态
    BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
    // 模拟异步操作(如网络请求)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 模拟请求结果
        NSDictionary *data = @{@"userId": @123, @"name": @"Alice"};
        // 标记任务成功,传入结果(会触发后续延续块)
        [source setResult:data];
        // 如果失败,可标记为错误:[source setError:[NSError errorWithDomain:...]]
    });
    // 返回任务(此时任务处于Pending状态)
    return source.task;
}

// 2. 使用任务并链式处理结果

- (void)handleData {
    // 发起任务,通过continueWithBlock串联后续操作
    [[[self fetchDataFromNetwork] continueWithBlock:^id(BFTask *task) {
        // 第一个延续:处理网络数据(默认在任务完成的线程执行)
        if (task.error) {
            NSLog(@"请求失败:%@", task.error);
            return nil; // 错误会终止链条,或传递给下一个延续
        }
        NSDictionary *data = task.result;
        NSLog(@"网络返回数据:%@", data);
        // 返回处理后的数据,作为下一个任务的输入
        return @([data[@"userId"] integerValue] * 2); // 示例:处理userId
    }]
    // 第二个延续:在主线程更新UI(指定线程)
    continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:^id(BFTask *task) {
        if (task.error) {
            NSLog(@"处理数据失败:%@", task.error);
            return nil;
        }
        
        NSNumber *processedUserId = task.result;
        NSLog(@"主线程更新UI:处理后的userId = %@", processedUserId);
        return nil;
    }];
}

常见问题点

  • 线程混乱导致 UI 操作崩溃

问题描述:在任务的延续块(continueWithBlock:)中直接更新 UI(如修改 UILabel 文本),偶尔会崩溃或出现界面异常。

原因:BFTask 的延续块默认在 “任务完成的线程” 执行(可能是后台线程),而 iOS 要求所有 UI 操作必须在主线程执行。

解决方案:通过 continueWithExecutor:block: 显式指定主线程执行器(BFExecutor mainThreadExecutor)

// 错误示例:延续块可能在后台线程执行,导致UI操作崩溃
[self fetchData]
.continueWithBlock:^id _Nullable(BFTask *task) {
    self.label.text = task.result; // 危险:可能在后台线程
    return nil;
};
// 正确示例:强制在主线程执行UI操作
[self fetchData]
.continueWithExecutor:[BFExecutor mainThreadExecutor]
block:^id _Nullable(BFTask *task) {
    self.label.text = task.result; // 安全:主线程执行
    return nil;
};
  • 循环引用导致内存泄漏

问题描述:使用 BFTask 链式调用时,若在 block 中直接引用 self,可能导致 self 与 task 形成循环引用,对象无法释放

原因:BFTask 的延续块会被任务强引用,而 block 中若强引用 self,会形成 self -> task -> block -> self 的循环

解决方案:使用 弱引用 + 强引用 打破循环

源码阅读

  • BFTaskCompletionSource(任务控制器)

用于创建和控制 BFTask 的状态,是任务的 “生产者”

内部维护一个 BFTask 实例,通过 setResult:/setError:/cancel 方法修改任务状态,且状态一旦修改不可再变

  • BFTask 类(任务对象)

BFTask 是不可变的,代表一个异步任务的 “状态快照”,包含任务的结果、错误和状态

BFTask 本身不直接修改状态,而是通过 BFTaskCompletionSource 创建并修改

延续块(ContinuationBlock)会在任务完成后被触发,返回新的 BFTask 实现链式调用

核心代码
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
                       withBlock:(BFContinuationBlock)block {
    BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
    
    // Capture all of the state that needs to used when the continuation is complete.
    void (^wrappedBlock)() = ^() {
        [executor execute:^{
            id result = nil;
            @try {
                result = block(self);
            } @catch (NSException *exception) {
                tcs.exception = exception;
                return;
            }
            if ([result isKindOfClass:[BFTask class]]) {
                [(BFTask *)result continueWithBlock:^id(BFTask *task) {
                    if (task.isCancelled) {
                        [tcs cancel];
                    } else if (task.exception) {
                        tcs.exception = task.exception;
                    } else if (task.error) {
                        tcs.error = task.error;
                    } else {
                        tcs.result = task.result;
                    }
                    return nil;
                }];
            } else {
                tcs.result = result;
            }
        }];
    };
    
    BOOL completed;
    @synchronized (self.lock) {
        completed = self.completed;
        if (!completed) {
            [self.callbacks addObject:[wrappedBlock copy]];
        }
    }
    if (completed) {
        wrappedBlock();
    }
    
    return tcs.task;
}

Bolts的Github地址

Written on September 4, 2024