Skip to content

Commit f028eca

Browse files
authored
Merge pull request #3 from orchetect/dev-progress-labels
Operation queue cancellation progress reset improvements
2 parents cd490a3 + c8b5235 commit f028eca

File tree

5 files changed

+48
-25
lines changed

5 files changed

+48
-25
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/OTOperations-CI.xcscheme

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@
5252
BlueprintName = "OTOperationsTests"
5353
ReferencedContainer = "container:">
5454
</BuildableReference>
55-
<SkippedTests>
56-
<Test
57-
Identifier = "AtomicOperationQueue_Tests/testOp_concurrentAutomatic_Pause_Run()">
58-
</Test>
59-
</SkippedTests>
6055
</TestableReference>
6156
</Testables>
6257
</TestAction>

Sources/OTOperations/Operation/Foundational/BasicOperation.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,13 @@ open class BasicOperation: Operation, ProgressReporting {
9292

9393
progressWeight = weight
9494
super.init()
95+
9596
if label != nil { labelProgress.label = label }
9697

98+
progress.cancellationHandler = { [progress] in
99+
// automatically set progress to finished state if cancelled
100+
progress.completedUnitCount = progress.totalUnitCount
101+
}
97102
}
98103

99104
// MARK: - Method Overrides
@@ -128,7 +133,7 @@ open class BasicOperation: Operation, ProgressReporting {
128133
/// If returning early from the operation due to `isCancelled` being true, call this with the `dueToCancellation` flag set to `true` to update this operation's progress as cancelled.
129134
public final func completeOperation(dueToCancellation: Bool = false) {
130135

131-
if isCancelled || dueToCancellation {
136+
if dueToCancellation {
132137
progress.cancel()
133138
}
134139

@@ -152,6 +157,14 @@ open class BasicOperation: Operation, ProgressReporting {
152157

153158
}
154159

160+
deinit {
161+
162+
// progress object MUST ALWAYS set completed == total, even if cancelled
163+
// or it will not be released from its parent progress
164+
progress.completedUnitCount = progress.totalUnitCount
165+
166+
}
167+
155168
}
156169

157170
#endif

Sources/OTOperations/OperationQueue/AtomicOperationQueue.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,13 @@ open class AtomicOperationQueue<T>: BasicOperationQueue {
186186
_ block: @escaping (_ atomicValue: VariableAccess) -> Void
187187
) -> ClosureOperation {
188188

189-
let op = ClosureOperation(label: label,
190-
weight: weight)
189+
ClosureOperation(label: label,
190+
weight: weight)
191191
{ [weak self] in
192192
guard let self = self else { return }
193193
let varAccess = VariableAccess(operationQueue: self)
194194
block(varAccess)
195195
}
196-
return op
197196

198197
}
199198

@@ -207,14 +206,13 @@ open class AtomicOperationQueue<T>: BasicOperationQueue {
207206
_ atomicValue: VariableAccess) -> Void
208207
) -> InteractiveClosureOperation {
209208

210-
let op = InteractiveClosureOperation(label: label,
211-
weight: weight)
209+
InteractiveClosureOperation(label: label,
210+
weight: weight)
212211
{ [weak self] operation in
213212
guard let self = self else { return }
214213
let varAccess = VariableAccess(operationQueue: self)
215214
block(operation, varAccess)
216215
}
217-
return op
218216

219217
}
220218

Sources/OTOperations/OperationQueue/BasicOperationQueue.swift

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,20 @@ open class BasicOperationQueue: OperationQueue {
119119
// failsafe reset of progress to known state if queue is empty
120120
var resetTotalUnitCountNudge = false
121121
if resetProgressWhenFinished, operationCount == 0 {
122-
#if DEBUG
123-
if !progress.children.isEmpty {
124-
assertionFailure("operationCount is 0 but progress children is not empty - possible retain cycle")
122+
if let children = progress.value(forKeyPath: "children") as? NSMutableSet,
123+
children.count > 0
124+
{
125+
// this is jank, but manually remove all children
126+
children
127+
.allObjects
128+
.compactMap { $0 as? LabelProgress }
129+
.forEach {
130+
$0.setValue(nil, forKeyPath: "parent")
131+
children.remove($0)
132+
}
133+
134+
//assertionFailure("operationCount is 0 but progress children is not empty - possible retain cycle")
125135
}
126-
#endif
127136

128137
progress.completedUnitCount = 0
129138
progress.totalUnitCount = 1
@@ -184,11 +193,20 @@ open class BasicOperationQueue: OperationQueue {
184193
// failsafe reset of progress to known state if queue is empty
185194
var resetTotalUnitCountNudge = false
186195
if resetProgressWhenFinished, operationCount == 0 {
187-
#if DEBUG
188-
if !progress.children.isEmpty {
189-
assertionFailure("operationCount is 0 but progress children is not empty - possible retain cycle")
196+
if let children = progress.value(forKeyPath: "children") as? NSMutableSet,
197+
children.count > 0
198+
{
199+
// this is jank, but manually remove all children
200+
children
201+
.allObjects
202+
.compactMap { $0 as? LabelProgress }
203+
.forEach {
204+
$0.setValue(nil, forKeyPath: "parent")
205+
children.remove($0)
206+
}
207+
208+
//assertionFailure("operationCount is 0 but progress children is not empty - possible retain cycle")
190209
}
191-
#endif
192210

193211
progress.completedUnitCount = 0
194212
progress.totalUnitCount = 1

Tests/OTOperationsTests/Operation/Closure/AsyncClosureOperation Tests.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,9 @@ final class AsyncClosureOperation_Tests: XCTestCase {
113113
XCTAssertTrue(op.isCancelled)
114114
XCTAssertTrue(op.isExecuting)
115115
// progress
116-
XCTAssertFalse(op.progress.isFinished)
116+
XCTAssertTrue(op.progress.isFinished)
117117
XCTAssertTrue(op.progress.isCancelled)
118-
XCTAssertEqual(op.progress.fractionCompleted, 0.0)
119-
XCTAssertLessThan(op.progress.fractionCompleted, 1.0)
118+
XCTAssertEqual(op.progress.fractionCompleted, 1.0)
120119
XCTAssertFalse(op.progress.isIndeterminate)
121120

122121
}
@@ -210,9 +209,9 @@ final class AsyncClosureOperation_Tests: XCTestCase {
210209
XCTAssertTrue(op.isCancelled)
211210
XCTAssertTrue(op.isExecuting) // still executing
212211
// progress - operation
213-
XCTAssertFalse(op.progress.isFinished)
212+
XCTAssertTrue(op.progress.isFinished)
214213
XCTAssertTrue(op.progress.isCancelled)
215-
XCTAssertEqual(op.progress.fractionCompleted, 0.0)
214+
XCTAssertEqual(op.progress.fractionCompleted, 1.0)
216215
XCTAssertFalse(op.progress.isIndeterminate)
217216
// progress - queue
218217
XCTAssertTrue(opQ.progress.isFinished) // even if the async op is still running, this will be true now

0 commit comments

Comments
 (0)