Why does Progress.fractionCompleted exceed 1.0?

I first discovered this behavior while using the MultipeerConnectivity framework by building a progress tree for sending multiple files via MCSession.sendResource (which by the way is much stranger than the result of the code below: fractionCompleted jumps all over the place, e.g. from 0.5 to 1.4 to 0.7 to 2.8)


I know that the property fractionCompleted is not supposed to be used for checking completion, however it is meant to be used for the UI, hence anything above 1.0 is not acceptable.


The following code causes Progress.fractionCompleted property to exceed 1.


let childUnitCounts: [(initialCompletedUnitCount: Int64, totalUnitCount: Int64)] = [
  (9855, 34375),
  (10950, 19110),
  (9855, 37511),
  (9855, 18799),
  (9855, 42705),
  (10950, 19021), // When commenting out this line or setting the initial unit count to 0, things work as expected.
]

let parent = Progress()
parent.totalUnitCount = Int64(childUnitCounts.count)

let children: [Progress] = childUnitCounts.map { unitCount in

  let child = Progress(totalUnitCount: unitCount.totalUnitCount)
  child.completedUnitCount = unitCount.initialCompletedUnitCount
  parent.addChild(child, withPendingUnitCount: 1)

  return child
}

print(parent)

for (i, state) in childUnitCounts.enumerated() {
  children[i].completedUnitCount = state.totalUnitCount
  print(parent)
}


And this is the output:


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 0.4088 / Completed: 0 of 6

<NSProgress: 0x6000001708c0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2867 / Completed: 9855 of 34375

<NSProgress: 0x600000170be0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021

<NSProgress: 0x600000170b40> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705

<NSProgress: 0x600000170aa0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799

<NSProgress: 0x600000170a00> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2627 / Completed: 9855 of 37511

<NSProgress: 0x600000170960> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5730 / Completed: 10950 of 19110


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 0.5277 / Completed: 1 of 6

<NSProgress: 0x600000170be0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021

<NSProgress: 0x600000170b40> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705

<NSProgress: 0x600000170aa0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799

<NSProgress: 0x600000170a00> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2627 / Completed: 9855 of 37511

<NSProgress: 0x600000170960> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5730 / Completed: 10950 of 19110


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 0.5989 / Completed: 2 of 6

<NSProgress: 0x600000170be0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021

<NSProgress: 0x600000170b40> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705

<NSProgress: 0x600000170aa0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799

<NSProgress: 0x600000170a00> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2627 / Completed: 9855 of 37511


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 0.8094 / Completed: 3 of 6

<NSProgress: 0x600000170be0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021

<NSProgress: 0x600000170b40> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705

<NSProgress: 0x600000170aa0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5242 / Completed: 9855 of 18799


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 0.8886 / Completed: 4 of 6

<NSProgress: 0x600000170be0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021

<NSProgress: 0x600000170b40> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.2308 / Completed: 9855 of 42705


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 1.0169 / Completed: 5 of 6

<NSProgress: 0x600000170be0> : Parent: 0x600000170820 (portion: 1) / Fraction completed: 0.5757 / Completed: 10950 of 19021


<NSProgress: 0x600000170820> : Parent: 0x0 (portion: 0) / Fraction completed: 1.2795 / Completed: 6 of 6


Please help me understand what I am missing or doing wrong.

Doc says:


Declaration

var fractionCompleted: Double { get }

Discussion

If the receiver object does not have any children,

fractionCompleted
is generally the result of dividing
completedUnitCount
by
totalUnitCount
. Setting both
totalUnitCount
and
completedUnitCount
properties to zero indicates that there is no progress to track; in this case, the
isIndeterminate
property returns
false
and the
fractionCompleted
property returns
0.0
.

If the receiver does have children,

fractionCompleted
reflects progress made in child objects in addition to its own
completedUnitCount
. When children finish, the
completedUnitCount
of the parent is updated.


So, if the receiver does have children (that's your case),

fractionCompleted
reflects (temprarily) progress made in child objects in addition to its own
completedUnitCount
.

This could explain that you get a value over 1 (up to 1 + number of children).


fractionCompleted jumps all over the place, e.g. from 0.5 to 1.4 to 0.7 to 2.8)

What I understand is :

0.5 or 0.7 are the values of the parent when children finish (the

completedUnitCount
of the parent is updated)

But when children are running, the values sum up, possibly above 1.


You could filter and not update UI when figures are above 1.0 (even though filter should be more sophiticated, to take into account parent ate 0.2 and one child at 0.3 for instance…)

Thank you Claude31 for your answer.


However, I'm pretty sure that it is not the intention of the API that the child objects take over the parent's fractionCompleted property for reporting back its intermediate progress. That's why we have child objects with their own set of property values. Remember that the documentation also states that progress objects are loosely coupled, so this would somewhat violate that "feature".


It turns out that my code sample produces the correct output when running on iOS 11 and iOS 12. It seems that starting with iOS 13 fractionCompleted gets corrupted.

Effectively, reading more carefully the log, we see it never goes over 1 execpt last line:

Parent: 0x0 (portion: 0) / Fraction completed: 1.2795 / Completed: 6 of 6

Which is curious, as all children have completed apparently.

In addition, it seems that figures are those of individual child or parent, but all below 1.0.


fractionCompleted jumps all over the place, e.g. from 0.5 to 1.4 to 0.7 to 2.8)

In which others cases have you seen it exceed 1 ?



It turns out that my code sample produces the correct output when running on iOS 11 or iOS 12. It seems that starting with iOS 13 fractionCompleted gets corrupted.


You should file a bug report. And ask for doc clarification as well.


Thanks to report FB number.

The jumping around of fractionCompleted's value happens when using MultipeerConnectivity and building a progress tree with the Progress objects returned from MCSession.sendResource. That's how I became aware of this issue in the first place.


A bug report has been filed: FB7463813


For the sake of completeness, here is the same discussion and conclusion on Stack Overflow:

https://stackoverflow.com/questions/58965920/why-is-progress-fractioncompleted-exceeding-1-0

Thanks for feedback.


You could file your own bug report, that will show that the issue is not device dependant.

Why does Progress.fractionCompleted exceed 1.0?
 
 
Q