进度开始后是不是可以动态扩展 NSProgress 层次结构?

Posted

技术标签:

【中文标题】进度开始后是不是可以动态扩展 NSProgress 层次结构?【英文标题】:Is it possible to dynamically extend an NSProgress hierarchy after progress has started?进度开始后是否可以动态扩展 NSProgress 层次结构? 【发布时间】:2014-04-21 17:23:07 【问题描述】:

假设我有一些NSProgress 对象的层次结构。为简单起见,假设有 2 个孩子的根进度。如果进度已经开始,我可以向层次结构添加另一个子进度并期望发生正确的事情吗?例如,假设我从 2 个孩子开始,所以根的 totalUnitCount2pendingUnitCount2。一旦第一个孩子完成,根进度'fractionCompleted 将是0.5。如果我再添加另一个子进程,根进程'fractionCompleted 确实会更新为~0.33,但根的totalUnitCount 仍为2pendingUnitCount 也是如此。我可以手动更新totalUnitCount,但这会使根的fractionCompleted 关闭。此外,似乎没有办法在不调用becomeCurrentWithPendingUnitCount: 的情况下更新pendingUnitCountNSProgress 不是为这种用途而设计的,还是有“正确”的方法来做到这一点?

我的用例是我有一个可以将文件上传到远程服务器的应用程序。用户可以选择一些文件并点击按钮开始上传它们。当他们上传时,用户会看到上传作业的整体进度。现在,如果用户想要在上传过程中选择更多要上传的照片,我想更新进度以反映新的总数。例如,他当前正在上传 2 个文件,其中 1 个已经完成,在他们正在上传的同时,他又添加了 2 个要上传。进度应自行调整。总单位数现在应该是 4,应该还有 3 个项目要上传。对我来说,这似乎是一个常见的用例。

这是我编写的一个单元测试,用于动态扩展NSProgress 层次结构。下面测试中的所有断言都通过了,但是如果您检查根的totalUnitCount,它始终保持为 2,并且似乎无法判断根进度还有多少项目需要完成。根进度是 UI 将观察到的,因此层次结构中更深层次的变化向上传播很重要,包括 totalUnitCount 等。

- (void)testDynamicNSProgress

    NSProgress *root = [NSProgress progressWithTotalUnitCount:2];
    [root becomeCurrentWithPendingUnitCount:2];

    NSProgress *child1 = [NSProgress progressWithTotalUnitCount:1];
    NSProgress *child2 = [NSProgress progressWithTotalUnitCount:1];
    XCTAssertEqual(root.totalUnitCount, 2);
    XCTAssertEqual(root.completedUnitCount, 0);

    child1.completedUnitCount++;
    XCTAssertEqual(ceil(root.fractionCompleted * 100), 50);

    NSProgress *child3 = [NSProgress progressWithTotalUnitCount:1];
    NSProgress *child4 = [NSProgress progressWithTotalUnitCount:1];
    XCTAssertEqual(ceil(root.fractionCompleted * 100), 25);

    child2.completedUnitCount++;
    XCTAssertEqual(ceil(root.fractionCompleted * 100), 50);

    child3.completedUnitCount++;
    XCTAssertEqual(ceil(root.fractionCompleted * 100), 75);

    child4.completedUnitCount++;
    XCTAssertEqual(ceil(root.fractionCompleted * 100), 100);

    __unused NSProgress *child5 = [NSProgress progressWithTotalUnitCount:1];
    XCTAssertEqual(ceil(root.fractionCompleted * 100), 80);

【问题讨论】:

当我想做类似的事情时,我发现我必须手动增加根NSProgress 对象的totalUnitCount。据我所知,在层次结构上报告的总体进度还可以,但除非您手动更改它,否则根单元的总数不会改变(NSProgresstotalUnitCount 属性是可变的,所以我是每当我准备去becomeCurrentWithPendingUnitCount 做下一个任务时,我就在做parentProgress.totalUnitCount += <whatever>;。) 嗨,马特,我一直在搞这个。似乎如果我更新根的 totalUnitCount 并按该顺序添加另一个子级,那么根的 fractionComplete 将是正确的,但 completedUnitCount 以及根的localizedDescription 和localizedAdditionalDescription 中的消息不是。 * 创建根和 2 个孩子 * child1.completedUnitCount++ * "50% 完成"、"1 of 2" * child2.completedUnitCount++ * "100% 完成"、"2 of 2" * root.totalUnitCount = 3 * add child3 * child3.completedUnitCount++ * "66% completed", "1 of 3" 66% 是对的,但应该是 2 of 3。如果我在添加 child3 之前手动设置 root.completedUnitCount = 2,那么 fractionCompleted 变为不正确。 【参考方案1】:

