From 2b1a7dc5a6011ade5d92b52997bbf69f0e582790 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 18 Sep 2018 21:41:06 -0600 Subject: [PATCH 01/14] refer to package "Outcome" in manifest --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index bbea4a4..1317e02 100644 --- a/Package.swift +++ b/Package.swift @@ -15,9 +15,10 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/glessard/swift-atomics.git", from: "4.1.0"), + .package(path: "../outcome") ], targets: [ - .target(name: "deferred", dependencies: ["CAtomics"]), + .target(name: "deferred", dependencies: ["CAtomics", "Outcome"]), .testTarget(name: "deferredTests", dependencies: ["deferred"]), ], swiftLanguageVersions: versions From c50546f0ea3c460e91a83989be48006205369fa8 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 08:45:53 -0600 Subject: [PATCH 02/14] use Outcome in the api of Deferred --- Source/deferred/deferred.swift | 36 +++++++++++++++--------------- Source/deferred/nsurlsession.swift | 3 ++- Source/deferred/waiter.swift | 9 ++++---- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Source/deferred/deferred.swift b/Source/deferred/deferred.swift index d7da25c..caad6d5 100644 --- a/Source/deferred/deferred.swift +++ b/Source/deferred/deferred.swift @@ -7,7 +7,7 @@ // import Dispatch - +import Outcome import CAtomics /// The possible states of a `Deferred`. @@ -48,7 +48,7 @@ open class Deferred let queue: DispatchQueue private var source: AnyObject? - private var determination: Determined? + private var determination: Outcome? private var waiters = AtomicMutableRawPointer() private var stateid = AtomicInt32() @@ -85,7 +85,7 @@ open class Deferred /// - parameter queue: the dispatch queue upon which to execute future notifications for this `Deferred` /// - parameter result: the result of this `Deferred` - public init(queue: DispatchQueue, result: Determined) + public init(queue: DispatchQueue, result: Outcome) { self.queue = queue source = nil @@ -149,7 +149,7 @@ open class Deferred public convenience init(queue: DispatchQueue, value: Value) { - self.init(queue: queue, result: Determined(value: value)) + self.init(queue: queue, result: Outcome(value: value)) } /// Initialize with an Error @@ -170,7 +170,7 @@ open class Deferred public convenience init(queue: DispatchQueue, error: Error) { - self.init(queue: queue, result: Determined(error: error)) + self.init(queue: queue, result: Outcome(error: error)) } // MARK: state changes / determine @@ -197,7 +197,7 @@ open class Deferred /// - returns: whether the call succesfully changed the state of this `Deferred`. @discardableResult - fileprivate func determine(_ result: Determined) -> Bool + fileprivate func determine(_ result: Outcome) -> Bool { var current = stateid.load(.relaxed) repeat { // keep trying if another thread hasn't succeeded yet @@ -240,7 +240,7 @@ open class Deferred @discardableResult fileprivate func determine(_ value: Value) -> Bool { - return determine(Determined(value: value)) + return determine(Outcome(value: value)) } /// Set this `Deferred` to an error and dispatch all notifications for execution. @@ -254,7 +254,7 @@ open class Deferred @discardableResult fileprivate func determine(_ error: Error) -> Bool { - return determine(Determined(error: error)) + return determine(Outcome(error: error)) } // MARK: public interface @@ -273,7 +273,7 @@ open class Deferred /// - parameter task: a closure to be executed after `self` becomes determined. /// - parameter result: the determined result of `self` - open func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (_ result: Determined) -> Void) + open func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (_ result: Outcome) -> Void) { var waitQueue = waiters.load(.acquire) if waitQueue != .determined @@ -315,7 +315,7 @@ open class Deferred /// - parameter task: a closure to be executed as a notification /// - parameter result: the determined result of `self` - public func notify(queue: DispatchQueue? = nil, task: @escaping (_ result: Determined) -> Void) + public func notify(queue: DispatchQueue? = nil, task: @escaping (_ result: Outcome) -> Void) { enqueue(queue: queue, task: { value in withExtendedLifetime(self) { task(value) } }) } @@ -357,13 +357,13 @@ open class Deferred return determine(error) } - /// Get this `Deferred`'s `Determined` result, blocking if necessary until it exists. + /// Get this `Deferred`'s `Outcome` result, blocking if necessary until it exists. /// When called on a `Deferred` that is already determined, this call is non-blocking. /// When called on a `Deferred` that is not determined, this call blocks the executing thread. /// /// - returns: this `Deferred`'s determined result - public var result: Determined { + public var result: Outcome { if waiters.load(.acquire) != .determined { if let current = DispatchQoS.QoSClass.current, current > queue.qos.qosClass @@ -393,12 +393,12 @@ open class Deferred return try result.get() } - /// Get this `Deferred`'s `Determined` result if exists, `nil` otherwise. + /// Get this `Deferred`'s `Outcome` result if exists, `nil` otherwise. /// This call is non-blocking. /// /// - returns: this `Deferred`'s determined result, or `nil` - public func peek() -> Determined? + public func peek() -> Outcome? { if waiters.load(.acquire) == .determined { @@ -519,7 +519,7 @@ class Flatten: Deferred } } catch { - super.init(queue: mine, result: Determined(error: error)) + super.init(queue: mine, result: Outcome(error: error)) } return } @@ -739,7 +739,7 @@ open class TBD: Deferred /// - returns: whether the call succesfully changed the state of this `Deferred`. @discardableResult - open override func determine(_ result: Determined) -> Bool + open override func determine(_ result: Outcome) -> Bool { return super.determine(result) } @@ -755,7 +755,7 @@ open class TBD: Deferred @discardableResult open override func determine(_ value: Value) -> Bool { - return determine(Determined(value: value)) + return determine(Outcome(value: value)) } /// Set this `Deferred` to an error and dispatch all notifications for execution. @@ -769,7 +769,7 @@ open class TBD: Deferred @discardableResult open override func determine(_ error: Error) -> Bool { - return determine(Determined(error: error)) + return determine(Outcome(error: error)) } /// Change the state of this `TBD` from `.waiting` to `.executing` diff --git a/Source/deferred/nsurlsession.swift b/Source/deferred/nsurlsession.swift index bf2212e..9d07d5f 100644 --- a/Source/deferred/nsurlsession.swift +++ b/Source/deferred/nsurlsession.swift @@ -7,6 +7,7 @@ // import Dispatch +import Outcome import Foundation //import struct Foundation.Data @@ -49,7 +50,7 @@ class DeferredURLSessionTask: Transfer return true } - public override func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (Determined) -> Void) + public override func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (Outcome) -> Void) { if state == .waiting { diff --git a/Source/deferred/waiter.swift b/Source/deferred/waiter.swift index 27d325d..681ed7b 100644 --- a/Source/deferred/waiter.swift +++ b/Source/deferred/waiter.swift @@ -7,27 +7,28 @@ // import Dispatch +import Outcome struct Waiter { private let queue: DispatchQueue? - private let handler: (Determined) -> Void + private let handler: (Outcome) -> Void var next: UnsafeMutablePointer>? = nil - init(_ queue: DispatchQueue?, _ handler: @escaping (Determined) -> Void) + init(_ queue: DispatchQueue?, _ handler: @escaping (Outcome) -> Void) { self.queue = queue self.handler = handler } - fileprivate func notify(_ queue: DispatchQueue, _ value: Determined) + fileprivate func notify(_ queue: DispatchQueue, _ value: Outcome) { let q = self.queue ?? queue q.async { [handler = self.handler] in handler(value) } } } -func notifyWaiters(_ queue: DispatchQueue, _ tail: UnsafeMutablePointer>?, _ value: Determined) +func notifyWaiters(_ queue: DispatchQueue, _ tail: UnsafeMutablePointer>?, _ value: Outcome) { var head = reverseList(tail) while let current = head From ce9754e5eff7df70e98a0d1fa8e8d00b72390652 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 08:50:06 -0600 Subject: [PATCH 03/14] replace Determined by a typealias --- Source/deferred/determined.swift | 109 +------------------------------ 1 file changed, 3 insertions(+), 106 deletions(-) diff --git a/Source/deferred/determined.swift b/Source/deferred/determined.swift index 38940b8..08043cd 100644 --- a/Source/deferred/determined.swift +++ b/Source/deferred/determined.swift @@ -2,112 +2,9 @@ // determined.swift // deferred // -// Created by Guillaume Lessard on 9/26/17. -// Copyright © 2017 Guillaume Lessard. All rights reserved. +// Created by Guillaume Lessard on 9/19/18. // -public struct Determined -{ - fileprivate let state: State +import Outcome - public init(value: Value) - { - state = .value(value) - } - - public init(error: Error) - { - state = .error(error) - } - - public func get() throws -> Value - { - switch state - { - case .value(let value): return value - case .error(let error): throw error - } - } - - public var value: Value? { - if case .value(let value) = state { return value } - return nil - } - - public var error: Error? { - if case .error(let error) = state { return error } - return nil - } - - public var isValue: Bool { - if case .value = state { return true } - return false - } - - public var isError: Bool { - if case .error = state { return true } - return false - } -} - -extension Determined: CustomStringConvertible -{ - public var description: String { - switch state - { - case .value(let value): return String(describing: value) - case .error(let error): return "Error: \(error)" - } - } -} - -#if swift (>=4.1) -extension Determined: Equatable where Value: Equatable -{ - public static func ==(lhs: Determined, rhs: Determined) -> Bool - { - switch (lhs.state, rhs.state) - { - case (.value(let lhv), .value(let rhv)): - return lhv == rhv - case (.error(let lhe), .error(let rhe)): - return String(describing: lhe) == String(describing: rhe) - default: - return false - } - } -} -#endif - -#if swift (>=4.2) -extension Determined: Hashable where Value: Hashable -{ - public func hash(into hasher: inout Hasher) - { - switch self.state - { - case .value(let value): - Int(0).hash(into: &hasher) - value.hash(into: &hasher) - case .error(let error): - Int(1).hash(into: &hasher) - String(describing: error).hash(into: &hasher) - } - } -} -#endif - -#if swift (>=4.2) -@usableFromInline -enum State -{ - case value(Value) - case error(Error) -} -#else -enum State -{ -case value(Value) -case error(Error) -} -#endif +public typealias Determined = Outcome From 1e7c19d2869267ee4d9a3f36510daa6973ca0b2b Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 08:50:21 -0600 Subject: [PATCH 04/14] update available tests --- Tests/deferredTests/DeterminedTests.swift | 87 ----------------------- Tests/deferredTests/XCTestManifests.swift | 10 --- Xcode/deferred.xcodeproj/project.pbxproj | 6 -- 3 files changed, 103 deletions(-) delete mode 100644 Tests/deferredTests/DeterminedTests.swift diff --git a/Tests/deferredTests/DeterminedTests.swift b/Tests/deferredTests/DeterminedTests.swift deleted file mode 100644 index ed7874c..0000000 --- a/Tests/deferredTests/DeterminedTests.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// DeterminedTests.swift -// deferred -// -// Created by Guillaume Lessard on 6/5/18. -// Copyright © 2018 Guillaume Lessard. All rights reserved. -// - -import XCTest - -import deferred - -class DeterminedTests: XCTestCase -{ - func testEquals() - { -#if swift(>=4.1) - let i1 = nzRandom() - let i2 = nzRandom() - let i3 = i1*i2 - - let o3 = Determined(value: i1*i2) - XCTAssert(o3 == Determined(value: i3)) - XCTAssert(o3 != Determined(value: i2)) - - var o4 = o3 - o4 = Determined(error: TestError(i1)) - XCTAssert(o3 != o4) - XCTAssert(o4 != Determined(error: TestError(i2))) -#endif - } - - func testHashable() - { -#if swift(>=4.2) - let d1 = Determined(value: nzRandom()) - let d2 = Determined(error: TestError(nzRandom())) - - let set = Set([d1, d2]) - - XCTAssert(set.contains(d1)) -#endif - } - - func testGetters() throws - { - let v1 = nzRandom() - let v2 = nzRandom() - - let detValue = Determined(value: v1) - let detError = Determined(error: TestError(v2)) - - do { - let v = try detValue.get() - XCTAssert(v1 == v) - - let _ = try detError.get() - } - catch let error as TestError { - XCTAssert(v2 == error.error) - } - - XCTAssertNotNil(detValue.value) - XCTAssertNotNil(detError.error) - - XCTAssertNil(detValue.error) - XCTAssertNil(detError.value) - - XCTAssertTrue(detValue.isValue) - XCTAssertTrue(detError.isError) - - XCTAssertFalse(detValue.isError) - XCTAssertFalse(detError.isValue) - } - - func testCustomStringConvertible() throws - { - let value = Determined(value: 1) - let error = value.isError ? value : Determined(error: TestError(1)) - - let v = String(describing: value) - let e = String(describing: error) - - XCTAssert(v != e) - // print(v, "\n", e, separator: "") - } -} diff --git a/Tests/deferredTests/XCTestManifests.swift b/Tests/deferredTests/XCTestManifests.swift index e584ebe..9f583f7 100644 --- a/Tests/deferredTests/XCTestManifests.swift +++ b/Tests/deferredTests/XCTestManifests.swift @@ -77,15 +77,6 @@ extension DeletionTests { ] } -extension DeterminedTests { - static let __allTests = [ - ("testCustomStringConvertible", testCustomStringConvertible), - ("testEquals", testEquals), - ("testGetters", testGetters), - ("testHashable", testHashable), - ] -} - extension DispatchUtilitiesTests { static let __allTests = [ ("testCurrent", testCurrent), @@ -125,7 +116,6 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(DeferredCombinationTimedTests.__allTests), testCase(DeferredTests.__allTests), testCase(DeletionTests.__allTests), - testCase(DeterminedTests.__allTests), testCase(DispatchUtilitiesTests.__allTests), testCase(TBDTests.__allTests), testCase(TBDTimingTests.__allTests), diff --git a/Xcode/deferred.xcodeproj/project.pbxproj b/Xcode/deferred.xcodeproj/project.pbxproj index a7142c6..bedf251 100644 --- a/Xcode/deferred.xcodeproj/project.pbxproj +++ b/Xcode/deferred.xcodeproj/project.pbxproj @@ -20,7 +20,6 @@ 0366AE5A1DF0FEC400F4B44B /* TBDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0366AE521DF0FEC400F4B44B /* TBDTests.swift */; }; 0366AE5C1DF0FEC400F4B44B /* TBDTimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0366AE531DF0FEC400F4B44B /* TBDTimingTests.swift */; }; 0366AE5E1DF0FEC400F4B44B /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0366AE541DF0FEC400F4B44B /* TestError.swift */; }; - 0370F2D620C8E25400F4B44B /* DeterminedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0370F2D520C8E25400F4B44B /* DeterminedTests.swift */; }; 03857DB81F55C3DE00F4B44B /* deferred-timing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03857DB71F55C3DE00F4B44B /* deferred-timing.swift */; }; 038CB65620DC637600F4B44B /* CAtomics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 038CB64F20DC636D00F4B44B /* CAtomics.framework */; }; 038CB65920DC639400F4B44B /* CAtomics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 038CB64F20DC636D00F4B44B /* CAtomics.framework */; }; @@ -39,7 +38,6 @@ 03B7769C20CAED1C00F4B44B /* DeferredCombinationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AA21731E3FF9D600F4B44B /* DeferredCombinationTests.swift */; }; 03B7769D20CAED1C00F4B44B /* DeletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038147591E4138BA00F4B44B /* DeletionTests.swift */; }; 03B7769E20CAED1C00F4B44B /* TBDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0366AE521DF0FEC400F4B44B /* TBDTests.swift */; }; - 03B776A020CAED1C00F4B44B /* DeterminedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0370F2D520C8E25400F4B44B /* DeterminedTests.swift */; }; 03B776A120CAED1C00F4B44B /* DispatchUtilitiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E8795A1F6C98ED00F4B44B /* DispatchUtilitiesTests.swift */; }; 03B776A220CAED1C00F4B44B /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0366AE541DF0FEC400F4B44B /* TestError.swift */; }; 03B776A320CAED1C00F4B44B /* nzRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0374DFB01DFF5BD800F4B44B /* nzRandom.swift */; }; @@ -175,7 +173,6 @@ 0366AE531DF0FEC400F4B44B /* TBDTimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TBDTimingTests.swift; path = DeferredTests/TBDTimingTests.swift; sourceTree = ""; }; 0366AE541DF0FEC400F4B44B /* TestError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestError.swift; path = DeferredTests/TestError.swift; sourceTree = ""; }; 0366AE551DF0FEC400F4B44B /* URLSessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLSessionTests.swift; path = DeferredTests/URLSessionTests.swift; sourceTree = ""; }; - 0370F2D520C8E25400F4B44B /* DeterminedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeterminedTests.swift; path = deferredTests/DeterminedTests.swift; sourceTree = ""; }; 0372B2DC1F8DBB2900F4B44B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 0374DFB01DFF5BD800F4B44B /* nzRandom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = nzRandom.swift; path = DeferredTests/nzRandom.swift; sourceTree = ""; }; 038147591E4138BA00F4B44B /* DeletionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeletionTests.swift; path = deferredTests/DeletionTests.swift; sourceTree = ""; }; @@ -298,7 +295,6 @@ 0366AE521DF0FEC400F4B44B /* TBDTests.swift */, 0366AE531DF0FEC400F4B44B /* TBDTimingTests.swift */, 0366AE551DF0FEC400F4B44B /* URLSessionTests.swift */, - 0370F2D520C8E25400F4B44B /* DeterminedTests.swift */, 03E8795A1F6C98ED00F4B44B /* DispatchUtilitiesTests.swift */, 0366AE541DF0FEC400F4B44B /* TestError.swift */, 0374DFB01DFF5BD800F4B44B /* nzRandom.swift */, @@ -646,7 +642,6 @@ files = ( 0366AE5E1DF0FEC400F4B44B /* TestError.swift in Sources */, 03AA21741E3FF9D600F4B44B /* DeferredCombinationTests.swift in Sources */, - 0370F2D620C8E25400F4B44B /* DeterminedTests.swift in Sources */, 0366AE5C1DF0FEC400F4B44B /* TBDTimingTests.swift in Sources */, 039E18411DFF621300F4B44B /* nzRandom.swift in Sources */, 03E8795B1F6C98ED00F4B44B /* DispatchUtilitiesTests.swift in Sources */, @@ -677,7 +672,6 @@ 03B776A220CAED1C00F4B44B /* TestError.swift in Sources */, 03B7769C20CAED1C00F4B44B /* DeferredCombinationTests.swift in Sources */, 03B776A120CAED1C00F4B44B /* DispatchUtilitiesTests.swift in Sources */, - 03B776A020CAED1C00F4B44B /* DeterminedTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From a76f83b33368f0908ad91a12a5cd52a4e879181f Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 17:15:34 -0600 Subject: [PATCH 05/14] add Outcome as a submodule --- .gitmodules | 3 +++ Package.swift | 2 +- Xcode/dependencies/Outcome | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 Xcode/dependencies/Outcome diff --git a/.gitmodules b/.gitmodules index 651b467..9733255 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "Xcode/dependencies/Atomics"] path = Xcode/dependencies/Atomics url = https://github.com/glessard/swift-atomics.git +[submodule "Xcode/dependencies/Outcome"] + path = Xcode/dependencies/Outcome + url = https://github.com/glessard/outcome.git diff --git a/Package.swift b/Package.swift index 1317e02..69e5a4c 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/glessard/swift-atomics.git", from: "4.1.0"), - .package(path: "../outcome") + .package(url: "https://github.com/glessard/outcome.git", from: "4.1.0") ], targets: [ .target(name: "deferred", dependencies: ["CAtomics", "Outcome"]), diff --git a/Xcode/dependencies/Outcome b/Xcode/dependencies/Outcome new file mode 160000 index 0000000..9942353 --- /dev/null +++ b/Xcode/dependencies/Outcome @@ -0,0 +1 @@ +Subproject commit 99423534248156491d7c664aef20460528eb1acc From 6f129fec1b62d7d22fd18255ba873be9995b623c Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 17:22:13 -0600 Subject: [PATCH 06/14] link Outcome framework from git submodule --- Xcode/deferred.xcodeproj/project.pbxproj | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Xcode/deferred.xcodeproj/project.pbxproj b/Xcode/deferred.xcodeproj/project.pbxproj index bedf251..5c9b067 100644 --- a/Xcode/deferred.xcodeproj/project.pbxproj +++ b/Xcode/deferred.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ 03110CEE20D2069F00F4B44B /* deferred.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 03B4E06D1C14003B00F4B44B /* deferred.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 032F0AA91E4454D600F4B44B /* DeletionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038147591E4138BA00F4B44B /* DeletionTests.swift */; }; 033928191F8C099700F4B44B /* deferred-first.swift in Sources */ = {isa = PBXBuildFile; fileRef = 033928181F8C099700F4B44B /* deferred-first.swift */; }; + 0358CBDD2153109600F4B44B /* Outcome.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0358CBDA2153107C00F4B44B /* Outcome.framework */; }; + 0358CBDE2153109F00F4B44B /* Outcome.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0358CBDA2153107C00F4B44B /* Outcome.framework */; }; + 0358CBDF2153109F00F4B44B /* Outcome.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0358CBDA2153107C00F4B44B /* Outcome.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 035D0C151D84A8DB00F4B44B /* deferred-combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D0C0D1D84A8DB00F4B44B /* deferred-combine.swift */; }; 035D0C171D84A8DB00F4B44B /* deferred-error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D0C0E1D84A8DB00F4B44B /* deferred-error.swift */; }; 035D0C191D84A8DB00F4B44B /* deferred-extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035D0C0F1D84A8DB00F4B44B /* deferred-extras.swift */; }; @@ -70,6 +73,27 @@ remoteGlobalIDString = 03B4E06C1C14003B00F4B44B; remoteInfo = deferred; }; + 0358CBD92153107C00F4B44B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0358CBD42153107C00F4B44B /* Outcome.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 0312008C2152C95100F4B44B; + remoteInfo = Outcome; + }; + 0358CBDB2153107C00F4B44B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0358CBD42153107C00F4B44B /* Outcome.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 031200952152C95100F4B44B; + remoteInfo = OutcomeTests; + }; + 0358CBE02153109F00F4B44B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0358CBD42153107C00F4B44B /* Outcome.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 0312008B2152C95100F4B44B; + remoteInfo = Outcome; + }; 038CB64720DC636D00F4B44B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 030A0C7820CB48DF00F4B44B /* Atomics.xcodeproj */; @@ -151,6 +175,7 @@ files = ( 038CB65A20DC63A500F4B44B /* CAtomics.framework in Embed Frameworks */, 03110CEE20D2069F00F4B44B /* deferred.framework in Embed Frameworks */, + 0358CBDF2153109F00F4B44B /* Outcome.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -161,6 +186,7 @@ 030A0C7820CB48DF00F4B44B /* Atomics.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Atomics.xcodeproj; path = Atomics/Xcode/Atomics.xcodeproj; sourceTree = ""; }; 033928181F8C099700F4B44B /* deferred-first.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "deferred-first.swift"; path = "deferred/deferred-first.swift"; sourceTree = ""; }; 034857621C11BD5E00F4B44B /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; + 0358CBD42153107C00F4B44B /* Outcome.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Outcome.xcodeproj; path = Outcome/Xcode/Outcome.xcodeproj; sourceTree = ""; }; 035D0C0D1D84A8DB00F4B44B /* deferred-combine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "deferred-combine.swift"; path = "deferred/deferred-combine.swift"; sourceTree = ""; }; 035D0C0E1D84A8DB00F4B44B /* deferred-error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "deferred-error.swift"; path = "deferred/deferred-error.swift"; sourceTree = ""; }; 035D0C0F1D84A8DB00F4B44B /* deferred-extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "deferred-extras.swift"; path = "deferred/deferred-extras.swift"; sourceTree = ""; }; @@ -205,6 +231,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0358CBDD2153109600F4B44B /* Outcome.framework in Frameworks */, 038CB65620DC637600F4B44B /* CAtomics.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -223,6 +250,7 @@ files = ( 038CB65920DC639400F4B44B /* CAtomics.framework in Frameworks */, 03B776A420CAED2F00F4B44B /* deferred.framework in Frameworks */, + 0358CBDE2153109F00F4B44B /* Outcome.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -241,6 +269,7 @@ 030A0C3220CB48CA00F4B44B /* dependencies */ = { isa = PBXGroup; children = ( + 0358CBD42153107C00F4B44B /* Outcome.xcodeproj */, 030A0C7820CB48DF00F4B44B /* Atomics.xcodeproj */, ); path = dependencies; @@ -266,6 +295,15 @@ name = Frameworks; sourceTree = ""; }; + 0358CBD52153107C00F4B44B /* Products */ = { + isa = PBXGroup; + children = ( + 0358CBDA2153107C00F4B44B /* Outcome.framework */, + 0358CBDC2153107C00F4B44B /* OutcomeTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 03850ABD1C76AB2200F4B44B /* Source */ = { isa = PBXGroup; children = ( @@ -436,6 +474,7 @@ dependencies = ( 038CB65820DC638E00F4B44B /* PBXTargetDependency */, 03B776A720CAED2F00F4B44B /* PBXTargetDependency */, + 0358CBE12153109F00F4B44B /* PBXTargetDependency */, ); name = deferredTest; productName = deferredTest; @@ -511,6 +550,10 @@ ProductGroup = 030A0C7920CB48DF00F4B44B /* Products */; ProjectRef = 030A0C7820CB48DF00F4B44B /* Atomics.xcodeproj */; }, + { + ProductGroup = 0358CBD52153107C00F4B44B /* Products */; + ProjectRef = 0358CBD42153107C00F4B44B /* Outcome.xcodeproj */; + }, ); projectRoot = ""; targets = ( @@ -537,6 +580,20 @@ remoteRef = 030A0C8120CB48DF00F4B44B /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 0358CBDA2153107C00F4B44B /* Outcome.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Outcome.framework; + remoteRef = 0358CBD92153107C00F4B44B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 0358CBDC2153107C00F4B44B /* OutcomeTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = OutcomeTests.xctest; + remoteRef = 0358CBDB2153107C00F4B44B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 038CB64F20DC636D00F4B44B /* CAtomics.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -683,6 +740,11 @@ target = 03B4E06C1C14003B00F4B44B /* deferred */; targetProxy = 03110CEA20D2065900F4B44B /* PBXContainerItemProxy */; }; + 0358CBE12153109F00F4B44B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Outcome; + targetProxy = 0358CBE02153109F00F4B44B /* PBXContainerItemProxy */; + }; 038CB64820DC636D00F4B44B /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = CAtomics; From e6cd2a2acf97206c099e499587bb25f65e952066 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 17:49:43 -0600 Subject: [PATCH 07/14] change a few variable names, for internal consistency --- Source/deferred/deferred.swift | 14 +++++++------- Tests/deferredTests/DeferredTests.swift | 8 ++++---- Tests/deferredTests/TBDTests.swift | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Source/deferred/deferred.swift b/Source/deferred/deferred.swift index caad6d5..1aa4301 100644 --- a/Source/deferred/deferred.swift +++ b/Source/deferred/deferred.swift @@ -503,11 +503,11 @@ class Flatten: Deferred init(queue: DispatchQueue? = nil, source: Deferred>) { - if let determined = source.peek() + if let outcome = source.peek() { let mine = queue ?? source.queue do { - let deferred = try determined.get() + let deferred = try outcome.get() if let result = deferred.peek() { super.init(queue: mine, result: result) @@ -526,9 +526,9 @@ class Flatten: Deferred super.init(queue: queue, source: source) source.enqueue(queue: queue) { - [weak self] determined in + [weak self] outcome in do { - let deferred = try determined.get() + let deferred = try outcome.get() if let result = deferred.peek() { self?.determine(result) @@ -598,11 +598,11 @@ class Recover: Deferred super.init(queue: queue, source: source) source.enqueue(queue: queue) { - [weak self] determined in + [weak self] outcome in guard let this = self else { return } if this.isDetermined { return } this.beginExecution() - if let error = determined.error + if let error = outcome.error { transform(error).notify(queue: queue) { [weak this] transformed in @@ -611,7 +611,7 @@ class Recover: Deferred } else { - this.determine(determined) + this.determine(outcome) } } } diff --git a/Tests/deferredTests/DeferredTests.swift b/Tests/deferredTests/DeferredTests.swift index 30021ee..b913b4c 100644 --- a/Tests/deferredTests/DeferredTests.swift +++ b/Tests/deferredTests/DeferredTests.swift @@ -278,8 +278,8 @@ class DeferredTests: XCTestCase return 42 } d3.notify { - determined in - guard let e = determined.error, + outcome in + guard let e = outcome.error, let deferredErr = e as? DeferredError, case .canceled = deferredErr else @@ -544,8 +544,8 @@ class DeferredTests: XCTestCase var v1 = 0 var v2 = 0 result.notify { - determined in - XCTAssert(determined.value == (Double(v1*v2))) + outcome in + XCTAssert(outcome.value == (Double(v1*v2))) expect.fulfill() } diff --git a/Tests/deferredTests/TBDTests.swift b/Tests/deferredTests/TBDTests.swift index 3a3af11..35df289 100644 --- a/Tests/deferredTests/TBDTests.swift +++ b/Tests/deferredTests/TBDTests.swift @@ -120,8 +120,8 @@ class TBDTests: XCTestCase let e3 = expectation(description: "TBD never determined") let d3 = TBD() d3.notify { - determined in - XCTAssert(determined.error as? DeferredError == DeferredError.canceled("")) + outcome in + XCTAssert(outcome.error as? DeferredError == DeferredError.canceled("")) } DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + 0.2) { // This will trigger the `XCWaitCompletionHandler` in the `waitForExpectationsWithTimeout` call below. From 8a4b8a35108cd52c3c92a3eacca263a8b9a1bf77 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 19 Sep 2018 22:38:53 -0600 Subject: [PATCH 08/14] introduce deprecation/renamed notice --- Source/deferred/determined.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/deferred/determined.swift b/Source/deferred/determined.swift index 08043cd..b05a088 100644 --- a/Source/deferred/determined.swift +++ b/Source/deferred/determined.swift @@ -7,4 +7,5 @@ import Outcome +@available(*, deprecated, renamed: "Outcome") public typealias Determined = Outcome From e52fae11ba0c316894ef0881fe59657b521b5c6d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 20 Sep 2018 07:02:52 -0600 Subject: [PATCH 09/14] project tweak --- Package.swift | 2 +- Xcode/dependencies/Outcome | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 69e5a4c..987158b 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/glessard/swift-atomics.git", from: "4.1.0"), - .package(url: "https://github.com/glessard/outcome.git", from: "4.1.0") + .package(url: "https://github.com/glessard/outcome.git", from: "4.1.5") ], targets: [ .target(name: "deferred", dependencies: ["CAtomics", "Outcome"]), diff --git a/Xcode/dependencies/Outcome b/Xcode/dependencies/Outcome index 9942353..4a435ba 160000 --- a/Xcode/dependencies/Outcome +++ b/Xcode/dependencies/Outcome @@ -1 +1 @@ -Subproject commit 99423534248156491d7c664aef20460528eb1acc +Subproject commit 4a435ba0f030e58a3c505a14275a1f4b0521f222 From 8c61963c4bc58b7b591cf00125286295ba9efb6a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Sep 2018 13:14:52 -0600 Subject: [PATCH 10/14] update more variables names, local parameter labels, and doc-comments --- Source/deferred/deferred.swift | 92 +++++++++++++++++----------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/Source/deferred/deferred.swift b/Source/deferred/deferred.swift index 1aa4301..f62340e 100644 --- a/Source/deferred/deferred.swift +++ b/Source/deferred/deferred.swift @@ -35,20 +35,20 @@ extension UnsafeMutableRawPointer /// /// A `Deferred` that becomes determined, will henceforth always be determined: it can no longer mutate. /// -/// The `get()` function will return the computation result (or throw an `Error`), +/// The `get()` function will return the value of the computation's result (or throw an `Error`), /// blocking until it becomes available. If the result is ready when `get()` is called, /// it will return immediately. The properties `value` and `error` are convenient non-throwing wrappers /// for the `get()` method -- although they do block. /// -/// Closures supplied to the `enqueuing` function will be called after the `Deferred` has become determined. -/// The functions `map`, `flatMap`, `notify` and others are wrappers that add functionality to the `enqueuing` function. +/// Closures supplied to the `enqueue` function will be called after the `Deferred` has become determined. +/// The functions `map`, `flatMap`, `notify` and others are wrappers that add functionality to the `enqueue` function. open class Deferred { let queue: DispatchQueue private var source: AnyObject? - private var determination: Outcome? + private var determined: Outcome? private var waiters = AtomicMutableRawPointer() private var stateid = AtomicInt32() @@ -66,7 +66,7 @@ open class Deferred { self.queue = queue ?? source.queue self.source = source - determination = nil + determined = nil waiters.initialize(nil) stateid.initialize(beginExecution && (source.stateid.load(.relaxed) != 0) ? 1:0) } @@ -75,7 +75,7 @@ open class Deferred { self.queue = queue source = nil - determination = nil + determined = nil waiters.initialize(nil) stateid.initialize(0) } @@ -89,7 +89,7 @@ open class Deferred { self.queue = queue source = nil - determination = result + determined = result waiters.initialize(.determined) stateid.initialize(2) } @@ -103,7 +103,7 @@ open class Deferred { self.queue = queue source = nil - determination = nil + determined = nil waiters.initialize(nil) stateid.initialize(1) @@ -188,16 +188,16 @@ open class Deferred } while !stateid.loadCAS(¤t, 1, .weak, .relaxed, .relaxed) } - /// Set the `Result` of this `Deferred` and dispatch all notifications for execution. + /// Set the `Outcome` of this `Deferred` and dispatch all notifications for execution. /// Note that a `Deferred` can only be determined once. /// On subsequent calls, `determine` will fail and return `false`. /// This operation is lock-free and thread-safe. /// - /// - parameter result: the intended `Result` to determine this `Deferred` + /// - parameter outcome: the intended `Outcome` to determine this `Deferred` /// - returns: whether the call succesfully changed the state of this `Deferred`. @discardableResult - fileprivate func determine(_ result: Outcome) -> Bool + fileprivate func determine(_ outcome: Outcome) -> Bool { var current = stateid.load(.relaxed) repeat { // keep trying if another thread hasn't succeeded yet @@ -207,20 +207,20 @@ open class Deferred } } while !stateid.loadCAS(¤t, 2, .weak, .relaxed, .relaxed) - determination = result + determined = outcome source = nil let waitQueue = waiters.swap(.determined, .acqrel)?.assumingMemoryBound(to: Waiter.self) // precondition(waitQueue != .determined) - notifyWaiters(queue, waitQueue, result) + notifyWaiters(queue, waitQueue, outcome) // precondition(waiters.load() == .determined, "waiters.pointer has incorrect value \(String(describing: waiters.load()))") - // The result is now available for the world + // The outcome has been determined return true } - /// Set the `Result` of this `Deferred` and dispatch all notifications for execution. + /// Set the `Outcome` of this `Deferred` and dispatch all notifications for execution. /// Note that a `Deferred` can only be determined once. /// On subsequent calls, `determine` will fail and return `false`. /// This operation is lock-free and thread-safe. @@ -271,9 +271,9 @@ open class Deferred /// - parameter queue: the `DispatchQueue` on which to dispatch this notification when ready; defaults to `self`'s queue. /// - parameter boostQoS: whether `enqueue` should attempt to boost the QoS if `queue.qos` is higher than `self.qos`; defaults to `true` /// - parameter task: a closure to be executed after `self` becomes determined. - /// - parameter result: the determined result of `self` + /// - parameter outcome: the determined `Outcome` of `self` - open func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (_ result: Outcome) -> Void) + open func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (_ outcome: Outcome) -> Void) { var waitQueue = waiters.load(.acquire) if waitQueue != .determined @@ -305,7 +305,7 @@ open class Deferred // this Deferred is determined let q = queue ?? self.queue - q.async(execute: { [value = determination!] in task(value) }) + q.async(execute: { [outcome = determined!] in task(outcome) }) } /// Enqueue a notification to be performed asynchronously after this `Deferred` becomes determined. @@ -313,11 +313,11 @@ open class Deferred /// /// - parameter queue: the `DispatchQueue` on which to dispatch this notification when ready; defaults to `self`'s queue. /// - parameter task: a closure to be executed as a notification - /// - parameter result: the determined result of `self` + /// - parameter outcome: the determined `Outcome` of `self` - public func notify(queue: DispatchQueue? = nil, task: @escaping (_ result: Outcome) -> Void) + public func notify(queue: DispatchQueue? = nil, task: @escaping (_ outcome: Outcome) -> Void) { - enqueue(queue: queue, task: { value in withExtendedLifetime(self) { task(value) } }) + enqueue(queue: queue, task: { outcome in withExtendedLifetime(self) { task(outcome) } }) } /// Query the current state of this `Deferred` @@ -326,7 +326,7 @@ open class Deferred public var state: DeferredState { return (waiters.load(.acquire) != .determined) ? (stateid.load(.relaxed) == 0 ? .waiting : .executing ) : - (determination!.isValue ? .succeeded : .errored) + (determined!.isValue ? .succeeded : .errored) } /// Query whether this `Deferred` has become determined. @@ -361,7 +361,7 @@ open class Deferred /// When called on a `Deferred` that is already determined, this call is non-blocking. /// When called on a `Deferred` that is not determined, this call blocks the executing thread. /// - /// - returns: this `Deferred`'s determined result + /// - returns: this `Deferred`'s determined `Outcome` public var result: Outcome { if waiters.load(.acquire) != .determined @@ -378,7 +378,7 @@ open class Deferred } // this Deferred is determined - return determination! + return determined! } /// Get this `Deferred`'s value, blocking if necessary until it becomes determined. @@ -402,7 +402,7 @@ open class Deferred { if waiters.load(.acquire) == .determined { - return determination + return determined } return nil } @@ -453,12 +453,12 @@ class Map: Deferred super.init(queue: queue, source: source) source.enqueue(queue: queue) { - [weak self] value in + [weak self] outcome in guard let this = self else { return } if this.isDetermined { return } this.beginExecution() do { - let value = try value.get() + let value = try outcome.get() let transformed = try transform(value) this.determine(transformed) } @@ -480,9 +480,9 @@ class Transfer: Deferred init(queue: DispatchQueue, source: Deferred) { - if let result = source.peek() + if let outcome = source.peek() { - super.init(queue: queue, result: result) + super.init(queue: queue, result: outcome) } else { @@ -508,9 +508,9 @@ class Flatten: Deferred let mine = queue ?? source.queue do { let deferred = try outcome.get() - if let result = deferred.peek() + if let outcome = deferred.peek() { - super.init(queue: mine, result: result) + super.init(queue: mine, result: outcome) } else { @@ -529,9 +529,9 @@ class Flatten: Deferred [weak self] outcome in do { let deferred = try outcome.get() - if let result = deferred.peek() + if let outcome = deferred.peek() { - self?.determine(result) + self?.determine(outcome) } else { @@ -565,12 +565,12 @@ class Bind: Deferred super.init(queue: queue, source: source) source.enqueue(queue: queue) { - [weak self] value in + [weak self] outcome in guard let this = self else { return } if this.isDetermined { return } this.beginExecution() do { - let value = try value.get() + let value = try outcome.get() transform(value).notify(queue: queue) { [weak this] transformed in this?.determine(transformed) @@ -634,11 +634,11 @@ class Apply: Deferred super.init(queue: queue, source: source) source.enqueue(queue: queue) { - [weak self] value in + [weak self] outcome in guard let this = self else { return } if this.isDetermined { return } do { - let value = try value.get() + let value = try outcome.get() transform.notify(queue: queue) { [weak this] transform in guard let this = this else { return } @@ -679,13 +679,13 @@ class Delay: Deferred super.init(queue: queue, source: source) source.enqueue(queue: queue, boostQoS: false) { - [weak self] value in + [weak self] outcome in guard let this = self else { return } if this.isDetermined { return } - if value.isError + if outcome.isError { - this.determine(value) + this.determine(outcome) return } @@ -696,12 +696,12 @@ class Delay: Deferred { this.queue.asyncAfter(deadline: time) { [weak this] in - this?.determine(value) + this?.determine(outcome) } } else { - this.determine(value) + this.determine(outcome) } } } @@ -730,18 +730,18 @@ open class TBD: Deferred self.init(queue: queue) } - /// Set the `Result` of this `Deferred` and dispatch all notifications for execution. + /// Set the `Outcome` of this `Deferred` and dispatch all notifications for execution. /// Note that a `Deferred` can only be determined once. /// On subsequent calls, `determine` will fail and return `false`. /// This operation is lock-free and thread-safe. /// - /// - parameter result: the determined value for this `Deferred` + /// - parameter outcome: the intended `Outcome` for this `Deferred` /// - returns: whether the call succesfully changed the state of this `Deferred`. @discardableResult - open override func determine(_ result: Outcome) -> Bool + open override func determine(_ outcome: Outcome) -> Bool { - return super.determine(result) + return super.determine(outcome) } /// Set the value of this `Deferred` and dispatch all notifications for execution. From 3a7a484e680419c4b881e4cc2c711439cfca664b Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Sep 2018 15:23:05 -0600 Subject: [PATCH 11/14] renamed two bits of api (with deprecation notices) --- Source/deferred/deferred.swift | 37 +++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Source/deferred/deferred.swift b/Source/deferred/deferred.swift index f62340e..789c50a 100644 --- a/Source/deferred/deferred.swift +++ b/Source/deferred/deferred.swift @@ -80,20 +80,26 @@ open class Deferred stateid.initialize(0) } - /// Initialize to an already determined state + /// Initialize with a pre-determined `Outcome` /// - /// - parameter queue: the dispatch queue upon which to execute future notifications for this `Deferred` - /// - parameter result: the result of this `Deferred` + /// - parameter queue: the dispatch queue upon which to execute future notifications for this `Deferred` + /// - parameter outcome: the `Outcome` of this `Deferred` - public init(queue: DispatchQueue, result: Outcome) + public init(queue: DispatchQueue, outcome: Outcome) { self.queue = queue source = nil - determined = result + determined = outcome waiters.initialize(.determined) stateid.initialize(2) } + @available(*, deprecated, renamed: "init(queue:outcome:)") + public convenience init(queue: DispatchQueue, result: Outcome) + { + self.init(queue: queue, outcome: result) + } + /// Initialize with a computation task to be performed on the specified queue /// /// - parameter queue: the `DispatchQueue` on which the computation (and notifications) will be executed @@ -149,7 +155,7 @@ open class Deferred public convenience init(queue: DispatchQueue, value: Value) { - self.init(queue: queue, result: Outcome(value: value)) + self.init(queue: queue, outcome: Outcome(value: value)) } /// Initialize with an Error @@ -170,7 +176,7 @@ open class Deferred public convenience init(queue: DispatchQueue, error: Error) { - self.init(queue: queue, result: Outcome(error: error)) + self.init(queue: queue, outcome: Outcome(error: error)) } // MARK: state changes / determine @@ -363,7 +369,7 @@ open class Deferred /// /// - returns: this `Deferred`'s determined `Outcome` - public var result: Outcome { + public var outcome: Outcome { if waiters.load(.acquire) != .determined { if let current = DispatchQoS.QoSClass.current, current > queue.qos.qosClass @@ -381,6 +387,9 @@ open class Deferred return determined! } + @available(*, deprecated, renamed: "outcome") + public var result: Outcome { return self.outcome } + /// Get this `Deferred`'s value, blocking if necessary until it becomes determined. /// If the `Deferred` is determined with an `Error`, throw it. /// When called on a `Deferred` that is already determined, this call is non-blocking. @@ -390,7 +399,7 @@ open class Deferred public func get() throws -> Value { - return try result.get() + return try outcome.get() } /// Get this `Deferred`'s `Outcome` result if exists, `nil` otherwise. @@ -415,7 +424,7 @@ open class Deferred /// - returns: this `Deferred`'s determined value, or `nil` public var value: Value? { - return result.value + return outcome.value } /// Get this `Deferred`'s error state, blocking if necessary until it becomes determined. @@ -426,7 +435,7 @@ open class Deferred /// - returns: this `Deferred`'s determined error state, or `nil` public var error: Error? { - return result.error + return outcome.error } /// Get the QoS of this `Deferred`'s queue @@ -482,7 +491,7 @@ class Transfer: Deferred { if let outcome = source.peek() { - super.init(queue: queue, result: outcome) + super.init(queue: queue, outcome: outcome) } else { @@ -510,7 +519,7 @@ class Flatten: Deferred let deferred = try outcome.get() if let outcome = deferred.peek() { - super.init(queue: mine, result: outcome) + super.init(queue: mine, outcome: outcome) } else { @@ -519,7 +528,7 @@ class Flatten: Deferred } } catch { - super.init(queue: mine, result: Outcome(error: error)) + super.init(queue: mine, outcome: Outcome(error: error)) } return } From 8ba497cb3d5e2b93bb9f52975cd25b4ae44b3fb0 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Sep 2018 17:15:39 -0600 Subject: [PATCH 12/14] make the Transferred subclass public - it's useful to make a Deferred subclass with extra properties, without having to expose TBD's `determine` methods. --- Source/deferred/deferred-extras.swift | 2 +- Source/deferred/deferred.swift | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/deferred/deferred-extras.swift b/Source/deferred/deferred-extras.swift index 46f78f6..a2ab13e 100644 --- a/Source/deferred/deferred-extras.swift +++ b/Source/deferred/deferred-extras.swift @@ -51,7 +51,7 @@ extension Deferred public func enqueuing(on queue: DispatchQueue) -> Deferred { - return Transfer(queue: queue, source: self) + return Transferred(from: self, on: queue) } /// Get a `Deferred` that will have the same `result` as `self` once determined, diff --git a/Source/deferred/deferred.swift b/Source/deferred/deferred.swift index 789c50a..f56782b 100644 --- a/Source/deferred/deferred.swift +++ b/Source/deferred/deferred.swift @@ -478,7 +478,7 @@ class Map: Deferred } } -class Transfer: Deferred +open class Transferred: Deferred { /// Transfer a `Deferred` result to a new `Deferred` that notifies on a new queue. /// (Acts like a fast path for a Map with no transform.) @@ -487,7 +487,7 @@ class Transfer: Deferred /// - parameter queue: the `DispatchQueue` onto which the new `Deferred` should dispatch notifications; use `source.queue` if `nil` /// - parameter source: the `Deferred` whose value will be transferred into a new instance. - init(queue: DispatchQueue, source: Deferred) + init(from source: Deferred, on queue: DispatchQueue) { if let outcome = source.peek() { @@ -496,14 +496,15 @@ class Transfer: Deferred else { super.init(queue: queue, source: source, beginExecution: true) - source.enqueue(queue: queue, boostQoS: false, task: { [weak self] in self?.determine($0) }) + source.enqueue(queue: queue, boostQoS: false, + task: { [weak self] outcome in self?.determine(outcome) }) } } } class Flatten: Deferred { - /// Flatten a Deferred-of-a-Deferred to a Deferred. + /// Flatten a Deferred> to a Deferred. /// (In the right conditions, acts like a fast path for a flatMap with no transform.) /// This constructor is used by `flatten()` /// From 35fa53829791b720ab7e370afd3acd81c4b931c4 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 21 Sep 2018 17:18:05 -0600 Subject: [PATCH 13/14] update DeferredURLSessionTask and tests --- Source/deferred/nsurlsession.swift | 81 ++++++++++---------- Tests/deferredTests/URLSessionTests.swift | 92 +++++++++++------------ 2 files changed, 89 insertions(+), 84 deletions(-) diff --git a/Source/deferred/nsurlsession.swift b/Source/deferred/nsurlsession.swift index 9d07d5f..6682fda 100644 --- a/Source/deferred/nsurlsession.swift +++ b/Source/deferred/nsurlsession.swift @@ -30,14 +30,18 @@ public enum URLSessionError: Error case InvalidState } -class DeferredURLSessionTask: Transfer +public class DeferredURLSessionTask: Transferred { public let urlSessionTask: URLSessionTask init(source: TBD, task: URLSessionTask) { urlSessionTask = task - super.init(queue: source.queue, source: source) + super.init(from: source, on: source.queue) + } + + deinit { + urlSessionTask.cancel() } @discardableResult @@ -50,7 +54,8 @@ class DeferredURLSessionTask: Transfer return true } - public override func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, task: @escaping (Outcome) -> Void) + public override func enqueue(queue: DispatchQueue? = nil, boostQoS: Bool = true, + task: @escaping (Outcome) -> Void) { if state == .waiting { @@ -91,55 +96,55 @@ public extension URLSession } public func deferredDataTask(qos: DispatchQoS = .current, - with request: URLRequest) -> Deferred<(Data, HTTPURLResponse)> + with request: URLRequest) -> DeferredURLSessionTask<(Data, HTTPURLResponse)> { - guard let scheme = request.url?.scheme, scheme == "http" || scheme == "https" - else { - let e = DeferredError.invalid( - "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" - ) - return Deferred(error: e) + let tbd = TBD<(Data, HTTPURLResponse)>(qos: qos) + + if let scheme = request.url?.scheme, + scheme != "http" && scheme != "https" + { + let message = "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" + tbd.determine(DeferredError.invalid(message)) } - let tbd = TBD<(Data, HTTPURLResponse)>(qos: qos) let task = dataTask(with: request, completionHandler: dataCompletion(tbd)) return DeferredURLSessionTask(source: tbd, task: task) } public func deferredDataTask(qos: DispatchQoS = .current, - with url: URL) -> Deferred<(Data, HTTPURLResponse)> + with url: URL) -> DeferredURLSessionTask<(Data, HTTPURLResponse)> { return deferredDataTask(qos: qos, with: URLRequest(url: url)) } public func deferredUploadTask(qos: DispatchQoS = .current, - with request: URLRequest, fromData bodyData: Data) -> Deferred<(Data, HTTPURLResponse)> + with request: URLRequest, fromData bodyData: Data) -> DeferredURLSessionTask<(Data, HTTPURLResponse)> { - guard let scheme = request.url?.scheme, scheme == "http" || scheme == "https" - else { - let e = DeferredError.invalid( - "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" - ) - return Deferred(error: e) + let tbd = TBD<(Data, HTTPURLResponse)>(qos: qos) + + if let scheme = request.url?.scheme, + scheme != "http" && scheme != "https" + { + let message = "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" + tbd.determine(DeferredError.invalid(message)) } - let tbd = TBD<(Data, HTTPURLResponse)>(qos: qos) let task = uploadTask(with: request, from: bodyData, completionHandler: dataCompletion(tbd)) return DeferredURLSessionTask(source: tbd, task: task) } public func deferredUploadTask(qos: DispatchQoS = .current, - with request: URLRequest, fromFile fileURL: URL) -> Deferred<(Data, HTTPURLResponse)> + with request: URLRequest, fromFile fileURL: URL) -> DeferredURLSessionTask<(Data, HTTPURLResponse)> { - guard let scheme = request.url?.scheme, scheme == "http" || scheme == "https" - else { - let e = DeferredError.invalid( - "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" - ) - return Deferred(error: e) + let tbd = TBD<(Data, HTTPURLResponse)>(qos: qos) + + if let scheme = request.url?.scheme, + scheme != "http" && scheme != "https" + { + let message = "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" + tbd.determine(DeferredError.invalid(message)) } - let tbd = TBD<(Data, HTTPURLResponse)>(qos: qos) let task = uploadTask(with: request, fromFile: fileURL, completionHandler: dataCompletion(tbd)) return DeferredURLSessionTask(source: tbd, task: task) } @@ -210,29 +215,29 @@ extension URLSession } public func deferredDownloadTask(qos: DispatchQoS = .current, - with request: URLRequest) -> Deferred<(URL, FileHandle, HTTPURLResponse)> + with request: URLRequest) -> DeferredURLSessionTask<(URL, FileHandle, HTTPURLResponse)> { - guard let scheme = request.url?.scheme, scheme == "http" || scheme == "https" - else { - let e = DeferredError.invalid( - "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" - ) - return Deferred(error: e) + let tbd = TBD<(URL, FileHandle, HTTPURLResponse)>(qos: qos) + + if let scheme = request.url?.scheme, + scheme != "http" && scheme != "https" + { + let message = "deferred does not support url scheme \"\(request.url?.scheme! ?? "unknown")\"" + tbd.determine(DeferredError.invalid(message)) } - let tbd = TBD<(URL, FileHandle, HTTPURLResponse)>(qos: qos) let task = downloadTask(with: request, completionHandler: downloadCompletion(tbd)) return DeferredDownloadTask(source: tbd, task: task) } public func deferredDownloadTask(qos: DispatchQoS = .current, - with url: URL) -> Deferred<(URL, FileHandle, HTTPURLResponse)> + with url: URL) -> DeferredURLSessionTask<(URL, FileHandle, HTTPURLResponse)> { return deferredDownloadTask(qos: qos, with: URLRequest(url: url)) } public func deferredDownloadTask(qos: DispatchQoS = .current, - withResumeData data: Data) -> Deferred<(URL, FileHandle, HTTPURLResponse)> + withResumeData data: Data) -> DeferredURLSessionTask<(URL, FileHandle, HTTPURLResponse)> { let tbd = TBD<(URL, FileHandle, HTTPURLResponse)>(qos: qos) diff --git a/Tests/deferredTests/URLSessionTests.swift b/Tests/deferredTests/URLSessionTests.swift index 62f96c7..7d59a55 100644 --- a/Tests/deferredTests/URLSessionTests.swift +++ b/Tests/deferredTests/URLSessionTests.swift @@ -51,11 +51,12 @@ class URLSessionTests: XCTestCase return false } - switch success.result - { - case .value(let success) where success == true: break // savor success - case .value: XCTFail("Failed without error") - case .error(let error): XCTFail(String(describing: error)) + do { + let success = try success.get() + XCTAssert(success, "Failed with error") + } + catch { + XCTFail(String(describing: error)) } session.invalidateAndCancel() @@ -66,23 +67,22 @@ class URLSessionTests: XCTestCase let url = URL(string: basePath)! var request = URLRequest(url: url) request.httpMethod = "POST" - request.httpBody = Data(fromString: "name=John Tester&age=97&data=****") + request.httpBody = String("name=John Tester&age=97&data=****").data(using: .utf8) let session = URLSession(configuration: URLSessionConfiguration.ephemeral) let dataTask = session.deferredDataTask(with: request) - switch dataTask.result - { - case .value(let data, let response): + do { + let (data, response) = try dataTask.get() XCTAssert(response.statusCode == 200) XCTAssert(data.count > 0) - if let i = String(fromData: data).components(separatedBy: " ").last + if let i = String(data: data, encoding: .utf8)?.components(separatedBy: " ").last { XCTAssert(Int(i) == 4) } else { XCTFail("unexpected data in response") } - - case .error(let error): + } + catch { XCTFail(String(describing: error)) } @@ -112,7 +112,7 @@ class URLSessionTests: XCTestCase func testData_DoubleCancellation() { - let deferred: DeferredURLSessionTask<(Data, HTTPURLResponse)> = { + let deferred: Deferred<(Data, HTTPURLResponse)> = { let url = URL(string: imagePath)! let session = URLSession(configuration: URLSessionConfiguration.ephemeral) defer { session.invalidateAndCancel() } @@ -142,7 +142,7 @@ class URLSessionTests: XCTestCase let session = URLSession(configuration: URLSessionConfiguration.ephemeral) let deferred = session.deferredDataTask(with: url) - deferred.task?.suspend() + deferred.urlSessionTask.suspend() let canceled = deferred.cancel() XCTAssert(canceled) @@ -166,12 +166,12 @@ class URLSessionTests: XCTestCase let deferred = session.deferredDataTask(with: request) - switch deferred.result - { - case .value(let data, let response): + do { + let (data, response) = try deferred.get() XCTAssert(data.count > 0) XCTAssert(response.statusCode == 404) - case .error(let error): + } + catch { XCTFail(String(describing: error)) } @@ -209,10 +209,12 @@ class URLSessionTests: XCTestCase return false } - switch success.result - { - case .value(let success): XCTAssert(success, "Failed without error") - case .error(let error): XCTFail(String(describing: error)) + do { + let success = try success.get() + XCTAssert(success, "Failed without error") + } + catch { + XCTFail(String(describing: error)) } session.invalidateAndCancel() @@ -271,7 +273,7 @@ class URLSessionTests: XCTestCase let session = URLSession(configuration: URLSessionConfiguration.ephemeral) let deferred = session.deferredDownloadTask(with: url) - deferred.task?.suspend() + deferred.urlSessionTask.suspend() let canceled = deferred.cancel() XCTAssert(canceled) @@ -360,14 +362,14 @@ class URLSessionTests: XCTestCase let deferred = session.deferredDownloadTask(with: request) - switch deferred.result - { - case .value(_, let handle, let response): + do { + let (_, handle, response) = try deferred.get() let data = handle.readDataToEndOfFile() handle.closeFile() XCTAssert(data.count > 0) XCTAssert(response.statusCode == 404) - case .error(let error): + } + catch { XCTFail(String(describing: error)) } @@ -382,7 +384,7 @@ class URLSessionTests: XCTestCase var request = URLRequest(url: url) request.httpMethod = "POST" - let data = Data(fromString: "name=John Tester&age=97") + let data = String("name=John Tester&age=97").data(using: .utf8)! let deferred = session.deferredUploadTask(with: request, fromData: data) let canceled = deferred.cancel() @@ -409,19 +411,18 @@ class URLSessionTests: XCTestCase request.httpMethod = method let payload = "data=" + String(repeatElement("A", count: 1995)) - let length = payload.characters.count - let message = Data(fromString: payload) + let length = payload.count + let message = payload.data(using: .utf8)! XCTAssert(message.count == length) let task = session.deferredUploadTask(with: request, fromData: message) - switch task.result - { - case let .value(data, response): + do { + let (data, response) = try task.get() XCTAssert(response.statusCode == 200) - XCTAssert(task.task?.countOfBytesSent == Int64(length)) + XCTAssert(task.urlSessionTask.countOfBytesSent == Int64(length)) - if case let reply = String(fromData: data), + if let reply = String(data: data, encoding: .utf8), let text = reply.components(separatedBy: " ").last, let tlen = Int(text) { @@ -431,8 +432,8 @@ class URLSessionTests: XCTestCase { XCTFail("Unexpected data in response") } - - case .error(let error): + } + catch { XCTFail(String(describing: error)) } @@ -458,8 +459,8 @@ class URLSessionTests: XCTestCase request.httpMethod = method let payload = "data=" + String(repeatElement("A", count: 1995)) - let length = payload.characters.count - let message = Data(fromString: payload) + let length = payload.count + let message = payload.data(using: .utf8)! XCTAssert(message.count == length) let path = NSTemporaryDirectory() + "temporary.tmp" @@ -481,13 +482,12 @@ class URLSessionTests: XCTestCase let task = session.deferredUploadTask(with: request, fromFile: fileurl) - switch task.result - { - case let .value(data, response): + do { + let (data, response) = try task.get() XCTAssert(response.statusCode == 200) - XCTAssert(task.task?.countOfBytesSent == Int64(length)) + XCTAssert(task.urlSessionTask.countOfBytesSent == Int64(length)) - if case let reply = String(fromData: data), + if let reply = String(data: data, encoding: .utf8), let text = reply.components(separatedBy: " ").last, let tlen = Int(text) { @@ -497,8 +497,8 @@ class URLSessionTests: XCTestCase { XCTFail("Unexpected data in response") } - - case .error(let error): + } + catch { XCTFail(String(describing: error)) } From ecee96d112b73c1e3d65a8616b47f12c159f1093 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Sat, 22 Sep 2018 20:40:34 -0600 Subject: [PATCH 14/14] make Transferred more useful by making its constructor public --- Source/deferred/deferred.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/deferred/deferred.swift b/Source/deferred/deferred.swift index f77f7cd..80f662c 100644 --- a/Source/deferred/deferred.swift +++ b/Source/deferred/deferred.swift @@ -487,15 +487,15 @@ open class Transferred: Deferred /// - parameter queue: the `DispatchQueue` onto which the new `Deferred` should dispatch notifications; use `source.queue` if `nil` /// - parameter source: the `Deferred` whose value will be transferred into a new instance. - init(from source: Deferred, on queue: DispatchQueue) + public init(from source: Deferred, on queue: DispatchQueue? = nil) { if let outcome = source.peek() { - super.init(queue: queue, outcome: outcome) + super.init(queue: queue ?? source.queue, outcome: outcome) } else { - super.init(queue: queue, source: source, beginExecution: true) + super.init(queue: queue ?? source.queue, source: source, beginExecution: true) source.enqueue(queue: queue, boostQoS: false, task: { [weak self] outcome in self?.determine(outcome) }) }