Jelajahi Sumber

Dropped deployment targets to iOS 8.0 and macOS 10.10 for upcoming release.

Christian Noon 9 tahun lalu
induk
melakukan
8c399fc71f

+ 4 - 4
Alamofire.xcodeproj/project.pbxproj

@@ -1547,8 +1547,8 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
@@ -1596,8 +1596,8 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";

+ 2 - 0
Source/Alamofire.swift

@@ -442,6 +442,7 @@ public func upload(
 ///
 /// - returns: The created `StreamRequest`.
 @discardableResult
+@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
 public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
     return SessionManager.default.stream(withHostName: hostName, port: port)
 }
@@ -456,6 +457,7 @@ public func stream(withHostName hostName: String, port: Int) -> StreamRequest {
 ///
 /// - returns: The created `StreamRequest`.
 @discardableResult
+@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
 public func stream(with netService: NetService) -> StreamRequest {
     return SessionManager.default.stream(with: netService)
 }

+ 33 - 1
Source/ParameterEncoding.swift

@@ -199,7 +199,39 @@ public struct URLEncoding: ParameterEncoding {
         var allowedCharacterSet = CharacterSet.urlQueryAllowed
         allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
 
-        return string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
+        var escaped = ""
+
+        //==========================================================================================================
+        //
+        //  Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
+        //  hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
+        //  longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
+        //  info, please refer to:
+        //
+        //      - https://github.com/Alamofire/Alamofire/issues/206
+        //
+        //==========================================================================================================
+
+        if #available(iOS 8.3, *) {
+            escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
+        } else {
+            let batchSize = 50
+            var index = string.startIndex
+
+            while index != string.endIndex {
+                let startIndex = index
+                let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
+                let range = startIndex..<endIndex
+
+                let substring = string.substring(with: range)
+
+                escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring
+
+                index = endIndex
+            }
+        }
+
+        return escaped
     }
 
     private func query(_ parameters: [String: Any]) -> String {

+ 1 - 0
Source/Request.swift

@@ -592,6 +592,7 @@ open class UploadRequest: DataRequest {
 #if !os(watchOS)
 
 /// Specific type of `Request` that manages an underlying `URLSessionStreamTask`.
+@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
 open class StreamRequest: Request {
     enum Streamable: TaskConvertible {
         case stream(hostName: String, port: Int)

+ 55 - 15
Source/SessionDelegate.swift

@@ -108,16 +108,53 @@ open class SessionDelegate: NSObject {
 #if !os(watchOS)
 
     /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:readClosedFor:)`.
-    open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)?
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
+    open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)? {
+        get {
+            return _streamTaskReadClosed as? (URLSession, URLSessionStreamTask) -> Void
+        }
+        set {
+            _streamTaskReadClosed = newValue
+        }
+    }
 
     /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:writeClosedFor:)`.
-    open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)?
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
+    open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)? {
+        get {
+            return _streamTaskWriteClosed as? (URLSession, URLSessionStreamTask) -> Void
+        }
+        set {
+            _streamTaskWriteClosed = newValue
+        }
+    }
 
     /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:betterRouteDiscoveredFor:)`.
-    open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)?
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
+    open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)? {
+        get {
+            return _streamTaskBetterRouteDiscovered as? (URLSession, URLSessionStreamTask) -> Void
+        }
+        set {
+            _streamTaskBetterRouteDiscovered = newValue
+        }
+    }
 
     /// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:streamTask:didBecome:outputStream:)`.
-    open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)?
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
+    open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)? {
+        get {
+            return _streamTaskDidBecomeInputStream as? (URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void
+        }
+        set {
+            _streamTaskDidBecomeInputStream = newValue
+        }
+    }
+
+    var _streamTaskReadClosed: Any?
+    var _streamTaskWriteClosed: Any?
+    var _streamTaskBetterRouteDiscovered: Any?
+    var _streamTaskDidBecomeInputStream: Any?
 
 #endif
 
@@ -166,17 +203,19 @@ open class SessionDelegate: NSObject {
         #endif
 
         #if !os(watchOS)
-            switch selector {
-            case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)):
-                return streamTaskReadClosed != nil
-            case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)):
-                return streamTaskWriteClosed != nil
-            case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)):
-                return streamTaskBetterRouteDiscovered != nil
-            case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)):
-                return streamTaskDidBecomeInputAndOutputStreams != nil
-            default:
-                break
+            if #available(iOS 9.0, macOS 10.11, tvOS 9.0, *) {
+                switch selector {
+                case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)):
+                    return streamTaskReadClosed != nil
+                case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)):
+                    return streamTaskWriteClosed != nil
+                case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)):
+                    return streamTaskBetterRouteDiscovered != nil
+                case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)):
+                    return streamTaskDidBecomeInputAndOutputStreams != nil
+                default:
+                    break
+                }
             }
         #endif
 
