云端 Mac 上的 iOS 聚合进度验收:权重、取消与完成语义

云端 Mac 上的 iOS 聚合进度验收:权重、取消与完成语义

批量处理文件时,进度条先到百分之百,随后却弹出失败;用户点了取消,后台仍提交结果。这通常不是绘制问题,而是工作量、取消信号和业务终态混在了一起。在 GNUMac 云端 Mac 上,可以先用不依赖网络和模拟器的单元测试固定这些契约,再验证 iOS 界面。

先约定进度代表什么

本文用“转换四份输入,再校验两份输出”作为夹具。转换占总工作量的六成,校验占四成。这是测试指定的权重,不是性能测量结果,也不表示剩余时间。

父进度总量设为一百,子进度分别使用文件数计量。不要把转换文件数与上传字节数直接相加;不同单位必须先映射到父级权重。

业务状态另设为等待、运行、取消中、已取消、失败和成功。进度对象只负责描述数量,不承担错误分类。

百分比回答“约定的工作量完成多少”,业务状态回答“结果是否已经可靠交付”。两者不能互相替代。

在实现前写明三条约束:每个子进度只挂接一次;完成量不得超过总量;结果提交前必须经过取消检查。父子对象由单个任务持有,不放进所有任务共享的全局变量。

用确定性测试验收权重

在已有工程的单元测试目标中新建测试文件,加入以下代码。测试只使用 Foundation 与 XCTest,不需要等待真实文件转换,也不通过休眠猜测执行时序。

import Foundation
import XCTest

final class WeightedProgressTests: XCTestCase {
    func testWeightedStages() {
        let root = Progress(totalUnitCount: 100)
        let convert = Progress(totalUnitCount: 4)
        let verify = Progress(totalUnitCount: 2)

        root.addChild(convert, withPendingUnitCount: 60)
        root.addChild(verify, withPendingUnitCount: 40)

        XCTAssertEqual(root.fractionCompleted, 0, accuracy: 0.000001)

        convert.completedUnitCount = 2
        XCTAssertEqual(root.fractionCompleted, 0.3, accuracy: 0.000001)

        verify.completedUnitCount = 1
        XCTAssertEqual(root.fractionCompleted, 0.5, accuracy: 0.000001)

        convert.completedUnitCount = 4
        verify.completedUnitCount = 2
        XCTAssertEqual(root.fractionCompleted, 1, accuracy: 0.000001)
    }
}

这里故意让两个阶段交错推进,确认父级不是只反映当前阶段。浮点结果用容差比较,不用字符串格式化后的百分数断言。

在云端 Mac 的测试导航器执行该类,保存提交号、测试结果与所用工具链信息。若已有命令行测试入口,只增加这个测试目标,不为进度测试另建一套模拟器矩阵。

取消必须由执行器配合

Progress.cancel() 是信号,不会强制终止正在运行的循环。每处理一个单元前检查状态,真正完成后再增加计数;最终替换输出文件前还要再次检查。

下面的测试验证“取消后不启动下一个工作单元”。它不声称已经覆盖并发竞态,可直接加入前面的测试类。

func testCancellationStopsNextUnit() {
    let progress = Progress(totalUnitCount: 4)
    progress.isCancellable = true
    var processed = 0

    func runNextUnit() {
        guard !progress.isCancelled else { return }
        processed += 1
        progress.completedUnitCount += 1
    }

    runNextUnit()
    progress.cancel()
    runNextUnit()

    XCTAssertTrue(progress.isCancelled)
    XCTAssertEqual(processed, 1)
    XCTAssertEqual(progress.completedUnitCount, 1)
}

异步执行器还需要两个可控暂停点:开始耗时操作之前、提交结果之前。测试暂停任务,发出取消,再放行,断言没有新的工作启动,也没有发布最终结果。用测试闸门协调,不用固定等待秒数。

若底层操作提供取消接口,应同步转发信号。若无法中断,就等待它返回后丢弃结果,并清理临时文件;界面保持“取消中”,不要提前承诺已经停止。

未知总量与重试不要伪装

总文件数尚未扫描完成时,先显示“正在统计”,不要用零总量硬算百分比。扫描结束后冻结任务列表,再建立完整的父子关系,开始处理。

运行中不断增加分母,会让进度倒退。确实需要动态发现任务时,应显示阶段状态或已处理数量,并明确当前百分比只属于已确定的阶段。

重试也不要反复把同一个子进度清零。业务层可以保留“第几次尝试”,进度层按成功完成的逻辑单元累计。一次失败尝试不等于又完成了一份文件。

还要为零输入单独定规则:若空输入合法,直接返回“无需处理”的成功结果;若调用方必须提供输入,就在启动前报错,不创建一个永远停在起点的任务。

收口到界面与持续验收

观察进度变化后,将界面状态更新切回主执行器。每次运行分配独立任务标识,回写时核对标识,阻止上一轮任务的延迟通知覆盖新进度。

显示百分比可以取整,但不要把取整后的数值用于判断成功。最后一个文件处理完之后,若仍需校验和原子提交,应给这些步骤预留工作量;失败时保留错误状态,而不是强制补满计数。

合并前逐项检查:

  • 中间权重与交错推进的断言通过,结果不依赖执行速度。
  • 开始前取消、处理中取消、提交前取消都有独立用例。
  • 错误路径不会被映射为成功,临时输出能够回收。
  • 新任务启动后,旧任务通知不能修改当前界面。
  • 空输入、未知总量和重试都有明确显示规则。

最终交付的不是一条看起来平滑的进度条,而是一份可验证的状态契约。先让计数可信,再让取消可控,最后让成功只代表结果真正可用。

常见问题

Progress 到达百分之百就能显示成功吗?

不能。计数完成只描述工作量,业务成功还需要最终写入、校验或交付成功。界面应同时检查独立的业务状态。

调用 cancel() 会自动停止正在执行的工作吗?

不会。执行器必须检查取消状态,并把取消传给实际的网络或计算任务;还要在提交最终结果前再次检查。

为你的工作流配置一台独享 Mac mini

按项目选择机型、节点与租期。先核对内存、存储和连接需求,再提交订单;实际可用状态以控制台实时返回为准。

先确定工作流,再把机器开起来。

独享物理 Mac mini。选择机型、节点与租期后,再按项目需要配置存储。