Browse Source

Random stuff from WWDC.

Jon Shier 6 years ago
parent
commit
c864159deb

+ 23 - 27
Alamofire.xcodeproj/xcshareddata/xcschemes/Alamofire macOS.xcscheme

@@ -40,31 +40,10 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "NO"
       enableThreadSanitizer = "YES"
       codeCoverageEnabled = "YES"
-      onlyGenerateCoverageForSpecifiedTargets = "YES"
-      shouldUseLaunchSchemeArgsEnv = "NO">
-      <CodeCoverageTargets>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "4DD67C0A1A5C55C900ED2280"
-            BuildableName = "Alamofire.framework"
-            BlueprintName = "Alamofire macOS"
-            ReferencedContainer = "container:Alamofire.xcodeproj">
-         </BuildableReference>
-      </CodeCoverageTargets>
-      <Testables>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "F829C6B11A7A94F100A2CD59"
-               BuildableName = "Alamofire macOS Tests.xctest"
-               BlueprintName = "Alamofire macOS Tests"
-               ReferencedContainer = "container:Alamofire.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-      </Testables>
+      onlyGenerateCoverageForSpecifiedTargets = "YES">
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -86,8 +65,27 @@
             isEnabled = "YES">
          </EnvironmentVariable>
       </EnvironmentVariables>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <CodeCoverageTargets>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "4DD67C0A1A5C55C900ED2280"
+            BuildableName = "Alamofire.framework"
+            BlueprintName = "Alamofire macOS"
+            ReferencedContainer = "container:Alamofire.xcodeproj">
+         </BuildableReference>
+      </CodeCoverageTargets>
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "F829C6B11A7A94F100A2CD59"
+               BuildableName = "Alamofire macOS Tests.xctest"
+               BlueprintName = "Alamofire macOS Tests"
+               ReferencedContainer = "container:Alamofire.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -108,8 +106,6 @@
             ReferencedContainer = "container:Alamofire.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 2 - 1
Example/Source/MasterViewController.swift

