diff --git a/SwiftTask.xcodeproj/project.pbxproj b/SwiftTask.xcodeproj/project.pbxproj index 55d2778..37639a7 100644 --- a/SwiftTask.xcodeproj/project.pbxproj +++ b/SwiftTask.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 1F4C76A41AD8CF40004E47C1 /* AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5FA35619A374E600975FB9 /* AlamofireTests.swift */; }; 1F4C76A51AD8CF41004E47C1 /* AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F5FA35619A374E600975FB9 /* AlamofireTests.swift */; }; 1F6A8CA319A4E4F200369A5D /* SwiftTaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEE3199EDF1000F97868 /* SwiftTaskTests.swift */; }; + 1FB3B8F31B0A194F00F78900 /* _Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB3B8F21B0A194F00F78900 /* _Atomic.swift */; }; + 1FB3B8F41B0A194F00F78900 /* _Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB3B8F21B0A194F00F78900 /* _Atomic.swift */; }; 1FCF71121AD8CD2B007079C2 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71111AD8CD2B007079C2 /* Alamofire.framework */; }; 1FCF71141AD8CD2F007079C2 /* Async.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71131AD8CD2F007079C2 /* Async.framework */; }; 1FCF71161AD8CD38007079C2 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FCF71151AD8CD38007079C2 /* Alamofire.framework */; }; @@ -31,6 +33,8 @@ 48511C5B19C17563002FE03C /* RetainCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48511C5A19C17563002FE03C /* RetainCycleTests.swift */; }; 485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; }; 485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */; }; + 487858241B09701F0022E56A /* _RecursiveLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487858231B09701F0022E56A /* _RecursiveLock.swift */; }; + 487858251B09701F0022E56A /* _RecursiveLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487858231B09701F0022E56A /* _RecursiveLock.swift */; }; 48A1E8221A366F9C007619EB /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F46DED4199EDF1000F97868 /* SwiftTask.framework */; }; 48A1E8231A366FA8007619EB /* SwiftTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48CD5A0C19AEE3570042B9F1 /* SwiftTask.framework */; }; 48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */; }; @@ -51,6 +55,7 @@ 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftTask.swift; sourceTree = ""; }; 1F46DEFC199EE2C200F97868 /* _TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _TestCase.swift; sourceTree = ""; }; 1F5FA35619A374E600975FB9 /* AlamofireTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlamofireTests.swift; sourceTree = ""; }; + 1FB3B8F21B0A194F00F78900 /* _Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Atomic.swift; sourceTree = ""; }; 1FCF71111AD8CD2B007079C2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "../Carthage/Checkouts/Alamofire/build/Debug-iphoneos/Alamofire.framework"; sourceTree = ""; }; 1FCF71131AD8CD2F007079C2 /* Async.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Async.framework; path = "../Carthage/Checkouts/Async/build/Debug-iphoneos/Async.framework"; sourceTree = ""; }; 1FCF71151AD8CD38007079C2 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = ../Carthage/Checkouts/Alamofire/build/Debug/Alamofire.framework; sourceTree = ""; }; @@ -60,6 +65,7 @@ 4822F0D019D00ABF00F5F572 /* SwiftTask-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftTask-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 48511C5A19C17563002FE03C /* RetainCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetainCycleTests.swift; sourceTree = ""; }; 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeInferenceTests.swift; sourceTree = ""; }; + 487858231B09701F0022E56A /* _RecursiveLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _RecursiveLock.swift; sourceTree = ""; }; 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _StateMachine.swift; sourceTree = ""; }; 48CD5A0C19AEE3570042B9F1 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -129,6 +135,8 @@ 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */, 1FD7197A1AFE387C00BC38C4 /* Cancellable.swift */, 48B58D7A1A6F255E0068E18C /* _StateMachine.swift */, + 487858231B09701F0022E56A /* _RecursiveLock.swift */, + 1FB3B8F21B0A194F00F78900 /* _Atomic.swift */, 1F46DED7199EDF1000F97868 /* Supporting Files */, ); path = SwiftTask; @@ -345,6 +353,8 @@ 1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */, 1FD7197B1AFE387C00BC38C4 /* Cancellable.swift in Sources */, 48B58D7B1A6F255E0068E18C /* _StateMachine.swift in Sources */, + 1FB3B8F31B0A194F00F78900 /* _Atomic.swift in Sources */, + 487858241B09701F0022E56A /* _RecursiveLock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -385,6 +395,8 @@ 48CD5A3C19AEEBDF0042B9F1 /* SwiftTask.swift in Sources */, 1FD7197C1AFE387C00BC38C4 /* Cancellable.swift in Sources */, 48B58D7C1A6F255E0068E18C /* _StateMachine.swift in Sources */, + 1FB3B8F41B0A194F00F78900 /* _Atomic.swift in Sources */, + 487858251B09701F0022E56A /* _RecursiveLock.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftTask/SwiftTask.swift b/SwiftTask/SwiftTask.swift index 503151a..63ba4bb 100644 --- a/SwiftTask/SwiftTask.swift +++ b/SwiftTask/SwiftTask.swift @@ -84,16 +84,16 @@ public class Task: Cancellable, Printable internal let _paused: Bool internal var _initClosure: _InitClosure! // retained throughout task's lifetime - public var state: TaskState { return self._machine.state } + public var state: TaskState { return self._machine.state.rawValue } /// progress value (NOTE: always nil when `weakified = true`) - public var progress: Progress? { return self._machine.progress } + public var progress: Progress? { return self._machine.progress.rawValue } /// fulfilled value - public var value: Value? { return self._machine.value } + public var value: Value? { return self._machine.value.rawValue } /// rejected/cancelled tuple info - public var errorInfo: ErrorInfo? { return self._machine.errorInfo } + public var errorInfo: ErrorInfo? { return self._machine.errorInfo.rawValue } public var name: String = "DefaultTask" @@ -424,7 +424,7 @@ public class Task: Cancellable, Printable let selfMachine = self._machine self._then(&canceller) { - let innerTask = thenClosure(selfMachine.value, selfMachine.errorInfo) + let innerTask = thenClosure(selfMachine.value.rawValue, selfMachine.errorInfo.rawValue) _bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure) } @@ -484,11 +484,11 @@ public class Task: Cancellable, Printable // NOTE: using `self._then()` + `selfMachine` instead of `self.then()` will reduce Task allocation self._then(&canceller) { - if let value = selfMachine.value { + if let value = selfMachine.value.rawValue { let innerTask = successClosure(value) _bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure) } - else if let errorInfo = selfMachine.errorInfo { + else if let errorInfo = selfMachine.errorInfo.rawValue { _reject(errorInfo) } } @@ -534,10 +534,10 @@ public class Task: Cancellable, Printable let selfMachine = self._machine self._then(&canceller) { - if let value = selfMachine.value { + if let value = selfMachine.value.rawValue { fulfill(value) } - else if let errorInfo = selfMachine.errorInfo { + else if let errorInfo = selfMachine.errorInfo.rawValue { let innerTask = failureClosure(errorInfo) _bindInnerTask(innerTask, newMachine, progress, fulfill, _reject, configure) } @@ -617,10 +617,10 @@ internal func _bindInnerTask( configure.cancel = { innerTask.cancel(); return } // pause/cancel innerTask if descendant task is already paused/cancelled - if newMachine.state == .Paused { + if newMachine.state.rawValue == .Paused { innerTask.pause() } - else if newMachine.state == .Cancelled { + else if newMachine.state.rawValue == .Cancelled { innerTask.cancel() } } @@ -637,36 +637,37 @@ extension Task var completedCount = 0 let totalCount = tasks.count + let lock = _RecursiveLock() for task in tasks { task.success { (value: Value) -> Void in - synchronized(self) { - completedCount++ - - let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount) - progress(progressTuple) + lock.lock() + completedCount++ + + let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount) + progress(progressTuple) + + if completedCount == totalCount { + var values: [Value] = Array() - if completedCount == totalCount { - var values: [Value] = Array() - - for task in tasks { - values.append(task.value!) - } - - fulfill(values) + for task in tasks { + values.append(task.value!) } + + fulfill(values) } + lock.unlock() }.failure { (errorInfo: ErrorInfo) -> Void in - synchronized(self) { - _reject(errorInfo) - - for task in tasks { - task.cancel() - } + lock.lock() + _reject(errorInfo) + + for task in tasks { + task.cancel() } + lock.unlock() } } @@ -684,32 +685,33 @@ extension Task var completedCount = 0 var rejectedCount = 0 let totalCount = tasks.count + let lock = _RecursiveLock() for task in tasks { task.success { (value: Value) -> Void in - synchronized(self) { - completedCount++ + lock.lock() + completedCount++ + + if completedCount == 1 { + fulfill(value) - if completedCount == 1 { - fulfill(value) - - self.cancelAll(tasks) - } + self.cancelAll(tasks) } + lock.unlock() }.failure { (errorInfo: ErrorInfo) -> Void in - synchronized(self) { - rejectedCount++ + lock.lock() + rejectedCount++ + + if rejectedCount == totalCount { + var isAnyCancelled = (tasks.filter { task in task.state == .Cancelled }.count > 0) - if rejectedCount == totalCount { - var isAnyCancelled = (tasks.filter { task in task.state == .Cancelled }.count > 0) - - let errorInfo = ErrorInfo(error: nil, isCancelled: isAnyCancelled) // NOTE: Task.any error returns nil (spec) - _reject(errorInfo) - } + let errorInfo = ErrorInfo(error: nil, isCancelled: isAnyCancelled) // NOTE: Task.any error returns nil (spec) + _reject(errorInfo) } + lock.unlock() } } @@ -728,28 +730,29 @@ extension Task var completedCount = 0 let totalCount = tasks.count + let lock = _RecursiveLock() for task in tasks { task.then { (value: Value?, errorInfo: ErrorInfo?) -> Void in - synchronized(self) { - completedCount++ - - let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount) - progress(progressTuple) + lock.lock() + completedCount++ + + let progressTuple = BulkProgress(completedCount: completedCount, totalCount: totalCount) + progress(progressTuple) + + if completedCount == totalCount { + var values: [Value] = Array() - if completedCount == totalCount { - var values: [Value] = Array() - - for task in tasks { - if task.state == .Fulfilled { - values.append(task.value!) - } + for task in tasks { + if task.state == .Fulfilled { + values.append(task.value!) } - - fulfill(values) } + + fulfill(values) } + lock.unlock() } } @@ -795,15 +798,4 @@ infix operator ~ { associativity left } public func ~ (task: Task, tryCount: Int) -> Task { return task.try(tryCount) -} - -//-------------------------------------------------- -// MARK: - Utility -//-------------------------------------------------- - -internal func synchronized(object: AnyObject, closure: Void -> Void) -{ - objc_sync_enter(object) - closure() - objc_sync_exit(object) } \ No newline at end of file diff --git a/SwiftTask/_Atomic.swift b/SwiftTask/_Atomic.swift new file mode 100644 index 0000000..d3dd889 --- /dev/null +++ b/SwiftTask/_Atomic.swift @@ -0,0 +1,47 @@ +// +// _Atomic.swift +// SwiftTask +// +// Created by Yasuhiro Inami on 2015/05/18. +// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. +// + +import Darwin + +internal final class _Atomic +{ + private var spinlock = OS_SPINLOCK_INIT + private var _rawValue: T + + internal var rawValue: T + { + get { + lock() + let rawValue = self._rawValue + unlock() + + return rawValue + } + + set(newValue) { + lock() + self._rawValue = newValue + unlock() + } + } + + init(_ rawValue: T) + { + self._rawValue = rawValue + } + + private func lock() + { + withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) + } + + private func unlock() + { + withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) + } +} \ No newline at end of file diff --git a/SwiftTask/_RecursiveLock.swift b/SwiftTask/_RecursiveLock.swift new file mode 100644 index 0000000..9e418ec --- /dev/null +++ b/SwiftTask/_RecursiveLock.swift @@ -0,0 +1,41 @@ +// +// _RecursiveLock.swift +// SwiftTask +// +// Created by Yasuhiro Inami on 2015/05/18. +// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. +// + +import Darwin + +internal final class _RecursiveLock +{ + private let mutex: UnsafeMutablePointer + private let attribute: UnsafeMutablePointer + + internal init() + { + self.mutex = UnsafeMutablePointer.alloc(sizeof(pthread_mutex_t)) + self.attribute = UnsafeMutablePointer.alloc(sizeof(pthread_mutexattr_t)) + + pthread_mutexattr_init(self.attribute) + pthread_mutexattr_settype(self.attribute, PTHREAD_MUTEX_RECURSIVE) + pthread_mutex_init(self.mutex, self.attribute) + } + + deinit + { + pthread_mutexattr_destroy(self.attribute) + pthread_mutex_destroy(self.mutex) + } + + internal func lock() + { + pthread_mutex_lock(self.mutex) + } + + internal func unlock() + { + pthread_mutex_unlock(self.mutex) + } +} \ No newline at end of file diff --git a/SwiftTask/_StateMachine.swift b/SwiftTask/_StateMachine.swift index ba1da0e..c5c6dd8 100644 --- a/SwiftTask/_StateMachine.swift +++ b/SwiftTask/_StateMachine.swift @@ -20,106 +20,146 @@ internal class _StateMachine internal typealias ProgressTupleHandler = Task._ProgressTupleHandler internal let weakified: Bool - internal private(set) var state: TaskState + internal let state: _Atomic - internal private(set) var progress: Progress? // NOTE: always nil if `weakified = true` - internal private(set) var value: Value? - internal private(set) var errorInfo: ErrorInfo? + internal let progress: _Atomic = _Atomic(nil) // NOTE: always nil if `weakified = true` + internal let value: _Atomic = _Atomic(nil) + internal let errorInfo: _Atomic = _Atomic(nil) + + internal let configuration = TaskConfiguration() /// wrapper closure for `_initClosure` to invoke only once when started `.Running`, /// and will be set to `nil` afterward internal var initResumeClosure: (Void -> Void)? - internal private(set) lazy var progressTupleHandlers = _Handlers() - internal private(set) lazy var completionHandlers = _Handlers Void>() + private lazy var _progressTupleHandlers = _Handlers() + private lazy var _completionHandlers = _Handlers Void>() - internal let configuration = TaskConfiguration() + private let _recursiveLock = _RecursiveLock() internal init(weakified: Bool, paused: Bool) { self.weakified = weakified - self.state = paused ? .Paused : .Running + self.state = _Atomic(paused ? .Paused : .Running) } internal func addProgressTupleHandler(inout token: _HandlerToken?, _ progressTupleHandler: ProgressTupleHandler) -> Bool { - if self.state == .Running || self.state == .Paused { - token = self.progressTupleHandlers.append(progressTupleHandler) + self._recursiveLock.lock() + if self.state.rawValue == .Running || self.state.rawValue == .Paused { + token = self._progressTupleHandlers.append(progressTupleHandler) + self._recursiveLock.unlock() return token != nil } - return false + else { + self._recursiveLock.unlock() + return false + } } internal func removeProgressTupleHandler(handlerToken: _HandlerToken?) -> Bool { + self._recursiveLock.lock() if let handlerToken = handlerToken { - let removedHandler = self.progressTupleHandlers.remove(handlerToken) + let removedHandler = self._progressTupleHandlers.remove(handlerToken) + self._recursiveLock.unlock() return removedHandler != nil } - return false + else { + self._recursiveLock.unlock() + return false + } } internal func addCompletionHandler(inout token: _HandlerToken?, _ completionHandler: Void -> Void) -> Bool { - if self.state == .Running || self.state == .Paused { - token = self.completionHandlers.append(completionHandler) + self._recursiveLock.lock() + if self.state.rawValue == .Running || self.state.rawValue == .Paused { + token = self._completionHandlers.append(completionHandler) + self._recursiveLock.unlock() return token != nil } - return false + else { + self._recursiveLock.unlock() + return false + } } internal func removeCompletionHandler(handlerToken: _HandlerToken?) -> Bool { + self._recursiveLock.lock() if let handlerToken = handlerToken { - let removedHandler = self.completionHandlers.remove(handlerToken) + let removedHandler = self._completionHandlers.remove(handlerToken) + self._recursiveLock.unlock() return removedHandler != nil } - return false + else { + self._recursiveLock.unlock() + return false + } } internal func handleProgress(progress: Progress) { - if self.state == .Running { + self._recursiveLock.lock() + if self.state.rawValue == .Running { - let oldProgress = self.progress + let oldProgress = self.progress.rawValue // NOTE: if `weakified = false`, don't store progressValue for less memory footprint if !self.weakified { - self.progress = progress + self.progress.rawValue = progress } - for handler in self.progressTupleHandlers { + for handler in self._progressTupleHandlers { handler(oldProgress: oldProgress, newProgress: progress) } + self._recursiveLock.unlock() + } + else { + self._recursiveLock.unlock() } } internal func handleFulfill(value: Value) { - if self.state == .Running { - self.state = .Fulfilled - self.value = value - self.finish() + self._recursiveLock.lock() + if self.state.rawValue == .Running { + self.state.rawValue = .Fulfilled + self.value.rawValue = value + self._finish() + self._recursiveLock.unlock() + } + else { + self._recursiveLock.unlock() } } internal func handleRejectInfo(errorInfo: ErrorInfo) { - if self.state == .Running || self.state == .Paused { - self.state = errorInfo.isCancelled ? .Cancelled : .Rejected - self.errorInfo = errorInfo - self.finish() + self._recursiveLock.lock() + if self.state.rawValue == .Running || self.state.rawValue == .Paused { + self.state.rawValue = errorInfo.isCancelled ? .Cancelled : .Rejected + self.errorInfo.rawValue = errorInfo + self._finish() + self._recursiveLock.unlock() + } + else { + self._recursiveLock.unlock() } } internal func handlePause() -> Bool { - if self.state == .Running { + self._recursiveLock.lock() + if self.state.rawValue == .Running { self.configuration.pause?() - self.state = .Paused + self.state.rawValue = .Paused + self._recursiveLock.unlock() return true } else { + self._recursiveLock.unlock() return false } } @@ -135,9 +175,15 @@ internal class _StateMachine // which eventually calls upstream's `initResumeClosure` // and thus upstream starts sending values. // + + self._recursiveLock.lock() + self._handleInitResumeIfNeeded() + let resumed = _handleResume() + + self._recursiveLock.unlock() - return _handleResume() + return resumed } /// @@ -150,10 +196,9 @@ internal class _StateMachine { if (self.initResumeClosure != nil) { - let isInitPaused = (self.state == .Paused) - + let isInitPaused = (self.state.rawValue == .Paused) if isInitPaused { - self.state = .Running // switch `.Paused` => `.Resume` temporarily without invoking `configure.resume()` + self.state.rawValue = .Running // switch `.Paused` => `.Resume` temporarily without invoking `configure.resume()` } // NOTE: performing `initResumeClosure` might change `state` to `.Fulfilled` or `.Rejected` **immediately** @@ -162,17 +207,17 @@ internal class _StateMachine // switch back to `.Paused` if temporary `.Running` has not changed // so that consecutive `_handleResume()` can perform `configure.resume()` - if isInitPaused && self.state == .Running { - self.state = .Paused + if isInitPaused && self.state.rawValue == .Running { + self.state.rawValue = .Paused } } } private func _handleResume() -> Bool { - if self.state == .Paused { + if self.state.rawValue == .Paused { self.configuration.resume?() - self.state = .Running + self.state.rawValue = .Running return true } else { @@ -182,30 +227,33 @@ internal class _StateMachine internal func handleCancel(error: Error? = nil) -> Bool { - if self.state == .Running || self.state == .Paused { - self.state = .Cancelled - self.errorInfo = ErrorInfo(error: error, isCancelled: true) - self.finish() + self._recursiveLock.lock() + if self.state.rawValue == .Running || self.state.rawValue == .Paused { + self.state.rawValue = .Cancelled + self.errorInfo.rawValue = ErrorInfo(error: error, isCancelled: true) + self._finish() + self._recursiveLock.unlock() return true } else { + self._recursiveLock.unlock() return false } } - internal func finish() + private func _finish() { - for handler in self.completionHandlers { + for handler in self._completionHandlers { handler() } - self.progressTupleHandlers.removeAll() - self.completionHandlers.removeAll() + self._progressTupleHandlers.removeAll() + self._completionHandlers.removeAll() self.configuration.finish() self.initResumeClosure = nil - self.progress = nil + self.progress.rawValue = nil } }