diff --git a/Cartfile b/Cartfile index 62674f6..1a077cb 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,7 @@ -github "mxcl/PromiseKit" ~> 6.3 +#github "mxcl/PromiseKit" ~> 6.3 +github "dougzilla32/PromiseKit" "PMKCancel" github "mxcl/OMGHTTPURLRQ" ~> 3.2 -github "PromiseKit/Foundation" ~> 3.1 +#github "PromiseKit/Foundation" ~> 3.1 +github "dougzilla32/Foundation" "PMKCancel" +#github "PromiseKit/Cancel" ~> 1.0 +github "dougzilla32/Cancel" ~> 1.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index ebbdb14..2ebb1f0 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,4 +1,5 @@ github "AliSoftware/OHHTTPStubs" "6.1.0" -github "PromiseKit/Foundation" "3.1.0" +github "dougzilla32/Cancel" "1.0.0" +github "dougzilla32/Foundation" "1d0f84d4ae50db696feeb050f9dd9ebb90fd59be" +github "dougzilla32/PromiseKit" "a0217bd7b69af68237dcdeee0197e63259b9d445" github "mxcl/OMGHTTPURLRQ" "3.2.5" -github "mxcl/PromiseKit" "6.3.3" diff --git a/PMKOMGHTTPURLRQ.xcodeproj/project.pbxproj b/PMKOMGHTTPURLRQ.xcodeproj/project.pbxproj index c619061..22bb70a 100644 --- a/PMKOMGHTTPURLRQ.xcodeproj/project.pbxproj +++ b/PMKOMGHTTPURLRQ.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ OMGHTTPURLRQ, OHHTTPStubs, PMKFoundation, + PMKCancel, ); name = "Embed Carthage Frameworks"; outputPaths = ( diff --git a/Sources/NSURLSession+OMG+Promise.swift b/Sources/NSURLSession+OMG+Promise.swift index eca8512..d55e994 100644 --- a/Sources/NSURLSession+OMG+Promise.swift +++ b/Sources/NSURLSession+OMG+Promise.swift @@ -1,6 +1,7 @@ import OMGHTTPURLRQ import Foundation #if !PMKCocoaPods +import PMKCancel import PMKFoundation import PromiseKit #endif @@ -168,3 +169,156 @@ extension URLSession { } } } + +//////////////////////////////////////////////////////////// Cancellation + +extension URLSession { + /** + Makes a cancellable **GET** request to the provided URL. + + let p = URLSession.shared.GET("http://example.com", query: ["foo": "bar"]) + p.then { data -> Void in + //… + } + p.asImage().then { image -> Void in + //… + } + p.asDictionary().then { json -> Void in + //… + } + + - Parameter url: The URL to request. + - Parameter query: The parameters to be encoded as the query string for the GET request. + - Returns: A cancellable promise that represents the GET request. + */ + public func GETCC(_ url: String, query: [String: Any]? = nil) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.get(url, query) as URLRequest) + } + + /** + Makes a cancellable POST request to the provided URL passing form URL encoded + parameters. + + Form URL-encoding is the standard way to POST on the Internet, so + probably this is what you want. If it doesn’t work, try the `+POST:JSON` + variant. + + let url = "http://jsonplaceholder.typicode.com/posts" + let params = ["title": "foo", "body": "bar", "userId": 1] + URLSession.shared.POST(url, formData: params).asDictionary().then { json -> Void in + //… + } + + - Parameter url: The URL to request. + - Parameter formData: The parameters to be form URL-encoded and passed as the POST body. + - Returns: A cancellable promise that represents the POST request. + */ + public func POSTCC(_ url: String, formData: [String: Any]? = nil) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.post(url, formData) as URLRequest) + } + + /** + Makes a cancellable POST request to the provided URL passing multipart form-data. + + let formData = OMGMultipartFormData() + let imgData = Data(contentsOfFile: "image.png") + formData.addFile(imgdata, parameterName: "file1", filename: "myimage1.png", contentType: "image/png") + + URLSession.shared.POST(url, multipartFormData: formData).then { data in + //… + } + + - Parameter url: The URL to request. + - Parameter multipartFormData: The parameters to be multipart form-data encoded and passed as the POST body. + - Returns: A cancellable promise that represents the POST request. + - SeeAlso: [https://github.com/mxcl/OMGHTTPURLRQ](OMGHTTPURLRQ) + */ + public func POSTCC(_ url: String, multipartFormData: OMGMultipartFormData) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.post(url, multipartFormData) as URLRequest) + } + + /** + Makes a cancellable POST request to the provided URL passing JSON encoded + parameters. + + Most web servers nowadays support POST with either JSON or form + URL-encoding. If in doubt try form URL-encoded parameters first. + + let url = "http://jsonplaceholder.typicode.com/posts" + let params = ["title": "foo", "body": "bar", "userId": 1] + URLSession.shared.POST(url, json: params).asDictionary().then { json -> Void in + //… + } + + - Parameter url: The URL to request. + - Parameter json: The parameters to be JSON-encoded and passed as the POST body. + - Returns: A cancellable promise that represents the POST request. + */ + public func POSTCC(_ url: String, json: [String: Any]? = nil) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.post(url, json: json) as URLRequest) + } + + /** + Makes a cancellable PUT request to the provided URL passing JSON encoded parameters. + + let url = "http://jsonplaceholder.typicode.com/posts" + let params = ["title": "foo", "body": "bar", "userId": 1] + URLSession.shared.PUT(url, json: params).asDictionary().then { json -> Void in + //… + } + + - Parameter url: The URL to request. + - Parameter json: The parameters to be JSON-encoded and passed as the PUT body. + - Returns: A cancellable promise that represents the PUT request. + */ + public func PUTCC(_ url: String, json: [String: Any]? = nil) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.put(url, json: json) as URLRequest) + } + + /** + Makes a cancellable DELETE request to the provided URL passing form URL-encoded + parameters. + + let url = "http://jsonplaceholder.typicode.com/posts/1" + URLSession.shared.DELETE(url).then.asDictionary() { json -> Void in + //… + } + + - Parameter url: The URL to request. + - Returns: A cancellable promise that represents the PUT request. + */ + public func DELETECC(_ url: String) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.delete(url, nil) as URLRequest) + } + + /** + Makes a cancellable PATCH request to the provided URL passing the provided JSON parameters. + + let url = "http://jsonplaceholder.typicode.com/posts/1" + let params = ["foo": "bar"] + NSURLConnection.PATCH(url, json: params).asDictionary().then { json -> Void in + //… + } + - Parameter url: The URL to request. + - Parameter json: The JSON parameters to encode as the PATCH body. + - Returns: A cancellable promise that represents the PUT request. + */ + public func PATCHCC(_ url: String, json: [String: Any]? = nil) -> CancellablePromise<(data: Data, response: URLResponse)> { + return startCC(try OMGHTTPURLRQ.patch(url, json: json) as URLRequest) + } + + private func startCC(_ body: @autoclosure () throws -> URLRequest) -> CancellablePromise<(data: Data, response: URLResponse)> { + do { + var request = try body() + + if request.value(forHTTPHeaderField: "User-Agent") == nil { + request.setValue(OMGUserAgent(), forHTTPHeaderField: "User-Agent") + } + + return dataTaskCC(.promise, with: request) + } catch { + return CancellablePromise(error: error) + } + } +} + diff --git a/Tests/TestNSURLSession.swift b/Tests/TestNSURLSession.swift index 8e3d77e..91962a0 100644 --- a/Tests/TestNSURLSession.swift +++ b/Tests/TestNSURLSession.swift @@ -1,3 +1,4 @@ +import PMKCancel import PMKOMGHTTPURLRQ import OHHTTPStubs import PromiseKit @@ -72,3 +73,81 @@ class NSURLSessionTests: XCTestCase { OHHTTPStubs.removeAllStubs() } } + +//////////////////////////////////////////////////////////// Cancellation + +extension NSURLSessionTests { + func testCancel1() { + let json: NSDictionary = ["key1": "value1", "key2": ["value2A", "value2B"]] + + OHHTTPStubs.stubRequests(passingTest: { $0.url!.host == "example.com" }) { _ in + return OHHTTPStubsResponse(jsonObject: json, statusCode: 200, headers: nil) + } + + let ex = expectation(description: "") + URLSession.shared.GETCC("http://example.com").compactMap { + XCTFail() + try JSONSerialization.jsonObject(with: $0.data) + }.done { + XCTAssertEqual(json, $0 as? NSDictionary) + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + }.cancel() + waitForExpectations(timeout: 1) + } + + func testCancel2() { + + // test that Promise chains thens + // this test because I don’t trust the Swift compiler + + let dummy = ("fred" as NSString).data(using: String.Encoding.utf8.rawValue)! + + OHHTTPStubs.stubRequests(passingTest: { $0.url!.host == "example.com" }) { _ in + return OHHTTPStubsResponse(data: dummy, statusCode: 200, headers: [:]) + } + + let ex = expectation(description: "") + + afterCC(seconds: 0.1).then { () -> CancellablePromise<(data: Data, response: URLResponse)> in + let p = URLSession.shared.GETCC("http://example.com") + p.cancel() + return p + }.done { + XCTAssertEqual($0.data, dummy) + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + } + + waitForExpectations(timeout: 1) + } + + func testCancelSyntax() { + let json: NSDictionary = ["key1": "value1", "key2": ["value2A", "value2B"]] + + OHHTTPStubs.stubRequests(passingTest: { + $0.url!.host == "example.com" + }, withStubResponse: { _ in + OHHTTPStubsResponse(jsonObject: json, statusCode: 200, headers: nil) + }) + + let p = URLSession.shared.GETCC("http://example.com", query: [ + "1": 1, + "2": 2 + ]) + + let ex = expectation(description: "") + p.compactMap { + p.cancel() + try JSONSerialization.jsonObject(with: $0.data) + }.done { + XCTAssertEqual(json, $0 as? NSDictionary) + XCTFail() + }.catch(policy: .allErrors) { + $0.isCancelled ? ex.fulfill() : XCTFail() + } + waitForExpectations(timeout: 1) + } +}