@@ -637,6 +676,7 @@ extension SessionDelegate: URLSessionDownloadDelegate {
 
 #if !os(watchOS)
 
+@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
 extension SessionDelegate: URLSessionStreamDelegate {
     /// Tells the delegate that the read side of the connection has been closed.
     ///

+ 4 - 0
Source/SessionManager.swift

@@ -779,6 +779,7 @@ open class SessionManager {
     ///
     /// - returns: The created `StreamRequest`.
     @discardableResult
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
     open func stream(withHostName hostName: String, port: Int) -> StreamRequest {
         return stream(.stream(hostName: hostName, port: port))
     }
@@ -793,12 +794,14 @@ open class SessionManager {
     ///
     /// - returns: The created `StreamRequest`.
     @discardableResult
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
     open func stream(with netService: NetService) -> StreamRequest {
         return stream(.netService(netService))
     }
 
     // MARK: Private - Stream Implementation
 
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
     private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
         do {
             let task = try streamable.task(session: session, adapter: adapter, queue: queue)
@@ -814,6 +817,7 @@ open class SessionManager {
         }
     }
 
+    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
     private func stream(failedWith error: Error) -> StreamRequest {
         let stream = StreamRequest(session: session, requestTask: .stream(nil, nil), error: error)
         if startRequestsImmediately { stream.resume() }

+ 42 - 14
Tests/CacheTests.swift

@@ -239,6 +239,20 @@ class CacheTestCase: BaseTestCase {
         }
     }
 
+    // MARK: - Cache Helper Methods
+
+    private func isCachedResponseForNoStoreHeaderExpected() -> Bool {
+    #if os(iOS)
+        if #available(iOS 8.3, *) {
+            return false
+        } else {
+            return true
+        }
+    #else
+        return false
+    #endif
+    }
+
     // MARK: - Tests
 
     func testURLCacheContainsCachedResponsesForAllRequests() {
@@ -264,7 +278,12 @@ class CacheTestCase: BaseTestCase {
         XCTAssertNotNil(maxAgeNonExpiredResponse, "\(CacheControl.maxAgeNonExpired) response should not be nil")
         XCTAssertNotNil(maxAgeExpiredResponse, "\(CacheControl.maxAgeExpired) response should not be nil")
         XCTAssertNotNil(noCacheResponse, "\(CacheControl.noCache) response should not be nil")
-        XCTAssertNil(noStoreResponse, "\(CacheControl.noStore) response should be nil")
+
+        if isCachedResponseForNoStoreHeaderExpected() {
+            XCTAssertNotNil(noStoreResponse, "\(CacheControl.noStore) response should not be nil")
+        } else {
+            XCTAssertNil(noStoreResponse, "\(CacheControl.noStore) response should be nil")
+        }
     }
 
     func testDefaultCachePolicy() {
@@ -297,7 +316,12 @@ class CacheTestCase: BaseTestCase {
         executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.maxAgeNonExpired, shouldReturnCachedResponse: true)
         executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.maxAgeExpired, shouldReturnCachedResponse: true)
         executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.noCache, shouldReturnCachedResponse: true)
-        executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.noStore, shouldReturnCachedResponse: false)
+
+        if isCachedResponseForNoStoreHeaderExpected() {
+            executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.noStore, shouldReturnCachedResponse: true)
+        } else {
+            executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.noStore, shouldReturnCachedResponse: false)
+        }
     }
 
     func testUseLocalCacheDataAndDontLoadFromNetworkPolicy() {
@@ -309,19 +333,23 @@ class CacheTestCase: BaseTestCase {
         executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.maxAgeExpired, shouldReturnCachedResponse: true)
         executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.noCache, shouldReturnCachedResponse: true)
 
-        // Given
-        let expectation = self.expectation(description: "GET request to httpbin")
-        var response: HTTPURLResponse?
-
-        // When
-        startRequest(cacheControl: CacheControl.noStore, cachePolicy: cachePolicy) { _, responseResponse in
-            response = responseResponse
-            expectation.fulfill()
-        }
+        if isCachedResponseForNoStoreHeaderExpected() {
+            executeTest(cachePolicy: cachePolicy, cacheControl: CacheControl.noStore, shouldReturnCachedResponse: true)
+        } else {
+            // Given
+            let expectation = self.expectation(description: "GET request to httpbin")
+            var response: HTTPURLResponse?
+
+            // When
+            startRequest(cacheControl: CacheControl.noStore, cachePolicy: cachePolicy) { _, responseResponse in
+                response = responseResponse
+                expectation.fulfill()
+            }
 
-        waitForExpectations(timeout: timeout, handler: nil)
+            waitForExpectations(timeout: timeout, handler: nil)
 
-        // Then
-        XCTAssertNil(response, "response should be nil")
+            // Then
+            XCTAssertNil(response, "response should be nil")
+        }
     }
 }

+ 48 - 34
Tests/SessionDelegateTests.swift