@@ -26,6 +26,7 @@ import Alamofire
 import UIKit
 
 class MasterViewController: UITableViewController {
+    static let session = Session(eventMonitors: [AlamofireSignposts()])
 
     // MARK: - Properties
 
@@ -58,7 +59,7 @@ class MasterViewController: UITableViewController {
                 switch segue.identifier! {
                 case "GET":
                     detailViewController.segueIdentifier = "GET"
-                    return AF.request("https://httpbin.org/get")
+                    return MasterViewController.session.request("https://httpbin.org/get")
                 case "POST":
                     detailViewController.segueIdentifier = "POST"
                     return AF.request("https://httpbin.org/post", method: .post)

+ 2 - 2
Example/iOS Example.xcodeproj/project.pbxproj

@@ -381,7 +381,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MACOSX_DEPLOYMENT_TARGET = 10.12;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
@@ -433,7 +433,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MACOSX_DEPLOYMENT_TARGET = 10.12;
 				SDKROOT = iphoneos;
 				SWIFT_COMPILATION_MODE = wholemodule;

+ 3 - 7
Example/iOS Example.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme

@@ -27,8 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-      </Testables>
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
             ReferencedContainer = "container:iOS Example.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <Testables>
+      </Testables>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -65,11 +63,9 @@
          <EnvironmentVariable
             key = "OS_ACTIVITY_MODE"
             value = "disable"
-            isEnabled = "YES">
+            isEnabled = "NO">
          </EnvironmentVariable>
       </EnvironmentVariables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 24 - 0
Source/EventMonitor.swift

@@ -824,3 +824,27 @@ open class ClosureEventMonitor: EventMonitor {
     }
 
 }
+
+import os.log
+import os.signpost
+
+@available(iOS 12.0, macOS 10.14, *)
+public final class AlamofireSignposts: EventMonitor {
+    let log = OSLog(subsystem: "org.alamofire", category: "Alamofire")
+    
+    public init() { }
+    
+    public func requestDidResume(_ request: Request) {
+        let id = OSSignpostID(log: log, object: request)
+        os_log("Resume", log: log, type: .info)
+        os_log("Other resume.")
+        os_signpost(.begin, log: log, name: "Request", signpostID: id)
+    }
+    
+    public func requestDidFinish(_ request: Request) {
+        let id = OSSignpostID(log: log, object: request)
+        os_log("Finish", log: log, type: .info)
+        os_log("Other finish.")
+        os_signpost(.end, log: log, name: "Request", signpostID: id)
+    }
+}

+ 18 - 4
Source/Protector.swift

@@ -28,14 +28,18 @@ import Foundation
 
 /// An `os_unfair_lock` wrapper.
 final class UnfairLock {
-    private var unfairLock = os_unfair_lock()
+    private let _lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
+    
+    deinit {
+        _lock.deallocate()
+    }
 
     fileprivate func lock() {
-        os_unfair_lock_lock(&unfairLock)
+        os_unfair_lock_lock(_lock)
     }
 
     fileprivate func unlock() {
-        os_unfair_lock_unlock(&unfairLock)
+        os_unfair_lock_unlock(_lock)
     }
 
     /// Executes a closure returning a value while acquiring the lock.
@@ -57,9 +61,10 @@ final class UnfairLock {
 }
 
 /// A thread-safe wrapper around a value.
+@propertyDelegate @dynamicMemberLookup
 final class Protector<T> {
     private let lock = UnfairLock()
-    private var value: T
+    var value: T
 
     init(_ value: T) {
         self.value = value
@@ -87,6 +92,15 @@ final class Protector<T> {
     func write<U>(_ closure: (inout T) -> U) -> U {
         return lock.around { closure(&self.value) }
     }
+    
+    init(initialValue: T) {
+        value = initialValue
+    }
+    
+    subscript<Property>(dynamicMember keyPath: WritableKeyPath<T, Property>) -> Property {
+        get { return lock.around { value[keyPath: keyPath] } }
+        set { lock.around { value[keyPath: keyPath] = newValue } }
+    }
 }
 
 extension Protector where T: RangeReplaceableCollection {

+ 4 - 4
Source/Request.swift

@@ -111,10 +111,10 @@ public class Request {
     }
 
     /// Protected `MutableState` value that provides threadsafe access to state values.
-    fileprivate let protectedMutableState: Protector<MutableState> = Protector(MutableState())
+    @Protector fileprivate var protectedMutableState: MutableState = MutableState()
 
     /// `State` of the `Request`.
-    public var state: State { return protectedMutableState.directValue.state }
+    public var state: State { return protectedMutableState.state }
     /// Returns whether `state` is `.initialized`.
     public var isInitialized: Bool { return state == .initialized }
     /// Returns whether `state is `.resumed`.
@@ -222,8 +222,8 @@ public class Request {
 
     /// `Error` returned from Alamofire internally, from the network request directly, or any validators executed.
     fileprivate(set) public var error: Error? {
-        get { return protectedMutableState.directValue.error }
-        set { protectedMutableState.write { $0.error = newValue } }
+        get { return protectedMutableState.error }
+        set { protectedMutableState.error = newValue }
     }
 
     /// Default initializer for the `Request` superclass.

+ 125 - 0
Source/ResponseSerialization.swift

@@ -671,3 +671,128 @@ extension DataRequest {
                         completionHandler: completionHandler)
     }
 }
+
+#if canImport(Combine)
+
+import Combine
+
+extension DataRequest {
+    @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
+    public func futureDecodable<T: Decodable>(queue: DispatchQueue = .main, decoder: DataDecoder = JSONDecoder()) -> Publishers.Future<T, Error> {
+        return Publishers.Future<T, Error> { (completion) in
+            self.responseDecodable(queue: queue, decoder: decoder) { (response: DataResponse<T>) in
+                completion(response.result)
+            }
+        }
+    }
+}
+
+@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
+public extension Publisher where Output == DataRequest {
+    func response<T: Decodable>(of t: T.Type, queue: DispatchQueue = .main, decoder: DataDecoder = JSONDecoder()) -> AlamoOperator<Self, T> {
+        return AlamoOperator(self, queue: queue, decoder: decoder, of: t)
+    }
+}
+
+@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
+public struct AlamoOperator<Upstream: Publisher, T: Decodable>: Publisher where Upstream.Output == DataRequest {
+    public typealias Output = DataResponse<T>
+    public typealias Failure = Upstream.Failure
+    
+    let upstream: Upstream
+    let queue: DispatchQueue
+    let decoder: DataDecoder
+    
+    init(_ upstream: Upstream, queue: DispatchQueue, decoder: DataDecoder, of: T.Type) {
+        self.upstream = upstream
+        self.queue = queue
+        self.decoder = decoder
+    }
+    
+    public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
+        let inner = Inner(subscriber, queue: queue, decoder: decoder)
+        upstream.subscribe(inner)
+        subscriber.receive(subscription: inner)
+    }
+    
+    final class Inner<Downstream: Subscriber>: Subscriber, Subscription where Downstream.Input == DataResponse<T> {
+        typealias Input = DataRequest
+        typealias Failure = Downstream.Failure
+        
+        var subscription: Subscription?
+        var downstream: Downstream?
+        
+        let queue: DispatchQueue
+        let decoder: DataDecoder
+        let lock = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
+        
+        init(_ downstream: Downstream, queue: DispatchQueue, decoder: DataDecoder) {
+            self.downstream = downstream
+            self.queue = queue
+            self.decoder = decoder
+        }
+        
+        deinit {
+            lock.deallocate()
+        }
+        
+        func receive(subscription: Subscription) {
+            os_unfair_lock_lock(lock)
+            guard self.subscription == nil else {
+                os_unfair_lock_unlock(lock)
+                subscription.cancel()
+                return
+            }
+            self.subscription = subscription
+            os_unfair_lock_unlock(lock)
+        }
+        
+        func receive(_ input: DataRequest) -> Subscribers.Demand {
+            input.responseDecodable(queue: queue, decoder: decoder, completionHandler: { (response: DataResponse<T>) -> Void in
+                if let result = self.downstream?.receive(response) {
+                    if result > 0 {
+                        os_unfair_lock_lock(self.lock)
+                        if let sub = self.subscription {
+                            os_unfair_lock_unlock(self.lock)
+                            sub.request(result)
+                            return
+                        }
+                        os_unfair_lock_unlock(self.lock)
+                    }
+                }
+            })
+            return .none
+        }
+        // swiftpm.slack.com
+        func receive(completion: Subscribers.Completion<Downstream.Failure>) {
+            os_unfair_lock_lock(lock)
+            if let ds = downstream {
+                os_unfair_lock_unlock(lock)
+                ds.receive(completion: completion)
+                return
+            }
+            os_unfair_lock_unlock(lock)
+        }
+        
+        func request(_ demand: Subscribers.Demand) {
+            os_unfair_lock_lock(lock)
+            if let sub = subscription {
+                os_unfair_lock_unlock(lock)
+                sub.request(demand)
+                return
+            }
+            os_unfair_lock_unlock(lock)
+            
+        }
+        
+        func cancel() {
+            os_unfair_lock_lock(lock)
+            subscription = nil
+            downstream = nil
+            os_unfair_lock_unlock(lock)
+        }
+        
+    }
+}
+
+#endif

+ 1 - 1
Source/Session.swift

@@ -118,7 +118,7 @@ open class Session {
             return try encoding.encode(request, with: parameters)
         }
     }
-
+    
     open func request(_ url: URLConvertible,
                       method: HTTPMethod = .get,
                       parameters: Parameters? = nil,

+ 49 - 0
Tests/RequestTests.swift

@@ -958,3 +958,52 @@ class RequestDebugDescriptionTestCase: BaseTestCase {
             .filter { $0 != "" && $0 != "\\" }
     }
 }
+
+#if canImport(Combine)
+
+import Combine
+
+@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
+final class RequestCombineTests: BaseTestCase {
+    func testReceiveDecodable() {
+        // Given
+        let request = URLRequest.makeHTTPBinRequest()
+        let expect = expectation(description: "request should finish")
+        var response: HTTPBinResponse?
+        
+        // When
+        let source: Publishers.Future<HTTPBinResponse, Error> = AF.request(request).futureDecodable()
+        
+        
+        _ = source.sink(receiveCompletion: { _ in expect.fulfill() },
+                    receiveValue: { resp in response = resp })
+        
+        waitForExpectations(timeout: 1)
+        
+        // Then
+        XCTAssertNotNil(response)
+    }
+    
+    func testReceiveDecodable2() {
+        // Given
+        let request = URLRequest.makeHTTPBinRequest()
+        let expect = expectation(description: "request should finish")
+        var response: DataResponse<HTTPBinResponse>?
+        
+        // When
+        let afRequest = AF.request(request)
+        let just = Publishers.Just(afRequest)
+        let connection = just.response(of: HTTPBinResponse.self).sink { networkResponse in
+            response = networkResponse
+            expect.fulfill()
+        }
+        
+        waitForExpectations(timeout: 1)
+        connection.cancel()
+        
+        // Then
+        XCTAssertNotNil(response)
+    }
+}
+
+#endif