我向 Apple 提交了一个错误,这是他们的回复:

工程部门已根据以下信息确定此问题的行为符合预期:

原因是我们无法更新父级的已完成单元计数,而子级“组”中的任何成员(在当前进度时作为子级进度附加到该组的成员)未完成。一旦该组的所有成员都完成了,我们将该组与父组分离并增加父组的已完成单元计数。这保留了对已完成工作的正确核算。本地化描述方法说明了这一点,这就是为什么它给出的结果与 completedUnitCount 属性不同的原因。

如果您想更密切地跟踪这些子项的完成情况,那么您可以让父项更频繁地更新,并减少单元数。然后,当孩子完成时,父母会更频繁地更新其完成的单元数。

【讨论】:

另外,我应该补充一点,我仍然认为 NSProgress 存在错误。也就是说,localizedAdditionDescription 没有保持最新。我根据他们的建议更改了我的代码以更密切地跟踪孩子,但仍然有一点,completedUnitCount 是 2,totalUnitCount 是 3,localizedDescription 是“66% 完成”,localizedAdditionalDescription 是“1 of 3”。我回复了他们的询问。如果他们再次回复,我会通知你。【参考方案2】:

好的,我想我知道我做错了什么。在添加新子节点之前,根必须完成正在进行的工作并调用 resignCurrent。然后根可以更新totalUnitCount,再次成为当前新的未决单元计数,孩子们可以工作。似乎 currentProgress 的 completedUnitCount 直到它退出当前才更新。这就是localizedAdditionalDescription 错误的原因。不确定这是设计使然还是错误,但这意味着如果我要动态地将子节点添加到进度层次结构中,我不能依赖根节点的 completedUnitCount。

- (void)testDynamicNSProgress2

    NSProgress *root = [NSProgress progressWithTotalUnitCount:2];
    [root becomeCurrentWithPendingUnitCount:2];

    XCTAssertEqual(root.completedUnitCount, 0);
    NSLog(@"%@", root.localizedDescription);           // 0% completed
    NSLog(@"%@", root.localizedAdditionalDescription); // 0 of 2

    // Add two children
    NSProgress *child1 = [NSProgress progressWithTotalUnitCount:1];
    NSProgress *child2 = [NSProgress progressWithTotalUnitCount:1];

    // First child completes work    
    child1.completedUnitCount++;
    XCTAssertEqual(root.completedUnitCount, 0);
    NSLog(@"%@", root.localizedDescription);           // 50% complete
    NSLog(@"%@", root.localizedAdditionalDescription); // 1 of 2

    // Second child completes work
    child2.completedUnitCount++;
    [root resignCurrent];
    XCTAssertEqual(root.completedUnitCount, 2);
    NSLog(@"%@", root.localizedDescription);           // 100% complete
    NSLog(@"%@", root.localizedAdditionalDescription); // 2 of 2

    // Update totalUnitCount and become current again
    root.totalUnitCount = 3;
    [root becomeCurrentWithPendingUnitCount:1];
    XCTAssertEqual(root.completedUnitCount, 2);
    XCTAssertEqual(root.totalUnitCount, 3);
    NSLog(@"%@", root.localizedDescription);           // 66% completed
    NSLog(@"%@", root.localizedAdditionalDescription); // 1 of 3 (wrong! should be 2 of 3)

    // Last child completes
    NSProgress *child3 = [NSProgress progressWithTotalUnitCount:1];
    child3.completedUnitCount++;
    [root resignCurrent];
    XCTAssertEqual(root.completedUnitCount, 3);
    NSLog(@"%@", root.localizedDescription);           // 100% completed
    NSLog(@"%@", root.localizedAdditionalDescription); // 3 of 3

【讨论】:

如果在添加孩子后立即将 resignCurrent 移到会发生什么?有什么区别吗? 马特,我确实尝试过,但结果是一样的。 :( 好吧,这只是在黑暗中的一点点跳跃。并不是说目前可以用 NSProgress 做很多其他事情:) 一年后。这最终在 ios 8 上得到修复。

以上是关于进度开始后是不是可以动态扩展 NSProgress 层次结构?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Cocoa Bindings 使用 NSProgress 更新进度条?

AFNetworking 进度方法

为啥在 AFNetworking 中使用点对点类型(NSProgress * __autoreleasing *)而不仅仅是点类型(NSProgress * __autoreleasing)?

如何并行跟踪两个 NSProgress?

iOS - 无法调用非函数类型“NSProgress”的值

NSProgress::completedUnitCount 设置器挂起