@@ -64,53 +64,67 @@ class SessionDelegateTestCase: BaseTestCase {
     // MARK: - Tests - Session Challenges
 
     func testThatSessionDidReceiveChallengeClosureIsCalledWhenSet() {
-        // Given
-        let expectation = self.expectation(description: "Override closure should be called")
+        if #available(iOS 9.0, *) {
+            // Given
+            let expectation = self.expectation(description: "Override closure should be called")
 
-        var overrideClosureCalled = false
-        var response: HTTPURLResponse?
+            var overrideClosureCalled = false
+            var response: HTTPURLResponse?
 
-        manager.delegate.sessionDidReceiveChallenge = { session, challenge in
-            overrideClosureCalled = true
-            return (.performDefaultHandling, nil)
-        }
+            manager.delegate.sessionDidReceiveChallenge = { session, challenge in
+                overrideClosureCalled = true
+                return (.performDefaultHandling, nil)
+            }
 
-        // When
-        manager.request("https://httpbin.org/get").responseJSON { closureResponse in
-            response = closureResponse.response
-            expectation.fulfill()
-        }
+            // When
+            manager.request("https://httpbin.org/get").responseJSON { closureResponse in
+                response = closureResponse.response
+                expectation.fulfill()
+            }
 
-        waitForExpectations(timeout: timeout, handler: nil)
+            waitForExpectations(timeout: timeout, handler: nil)
 
-        // Then
-        XCTAssertTrue(overrideClosureCalled)
-        XCTAssertEqual(response?.statusCode, 200)
+            // Then
+            XCTAssertTrue(overrideClosureCalled)
+            XCTAssertEqual(response?.statusCode, 200)
+        } else {
+            // This test MUST be disabled on iOS 8.x because `respondsToSelector` is not being called for the
+            // `URLSession:didReceiveChallenge:completionHandler:` selector when more than one test here is run
+            // at a time. Whether we flush the URL session of wipe all the shared credentials, the behavior is
+            // still the same. Until we find a better solution, we'll need to disable this test on iOS 8.x.
+        }
     }
 
     func testThatSessionDidReceiveChallengeWithCompletionClosureIsCalledWhenSet() {
-        // Given
-        let expectation = self.expectation(description: "Override closure should be called")
+        if #available(iOS 9.0, *) {
+            // Given
+            let expectation = self.expectation(description: "Override closure should be called")
 
-        var overrideClosureCalled = false
-        var response: HTTPURLResponse?
+            var overrideClosureCalled = false
+            var response: HTTPURLResponse?
 
-        manager.delegate.sessionDidReceiveChallengeWithCompletion = { session, challenge, completion in
-            overrideClosureCalled = true
-            completion(.performDefaultHandling, nil)
-        }
+            manager.delegate.sessionDidReceiveChallengeWithCompletion = { session, challenge, completion in
+                overrideClosureCalled = true
+                completion(.performDefaultHandling, nil)
+            }
 
-        // When
-        manager.request("https://httpbin.org/get").responseJSON { closureResponse in
-            response = closureResponse.response
-            expectation.fulfill()
-        }
+            // When
+            manager.request("https://httpbin.org/get").responseJSON { closureResponse in
+                response = closureResponse.response
+                expectation.fulfill()
+            }
 
-        waitForExpectations(timeout: timeout, handler: nil)
+            waitForExpectations(timeout: timeout, handler: nil)
 
-        // Then
-        XCTAssertTrue(overrideClosureCalled)
-        XCTAssertEqual(response?.statusCode, 200)
+            // Then
+            XCTAssertTrue(overrideClosureCalled)
+            XCTAssertEqual(response?.statusCode, 200)
+        } else {
+            // This test MUST be disabled on iOS 8.x because `respondsToSelector` is not being called for the
+            // `URLSession:didReceiveChallenge:completionHandler:` selector when more than one test here is run
+            // at a time. Whether we flush the URL session of wipe all the shared credentials, the behavior is
+            // still the same. Until we find a better solution, we'll need to disable this test on iOS 8.x.
+        }
     }
 
     // MARK: - Tests - Redirects

+ 9 - 3
Tests/URLProtocolTests.swift

@@ -162,9 +162,15 @@ class URLProtocolTestCase: BaseTestCase {
         XCTAssertNotNil(response?.data)
         XCTAssertNil(response?.error)
 
-        if let headers = response?.response?.allHeaderFields {
-            XCTAssertEqual(headers["request-header"] as? String, "foobar")
-            XCTAssertEqual(headers["session-configuration-header"] as? String, "foo")
+        if let headers = response?.response?.allHeaderFields as? [String: String] {
+            XCTAssertEqual(headers["request-header"], "foobar")
+
+            // Configuration headers are only passed in on iOS 9.0+
+            if #available(iOS 9.0, *) {
+                XCTAssertEqual(headers["session-configuration-header"], "foo")
+            } else {
+                XCTAssertNil(headers["session-configuration-header"])
+            }
         } else {
             XCTFail("headers should not be nil")
         }