From 51ccc175ccebbdb6e042078f1ee82d3994dfaf34 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 18 Oct 2015 00:35:30 +0900 Subject: [PATCH 1/7] Rename API as Session --- APIKit/API.swift | 5 ++++- APIKit/Session.swift | 9 ++++++++ APIKitTests/APITests.swift | 38 +++++++++++++++++----------------- APIKitTests/RequestTests.swift | 6 +++--- 4 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 APIKit/Session.swift diff --git a/APIKit/API.swift b/APIKit/API.swift index c04477c0..21e4e220 100644 --- a/APIKit/API.swift +++ b/APIKit/API.swift @@ -1,7 +1,7 @@ import Foundation import Result -public class API { +public class Session { public class var defaultURLSession: NSURLSession { return internalDefaultURLSession } @@ -78,6 +78,9 @@ public class API { } } +@available(*, deprecated, message="API is renamed as Session.") +public typealias API = Session + // MARK: - default implementation of URLSessionDelegate public class URLSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { // MARK: NSURLSessionTaskDelegate diff --git a/APIKit/Session.swift b/APIKit/Session.swift new file mode 100644 index 00000000..2282a3f8 --- /dev/null +++ b/APIKit/Session.swift @@ -0,0 +1,9 @@ +// +// Session.swift +// APIKit +// +// Created by Yosuke Ishikawa on 2015/10/17. +// Copyright © 2015年 Yosuke Ishikawa. All rights reserved. +// + +import Foundation diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift index 45176d9b..2d5f3b04 100644 --- a/APIKitTests/APITests.swift +++ b/APIKitTests/APITests.swift @@ -3,17 +3,17 @@ import APIKit import XCTest import OHHTTPStubs -protocol MockAPIRequestType: RequestType { +protocol MockSessionRequestType: RequestType { } -extension MockAPIRequestType { +extension MockSessionRequestType { var baseURL: NSURL { return NSURL(string: "https://api.github.com")! } } -class MockAPI: API { - struct GetRoot: MockAPIRequestType { +class MockSession: Session { + struct GetRoot: MockSessionRequestType { typealias Response = [String: AnyObject] var method: HTTPMethod { @@ -30,7 +30,7 @@ class MockAPI: API { } } -class AnotherMockAPI: API { +class AnotherMockSession: Session { } @@ -52,9 +52,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = MockAPI.GetRoot() + let request = MockSession.GetRoot() - MockAPI.sendRequest(request) { response in + MockSession.sendRequest(request) { response in switch response { case .Success(let dictionary): XCTAssert(dictionary["key"] as? String == "value") @@ -79,9 +79,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = MockAPI.GetRoot() + let request = MockSession.GetRoot() - MockAPI.sendRequest(request) { response in + MockSession.sendRequest(request) { response in switch response { case .Success: XCTFail() @@ -112,9 +112,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = MockAPI.GetRoot() + let request = MockSession.GetRoot() - MockAPI.sendRequest(request) { response in + MockSession.sendRequest(request) { response in switch response { case .Success: XCTFail() @@ -146,9 +146,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = MockAPI.GetRoot() + let request = MockSession.GetRoot() - MockAPI.sendRequest(request) { response in + MockSession.sendRequest(request) { response in switch response { case .Success: XCTFail() @@ -182,9 +182,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = MockAPI.GetRoot() + let request = MockSession.GetRoot() - MockAPI.sendRequest(request) { response in + MockSession.sendRequest(request) { response in switch response { case .Success: XCTFail() @@ -203,7 +203,7 @@ class APITests: XCTestCase { expectation.fulfill() } - MockAPI.cancelRequest(MockAPI.GetRoot.self) + MockSession.cancelRequest(MockSession.GetRoot.self) waitForExpectationsWithTimeout(1.0, handler: nil) } @@ -221,9 +221,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = MockAPI.GetRoot() + let request = MockSession.GetRoot() - MockAPI.sendRequest(request) { response in + MockSession.sendRequest(request) { response in switch response { case .Success: break @@ -235,7 +235,7 @@ class APITests: XCTestCase { expectation.fulfill() } - MockAPI.cancelRequest(MockAPI.GetRoot.self) { request in + MockSession.cancelRequest(MockSession.GetRoot.self) { request in return false } diff --git a/APIKitTests/RequestTests.swift b/APIKitTests/RequestTests.swift index 7be11af5..3e3cbad5 100644 --- a/APIKitTests/RequestTests.swift +++ b/APIKitTests/RequestTests.swift @@ -3,7 +3,7 @@ import OHHTTPStubs import APIKit class RequestTests: XCTestCase { - struct SearchRequest: MockAPIRequestType { + struct SearchRequest: MockSessionRequestType { let query: String // MARK: RequestType @@ -45,7 +45,7 @@ class RequestTests: XCTestCase { let request = SearchRequest(query: "こんにちは") let expectation = expectationWithDescription("waiting for the response.") - API.sendRequest(request) { result in + Session.sendRequest(request) { result in expectation.fulfill() } @@ -63,7 +63,7 @@ class RequestTests: XCTestCase { let request = SearchRequest(query: "!\"#$%&'()0=~|`{}*+<>?_") let expectation = expectationWithDescription("waiting for the response.") - API.sendRequest(request) { result in + Session.sendRequest(request) { result in expectation.fulfill() } From 81916bf88b174271490679bb76c64595d65ac3ee Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 18 Oct 2015 00:36:50 +0900 Subject: [PATCH 2/7] Rename file: API.swift -> Session.swift --- APIKit.xcodeproj/project.pbxproj | 12 +-- APIKit/API.swift | 172 ----------------------------- APIKit/Session.swift | 179 +++++++++++++++++++++++++++++-- 3 files changed, 177 insertions(+), 186 deletions(-) delete mode 100644 APIKit/API.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 642641a2..d9417b7b 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -14,11 +14,11 @@ 7F0869A11A9787AF001AD3E1 /* ResponseBodyParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */; }; 7F0869A61A978BCA001AD3E1 /* URLEncodedSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0869A51A978BCA001AD3E1 /* URLEncodedSerialization.swift */; }; 7F0869A71A978BCA001AD3E1 /* URLEncodedSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0869A51A978BCA001AD3E1 /* URLEncodedSerialization.swift */; }; - 7F0869A81A979088001AD3E1 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD171A94D085006863BB /* API.swift */; }; + 7F0869A81A979088001AD3E1 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD171A94D085006863BB /* Session.swift */; }; 7F1B190B1AA2CA1300C7AFCF /* APITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F1B190A1AA2CA1300C7AFCF /* APITests.swift */; }; 7F1B190C1AA2CA1300C7AFCF /* APITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F1B190A1AA2CA1300C7AFCF /* APITests.swift */; }; 7F30A8561A975BD600A8C136 /* RequestBodyBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F30A8551A975BD600A8C136 /* RequestBodyBuilderTests.swift */; }; - 7F45FD181A94D085006863BB /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD171A94D085006863BB /* API.swift */; }; + 7F45FD181A94D085006863BB /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD171A94D085006863BB /* Session.swift */; }; 7F5FA6B51B3C58210090B0AF /* APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F5FA6B41B3C58210090B0AF /* APIError.swift */; }; 7F68ABDA1AC4412E00688D68 /* RequestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F68ABD91AC4412E00688D68 /* RequestType.swift */; }; 7F68ABDB1AC4412E00688D68 /* RequestType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F68ABD91AC4412E00688D68 /* RequestType.swift */; }; @@ -96,7 +96,7 @@ 7F45FCE11A94D02C006863BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7F45FCE21A94D02C006863BB /* APIKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APIKit.h; sourceTree = ""; }; 7F45FCFE1A94D04D006863BB /* APIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = APIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 7F45FD171A94D085006863BB /* API.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; + 7F45FD171A94D085006863BB /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; 7F5FA6B41B3C58210090B0AF /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; 7F68ABD91AC4412E00688D68 /* RequestType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestType.swift; sourceTree = ""; }; 7F68ABDC1AC4414500688D68 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; @@ -176,7 +176,7 @@ isa = PBXGroup; children = ( 7F45FCE21A94D02C006863BB /* APIKit.h */, - 7F45FD171A94D085006863BB /* API.swift */, + 7F45FD171A94D085006863BB /* Session.swift */, 7F68ABD91AC4412E00688D68 /* RequestType.swift */, 7F68ABDC1AC4414500688D68 /* HTTPMethod.swift */, 7F5FA6B41B3C58210090B0AF /* APIError.swift */, @@ -407,7 +407,7 @@ buildActionMask = 2147483647; files = ( 7FCBE9DD1A9734880075AFD9 /* RequestBodyBuilder.swift in Sources */, - 7F45FD181A94D085006863BB /* API.swift in Sources */, + 7F45FD181A94D085006863BB /* Session.swift in Sources */, 7F68ABDD1AC4414500688D68 /* HTTPMethod.swift in Sources */, 7F68ABDA1AC4412E00688D68 /* RequestType.swift in Sources */, 7FCBE9E01A9734950075AFD9 /* ResponseBodyParser.swift in Sources */, @@ -422,7 +422,7 @@ files = ( 84B5C6BC1B42CD430032068D /* APIError.swift in Sources */, 7FCBE9DE1A9734880075AFD9 /* RequestBodyBuilder.swift in Sources */, - 7F0869A81A979088001AD3E1 /* API.swift in Sources */, + 7F0869A81A979088001AD3E1 /* Session.swift in Sources */, 7F68ABDE1AC4414500688D68 /* HTTPMethod.swift in Sources */, 7F68ABDB1AC4412E00688D68 /* RequestType.swift in Sources */, 7FCBE9E11A9734950075AFD9 /* ResponseBodyParser.swift in Sources */, diff --git a/APIKit/API.swift b/APIKit/API.swift deleted file mode 100644 index 21e4e220..00000000 --- a/APIKit/API.swift +++ /dev/null @@ -1,172 +0,0 @@ -import Foundation -import Result - -public class Session { - public class var defaultURLSession: NSURLSession { - return internalDefaultURLSession - } - - private static let internalDefaultURLSession = NSURLSession( - configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), - delegate: URLSessionDelegate(), - delegateQueue: nil - ) - - // send request and build response object - public static func sendRequest(request: T, URLSession: NSURLSession = defaultURLSession, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { - switch request.buildURLRequest() { - case .Failure(let error): - handler(.Failure(error)) - return nil - - case .Success(let URLRequest): - let dataTask = URLSession.dataTaskWithRequest(URLRequest) - dataTask.request = Box(request) - dataTask.completionHandler = { data, URLResponse, connectionError in - let sessionResult: Result<(NSData, NSURLResponse?), APIError> - if let error = connectionError { - sessionResult = .Failure(.ConnectionError(error)) - } else { - sessionResult = .Success((data, URLResponse)) - } - - let result: Result = sessionResult.flatMap { data, URLResponse in - request.parseData(data, URLResponse: URLResponse) - } - - dispatch_async(dispatch_get_main_queue()) { - handler(result) - } - } - - dataTask.resume() - - return dataTask - } - } - - public static func cancelRequest(requestType: T.Type, passingTest test: T -> Bool = { r in true }) { - cancelRequest(requestType, URLSession: defaultURLSession, passingTest: test) - } - - public static func cancelRequest(requestType: T.Type, URLSession: NSURLSession, passingTest test: T -> Bool = { r in true }) { - URLSession.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in - let allTasks = dataTasks as [NSURLSessionTask] - + uploadTasks as [NSURLSessionTask] - + downloadTasks as [NSURLSessionTask] - - allTasks.filter { task in - var request: T? - switch task { - case let x as NSURLSessionDataTask: - request = x.request?.value as? T - - case let x as NSURLSessionDownloadTask: - request = x.request?.value as? T - - default: - break - } - - if let request = request { - return test(request) - } else { - return false - } - }.forEach { $0.cancel() } - } - } -} - -@available(*, deprecated, message="API is renamed as Session.") -public typealias API = Session - -// MARK: - default implementation of URLSessionDelegate -public class URLSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { - // MARK: NSURLSessionTaskDelegate - public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) { - if let dataTask = task as? NSURLSessionDataTask { - dataTask.completionHandler?(dataTask.responseBuffer, dataTask.response, connectionError) - } - } - - // MARK: NSURLSessionDataDelegate - public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { - dataTask.responseBuffer.appendData(data) - } - - public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) { - downloadTask.request = dataTask.request - } -} - -// Box is still necessary internally to store struct into associated object -private final class Box { - let value: T - init(_ value: T) { - self.value = value - } -} - -// MARK: - NSURLSessionTask extensions -private var taskRequestKey = 0 -private var dataTaskResponseBufferKey = 0 -private var dataTaskCompletionHandlerKey = 0 - -private extension NSURLSessionDataTask { - // `var request: RequestType?` is not available in Swift 2.0 - // ("protocol can only be used as a generic constraint") - private var request: Box? { - get { - return objc_getAssociatedObject(self, &taskRequestKey) as? Box - } - - set { - if let value = newValue { - objc_setAssociatedObject(self, &taskRequestKey, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } else { - objc_setAssociatedObject(self, &taskRequestKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - } - - private var responseBuffer: NSMutableData { - if let responseBuffer = objc_getAssociatedObject(self, &dataTaskResponseBufferKey) as? NSMutableData { - return responseBuffer - } else { - let responseBuffer = NSMutableData() - objc_setAssociatedObject(self, &dataTaskResponseBufferKey, responseBuffer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return responseBuffer - } - } - - private var completionHandler: ((NSData, NSURLResponse?, NSError?) -> Void)? { - get { - return (objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData, NSURLResponse?, NSError?) -> Void>)?.value - } - - set { - if let value = newValue { - objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, Box(value), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } else { - objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - } -} - -private extension NSURLSessionDownloadTask { - private var request: Box? { - get { - return objc_getAssociatedObject(self, &taskRequestKey) as? Box - } - - set { - if let value = newValue { - objc_setAssociatedObject(self, &taskRequestKey, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } else { - objc_setAssociatedObject(self, &taskRequestKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - } -} diff --git a/APIKit/Session.swift b/APIKit/Session.swift index 2282a3f8..21e4e220 100644 --- a/APIKit/Session.swift +++ b/APIKit/Session.swift @@ -1,9 +1,172 @@ -// -// Session.swift -// APIKit -// -// Created by Yosuke Ishikawa on 2015/10/17. -// Copyright © 2015年 Yosuke Ishikawa. All rights reserved. -// - import Foundation +import Result + +public class Session { + public class var defaultURLSession: NSURLSession { + return internalDefaultURLSession + } + + private static let internalDefaultURLSession = NSURLSession( + configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), + delegate: URLSessionDelegate(), + delegateQueue: nil + ) + + // send request and build response object + public static func sendRequest(request: T, URLSession: NSURLSession = defaultURLSession, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { + switch request.buildURLRequest() { + case .Failure(let error): + handler(.Failure(error)) + return nil + + case .Success(let URLRequest): + let dataTask = URLSession.dataTaskWithRequest(URLRequest) + dataTask.request = Box(request) + dataTask.completionHandler = { data, URLResponse, connectionError in + let sessionResult: Result<(NSData, NSURLResponse?), APIError> + if let error = connectionError { + sessionResult = .Failure(.ConnectionError(error)) + } else { + sessionResult = .Success((data, URLResponse)) + } + + let result: Result = sessionResult.flatMap { data, URLResponse in + request.parseData(data, URLResponse: URLResponse) + } + + dispatch_async(dispatch_get_main_queue()) { + handler(result) + } + } + + dataTask.resume() + + return dataTask + } + } + + public static func cancelRequest(requestType: T.Type, passingTest test: T -> Bool = { r in true }) { + cancelRequest(requestType, URLSession: defaultURLSession, passingTest: test) + } + + public static func cancelRequest(requestType: T.Type, URLSession: NSURLSession, passingTest test: T -> Bool = { r in true }) { + URLSession.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in + let allTasks = dataTasks as [NSURLSessionTask] + + uploadTasks as [NSURLSessionTask] + + downloadTasks as [NSURLSessionTask] + + allTasks.filter { task in + var request: T? + switch task { + case let x as NSURLSessionDataTask: + request = x.request?.value as? T + + case let x as NSURLSessionDownloadTask: + request = x.request?.value as? T + + default: + break + } + + if let request = request { + return test(request) + } else { + return false + } + }.forEach { $0.cancel() } + } + } +} + +@available(*, deprecated, message="API is renamed as Session.") +public typealias API = Session + +// MARK: - default implementation of URLSessionDelegate +public class URLSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { + // MARK: NSURLSessionTaskDelegate + public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) { + if let dataTask = task as? NSURLSessionDataTask { + dataTask.completionHandler?(dataTask.responseBuffer, dataTask.response, connectionError) + } + } + + // MARK: NSURLSessionDataDelegate + public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { + dataTask.responseBuffer.appendData(data) + } + + public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) { + downloadTask.request = dataTask.request + } +} + +// Box is still necessary internally to store struct into associated object +private final class Box { + let value: T + init(_ value: T) { + self.value = value + } +} + +// MARK: - NSURLSessionTask extensions +private var taskRequestKey = 0 +private var dataTaskResponseBufferKey = 0 +private var dataTaskCompletionHandlerKey = 0 + +private extension NSURLSessionDataTask { + // `var request: RequestType?` is not available in Swift 2.0 + // ("protocol can only be used as a generic constraint") + private var request: Box? { + get { + return objc_getAssociatedObject(self, &taskRequestKey) as? Box + } + + set { + if let value = newValue { + objc_setAssociatedObject(self, &taskRequestKey, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + objc_setAssociatedObject(self, &taskRequestKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } + + private var responseBuffer: NSMutableData { + if let responseBuffer = objc_getAssociatedObject(self, &dataTaskResponseBufferKey) as? NSMutableData { + return responseBuffer + } else { + let responseBuffer = NSMutableData() + objc_setAssociatedObject(self, &dataTaskResponseBufferKey, responseBuffer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return responseBuffer + } + } + + private var completionHandler: ((NSData, NSURLResponse?, NSError?) -> Void)? { + get { + return (objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData, NSURLResponse?, NSError?) -> Void>)?.value + } + + set { + if let value = newValue { + objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, Box(value), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } +} + +private extension NSURLSessionDownloadTask { + private var request: Box? { + get { + return objc_getAssociatedObject(self, &taskRequestKey) as? Box + } + + set { + if let value = newValue { + objc_setAssociatedObject(self, &taskRequestKey, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + objc_setAssociatedObject(self, &taskRequestKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } +} From 01694c51cd5e0ea6f4de9fbddd4d82f818320ab9 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 18 Oct 2015 01:09:53 +0900 Subject: [PATCH 3/7] Assign NSURLSession to instances of Session --- APIKit/Session.swift | 51 ++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/APIKit/Session.swift b/APIKit/Session.swift index 21e4e220..46978c2a 100644 --- a/APIKit/Session.swift +++ b/APIKit/Session.swift @@ -2,18 +2,14 @@ import Foundation import Result public class Session { - public class var defaultURLSession: NSURLSession { - return internalDefaultURLSession + public let URLSession: NSURLSession + + public init(URLSession: NSURLSession) { + self.URLSession = URLSession } - private static let internalDefaultURLSession = NSURLSession( - configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), - delegate: URLSessionDelegate(), - delegateQueue: nil - ) - // send request and build response object - public static func sendRequest(request: T, URLSession: NSURLSession = defaultURLSession, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { + public func sendRequest(request: T, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { switch request.buildURLRequest() { case .Failure(let error): handler(.Failure(error)) @@ -44,12 +40,8 @@ public class Session { return dataTask } } - - public static func cancelRequest(requestType: T.Type, passingTest test: T -> Bool = { r in true }) { - cancelRequest(requestType, URLSession: defaultURLSession, passingTest: test) - } - - public static func cancelRequest(requestType: T.Type, URLSession: NSURLSession, passingTest test: T -> Bool = { r in true }) { + + public func cancelRequest(requestType: T.Type, passingTest test: T -> Bool = { r in true }) { URLSession.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in let allTasks = dataTasks as [NSURLSessionTask] + uploadTasks as [NSURLSessionTask] @@ -76,11 +68,38 @@ public class Session { }.forEach { $0.cancel() } } } + + // Shared session for static methods + public static let sharedSession = Session(URLSession: NSURLSession( + configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), + delegate: URLSessionDelegate(), + delegateQueue: nil + )) + + public static func sendRequest(request: T, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { + return sharedSession.sendRequest(request, handler: handler) + } + + public static func cancelRequest(requestType: T.Type, passingTest test: T -> Bool = { r in true }) { + sharedSession.cancelRequest(requestType, passingTest: test) + } } @available(*, deprecated, message="API is renamed as Session.") public typealias API = Session +extension Session { + @available(*, unavailable, message="Use separated Session instance instead.") + public static func sendRequest(request: T, URLSession: NSURLSession, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { + abort() + } + + @available(*, unavailable, message="Use separated Session instance instead.") + public static func cancelRequest(requestType: T.Type, URLSession: NSURLSession, passingTest test: T -> Bool = { r in true }) { + abort() + } +} + // MARK: - default implementation of URLSessionDelegate public class URLSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { // MARK: NSURLSessionTaskDelegate @@ -169,4 +188,4 @@ private extension NSURLSessionDownloadTask { } } } -} +} \ No newline at end of file From ac9b33470a6afa524e89bf42b1ad31663aa06cba Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 18 Oct 2015 01:37:33 +0900 Subject: [PATCH 4/7] Update README and Demo --- Demo.playground/Contents.swift | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Demo.playground/Contents.swift b/Demo.playground/Contents.swift index 52905dc0..19f68550 100644 --- a/Demo.playground/Contents.swift +++ b/Demo.playground/Contents.swift @@ -63,7 +63,7 @@ struct GetRateLimitRequest: GitHubRequestType { //: Step 4: Send request let request = GetRateLimitRequest() -API.sendRequest(request) { result in +Session.sendRequest(request) { result in switch result { case .Success(let rateLimit): debugPrint("count: \(rateLimit.count)") diff --git a/README.md b/README.md index 8ebd073c..c074d48a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ APIKit is a library for building type-safe web API client in Swift. ```swift let request = GetSearchRepositoriesRequest(query: "APIKit", sort: .Stars) -API.sendRequest(request) { result in +Session.sendRequest(request) { result in switch result { case .Success(let response): self.repositories = response // inferred as [Repository] @@ -117,7 +117,7 @@ struct RateLimit { ```swift let request = GetRateLimitRequest() -API.sendRequest(request) { result in +Session.sendRequest(request) { result in switch result { case .Success(let rateLimit): print("count: \(rateLimit.count)") @@ -276,7 +276,7 @@ extension GitHubRequest { ```swift let request = GetSomePaginatedRequest(page: 1) -API.sendRequest(request) { result in +Session.sendRequest(request) { result in switch result { case .Success(let response): print("results: \(response.results)") From d7c73ef4b6c0f0f25929bc8d955cb3e7b510fccb Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 19 Oct 2015 22:33:19 +0900 Subject: [PATCH 5/7] Oh my trailing LF! --- APIKit/Session.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIKit/Session.swift b/APIKit/Session.swift index 46978c2a..d770b3b1 100644 --- a/APIKit/Session.swift +++ b/APIKit/Session.swift @@ -188,4 +188,4 @@ private extension NSURLSessionDownloadTask { } } } -} \ No newline at end of file +} From 8a3c5c6470dcff772d3201bc5918d49d4fd5ad30 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 19 Oct 2015 22:34:18 +0900 Subject: [PATCH 6/7] Make API unavailable for clarifying --- APIKit/Session.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIKit/Session.swift b/APIKit/Session.swift index d770b3b1..9f238fee 100644 --- a/APIKit/Session.swift +++ b/APIKit/Session.swift @@ -85,7 +85,7 @@ public class Session { } } -@available(*, deprecated, message="API is renamed as Session.") +@available(*, unavailable, message="API is renamed as Session.") public typealias API = Session extension Session { From 9cdc680591c2f76abe19141b34b520a54401cb57 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 19 Oct 2015 23:27:31 +0900 Subject: [PATCH 7/7] Remove ReuqestTests --- APIKitTests/RequestTests.swift | 72 ---------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 APIKitTests/RequestTests.swift diff --git a/APIKitTests/RequestTests.swift b/APIKitTests/RequestTests.swift deleted file mode 100644 index 3e3cbad5..00000000 --- a/APIKitTests/RequestTests.swift +++ /dev/null @@ -1,72 +0,0 @@ -import XCTest -import OHHTTPStubs -import APIKit - -class RequestTests: XCTestCase { - struct SearchRequest: MockSessionRequestType { - let query: String - - // MARK: RequestType - typealias Response = [String: AnyObject] - - var method: HTTPMethod { - return .GET - } - - var path: String { - return "/" - } - - var parameters: [String: AnyObject] { - return [ - "q": query, - "dummy": NSNull() - ] - } - - func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Response? { - return object as? [String: AnyObject] - } - } - - override func tearDown() { - OHHTTPStubs.removeAllStubs() - super.tearDown() - } - - func testJapanesesURLQueryParameterEncoding() { - OHHTTPStubs.stubRequestsPassingTest({ request in - XCTAssert(request.URL?.query == "q=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF&dummy") - return true - }, withStubResponse: { request in - return OHHTTPStubsResponse(data: NSData(), statusCode: 200, headers: nil) - }) - - let request = SearchRequest(query: "こんにちは") - let expectation = expectationWithDescription("waiting for the response.") - - Session.sendRequest(request) { result in - expectation.fulfill() - } - - waitForExpectationsWithTimeout(1.0, handler: nil) - } - - func testSymbolURLQueryParameterEncoding() { - OHHTTPStubs.stubRequestsPassingTest({ request in - XCTAssert(request.URL?.query == "q=%21%22%23%24%25%26%27%28%290%3D~%7C%60%7B%7D%2A%2B%3C%3E%3F_&dummy") - return true - }, withStubResponse: { request in - return OHHTTPStubsResponse(data: NSData(), statusCode: 200, headers: nil) - }) - - let request = SearchRequest(query: "!\"#$%&'()0=~|`{}*+<>?_") - let expectation = expectationWithDescription("waiting for the response.") - - Session.sendRequest(request) { result in - expectation.fulfill() - } - - waitForExpectationsWithTimeout(1.0, handler: nil) - } -}