From b83635f9b037fd6dffa1e99b6492cda6e227371c Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Tue, 24 Feb 2015 00:11:15 +0900 Subject: [PATCH 01/36] create instance pair of API and NSURLSession for each classes --- APIKit.xcodeproj/project.pbxproj | 6 ++++ APIKit/APIKit.swift | 45 +++++++++++++++++++++++++----- APIKitTests/APIInstanceTests.swift | 34 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 APIKitTests/APIInstanceTests.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 8e828704..dfee62e1 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -38,6 +38,8 @@ 7FEC5A1A1A96FE2600B1D3C0 /* APIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FCDD1A94D02C006863BB /* APIKit.framework */; }; 7FEC5A211A96FFD300B1D3C0 /* LlamaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; }; 7FEC5A231A97001D00B1D3C0 /* LlamaKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 7FED27F11A9B702A0091C900 /* APIInstanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */; }; + 7FED27F21A9B70320091C900 /* APIInstanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */; }; CDB0CCDB1A9B2ECD00BADAC5 /* Assertions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */; }; CDB0CCDC1A9B2ED600BADAC5 /* Assertions.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CDB0CCDE1A9B2F6100BADAC5 /* Assertions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDB0CCDD1A9B2F6100BADAC5 /* Assertions.framework */; }; @@ -130,6 +132,7 @@ 7FEC5A141A96FE2600B1D3C0 /* APIKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = APIKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7FEC5A171A96FE2600B1D3C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseBodyParserTests.swift; sourceTree = ""; }; + 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIInstanceTests.swift; sourceTree = ""; }; CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Assertions.framework; sourceTree = ""; }; CDB0CCDD1A9B2F6100BADAC5 /* Assertions.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Assertions.framework; sourceTree = ""; }; /* End PBXFileReference section */ @@ -290,6 +293,7 @@ children = ( 7F30A8551A975BD600A8C136 /* RequestBodyBuilderTests.swift */, 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */, + 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */, 7FEC5A161A96FE2600B1D3C0 /* Supporting Files */, ); path = APIKitTests; @@ -511,6 +515,7 @@ buildActionMask = 2147483647; files = ( 7F0869A01A9787AF001AD3E1 /* RequestBodyBuilderTests.swift in Sources */, + 7FED27F21A9B70320091C900 /* APIInstanceTests.swift in Sources */, 7F0869A11A9787AF001AD3E1 /* ResponseBodyParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -553,6 +558,7 @@ buildActionMask = 2147483647; files = ( 7FEC5A191A96FE2600B1D3C0 /* ResponseBodyParserTests.swift in Sources */, + 7FED27F11A9B702A0091C900 /* APIInstanceTests.swift in Sources */, 7F30A8561A975BD600A8C136 /* RequestBodyBuilderTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index fad842e8..cc5babbc 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -26,16 +26,12 @@ public enum Method: String { case CONNECT = "CONNECT" } -public class API { +public class API: NSObject, NSURLSessionDelegate { // configurations public class func baseURL() -> NSURL { return NSURL() } - - public class func URLSession() -> NSURLSession { - return NSURLSession.sharedSession() - } - + public class func requestBodyBuilder() -> RequestBodyBuilder { return .JSON(writingOptions: nil) } @@ -43,6 +39,41 @@ public class API { public class func responseBodyParser() -> ResponseBodyParser { return .JSON(readingOptions: nil) } + + public class func URLSessionConfiguration() -> NSURLSessionConfiguration { + return NSURLSessionConfiguration.defaultSessionConfiguration() + } + + // prevent instantiation + override private init() { + super.init() + } + + // create session and instance of API for each subclasses + private final class var instancePair: (API, NSURLSession) { + struct Singleton { + static var dictionary = [String: (API, NSURLSession)]() + } + + let className = NSStringFromClass(self) + var pair: (API, NSURLSession) = Singleton.dictionary[className] ?? { + let instance = (self as NSObject.Type)() as API + let configuration = self.URLSessionConfiguration() + let session = NSURLSession(configuration: configuration, delegate: instance, delegateQueue: nil) + Singleton.dictionary[className] = (instance, session) + return (instance, session) + }() + + return pair + } + + public final class var instance: API { + return instancePair.0 + } + + public final class var URLSession: NSURLSession { + return instancePair.1 + } // build NSURLRequest public class func URLRequest(method: Method, _ path: String, _ parameters: [String: AnyObject] = [:]) -> NSURLRequest? { @@ -77,7 +108,7 @@ public class API { // send request and build response object public class func sendRequest(request: T, handler: (Result) -> Void = {r in}) -> NSURLSessionDataTask? { - let session = URLSession() + let session = URLSession let mainQueue = dispatch_get_main_queue() if let URLRequest = request.URLRequest { diff --git a/APIKitTests/APIInstanceTests.swift b/APIKitTests/APIInstanceTests.swift new file mode 100644 index 00000000..2728ef48 --- /dev/null +++ b/APIKitTests/APIInstanceTests.swift @@ -0,0 +1,34 @@ +import UIKit +import Assertions +import APIKit +import XCTest + +class APIInstanceTests: XCTestCase { + class Foo: API { + // NOTE: these are required to avoid segmentation fault of compliler (Swift 1.1) + override class func requestBodyBuilder() -> RequestBodyBuilder { + return .JSON(writingOptions: nil) + } + + override class func responseBodyParser() -> ResponseBodyParser { + return .JSON(readingOptions: nil) + } + } + + class Bar: API { + } + + func testDifferentSessionsAreCreatedForEachClasses() { + assert(Foo.URLSession, !=, Bar.URLSession) + } + + func testSameSessionsAreUsedInSameClasses() { + assertEqual(Foo.URLSession, Foo.URLSession) + assertEqual(Bar.URLSession, Bar.URLSession) + } + + func testDelegateOfSessions() { + assert(Foo.URLSession.delegate, { $0 is Foo }) + assert(Bar.URLSession.delegate, { $0 is Bar }) + } +} From f9f5b4151415c53d70590db2769640308a7248ec Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Tue, 24 Feb 2015 00:15:04 +0900 Subject: [PATCH 02/36] oops --- APIKitTests/APIInstanceTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIKitTests/APIInstanceTests.swift b/APIKitTests/APIInstanceTests.swift index 2728ef48..fbdcaa22 100644 --- a/APIKitTests/APIInstanceTests.swift +++ b/APIKitTests/APIInstanceTests.swift @@ -1,4 +1,4 @@ -import UIKit +import Foundation import Assertions import APIKit import XCTest From b58db6e6fad82a21151ffd5290db3219a3d91133 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Thu, 26 Feb 2015 21:15:30 +0900 Subject: [PATCH 03/36] fix coding style --- APIKit/APIKit.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index cc5babbc..aefe9aac 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -26,6 +26,9 @@ public enum Method: String { case CONNECT = "CONNECT" } +// use private, global scope variable until we can use stored class var in Swift 1.2 +private var instancePairDictionary = [String: (API, NSURLSession)]() + public class API: NSObject, NSURLSessionDelegate { // configurations public class func baseURL() -> NSURL { @@ -51,17 +54,14 @@ public class API: NSObject, NSURLSessionDelegate { // create session and instance of API for each subclasses private final class var instancePair: (API, NSURLSession) { - struct Singleton { - static var dictionary = [String: (API, NSURLSession)]() - } - let className = NSStringFromClass(self) - var pair: (API, NSURLSession) = Singleton.dictionary[className] ?? { - let instance = (self as NSObject.Type)() as API + let pair: (API, NSURLSession) = instancePairDictionary[className] ?? { + let instance = self.init() let configuration = self.URLSessionConfiguration() let session = NSURLSession(configuration: configuration, delegate: instance, delegateQueue: nil) - Singleton.dictionary[className] = (instance, session) - return (instance, session) + let pair = (instance, session) + instancePairDictionary[className] = pair + return pair }() return pair From 3dec41cb78ea2e78b534147bab15e2a2565c3321 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Thu, 26 Feb 2015 21:31:40 +0900 Subject: [PATCH 04/36] change repository of Assertions --- Cartfile.private | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.private b/Cartfile.private index 5d4accd4..9d8994b1 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1 +1 @@ -github "neilpa/Assertions" "swift-1.1" +github "ikesyo/Assertions" "swift-1.1" diff --git a/Cartfile.resolved b/Cartfile.resolved index 748040fa..cd76c812 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "neilpa/Assertions" "15e61d79e8858b811d2e061a1bf51e19f533f699" +github "ikesyo/Assertions" "45c8a5c09b4071406aefb1b23e0be9ae3f8aa94c" github "LlamaKit/LlamaKit" "v0.5.0" From d27b06082a8dd08cc750a507913ffcf441f62fb0 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Thu, 26 Feb 2015 22:03:58 +0900 Subject: [PATCH 05/36] assert overriding baseURL() instead of returning empty URL in abstract class --- APIKit/APIKit.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index aefe9aac..04f51e63 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -32,7 +32,7 @@ private var instancePairDictionary = [String: (API, NSURLSession)]() public class API: NSObject, NSURLSessionDelegate { // configurations public class func baseURL() -> NSURL { - return NSURL() + fatalError("API.baseURL() must be overrided in subclasses.") } public class func requestBodyBuilder() -> RequestBodyBuilder { From d355bd274a8f9737e3e96efd01c5e42c148b5f81 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Thu, 26 Feb 2015 22:10:22 +0900 Subject: [PATCH 06/36] lock using semaphore while accessing instancePairDictionary --- APIKit/APIKit.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 04f51e63..9b7dc2e9 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -28,6 +28,7 @@ public enum Method: String { // use private, global scope variable until we can use stored class var in Swift 1.2 private var instancePairDictionary = [String: (API, NSURLSession)]() +private let instancePairSemaphore = dispatch_semaphore_create(1) public class API: NSObject, NSURLSessionDelegate { // configurations @@ -55,6 +56,8 @@ public class API: NSObject, NSURLSessionDelegate { // create session and instance of API for each subclasses private final class var instancePair: (API, NSURLSession) { let className = NSStringFromClass(self) + + dispatch_semaphore_wait(instancePairSemaphore, DISPATCH_TIME_FOREVER) let pair: (API, NSURLSession) = instancePairDictionary[className] ?? { let instance = self.init() let configuration = self.URLSessionConfiguration() @@ -63,6 +66,7 @@ public class API: NSObject, NSURLSessionDelegate { instancePairDictionary[className] = pair return pair }() + dispatch_semaphore_signal(instancePairSemaphore) return pair } From 1eb8a68817c0dba7d10af8ae088f570751192c95 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Thu, 26 Feb 2015 22:34:11 +0900 Subject: [PATCH 07/36] add URLSessionDelegateQueue() --- APIKit/APIKit.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 9b7dc2e9..5037d7f3 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -48,6 +48,12 @@ public class API: NSObject, NSURLSessionDelegate { return NSURLSessionConfiguration.defaultSessionConfiguration() } + public class func URLSessionDelegateQueue() -> NSOperationQueue? { + // nil indicates NSURLSession creates its own serial operation queue. + // see doc of NSURLSession.init(configuration:delegate:delegateQueue:) for more details. + return nil + } + // prevent instantiation override private init() { super.init() @@ -61,7 +67,8 @@ public class API: NSObject, NSURLSessionDelegate { let pair: (API, NSURLSession) = instancePairDictionary[className] ?? { let instance = self.init() let configuration = self.URLSessionConfiguration() - let session = NSURLSession(configuration: configuration, delegate: instance, delegateQueue: nil) + let queue = self.URLSessionDelegateQueue() + let session = NSURLSession(configuration: configuration, delegate: instance, delegateQueue: queue) let pair = (instance, session) instancePairDictionary[className] = pair return pair From 4bd1825fc2b228d0d53c07dfb5f1b879bbf0ed8c Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Fri, 27 Feb 2015 02:02:27 +0900 Subject: [PATCH 08/36] handle NSURLSession events by delegate methods --- APIKit/APIKit.swift | 56 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 5037d7f3..3d802319 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -26,11 +26,36 @@ public enum Method: String { case CONNECT = "CONNECT" } +private var dataTaskResponseBufferKey = 0 +private var dataTaskCompletionHandlerKey = 0 + +private extension NSURLSessionDataTask { + private var responseBuffer: NSMutableData { + if let responseBuffer = objc_getAssociatedObject(self, &dataTaskResponseBufferKey) as? NSMutableData { + return responseBuffer + } else { + let responseBuffer = NSMutableData() + objc_setAssociatedObject(self, &dataTaskResponseBufferKey, responseBuffer, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + return responseBuffer + } + } + + private var completionHandler: Box<(NSData!, NSURLResponse!, NSError!) -> Void>? { + get { + return objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData!, NSURLResponse!, NSError!) -> Void> + } + + set { + objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + } + } +} + // use private, global scope variable until we can use stored class var in Swift 1.2 private var instancePairDictionary = [String: (API, NSURLSession)]() private let instancePairSemaphore = dispatch_semaphore_create(1) -public class API: NSObject, NSURLSessionDelegate { +public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { // configurations public class func baseURL() -> NSURL { fatalError("API.baseURL() must be overrided in subclasses.") @@ -123,7 +148,9 @@ public class API: NSObject, NSURLSessionDelegate { let mainQueue = dispatch_get_main_queue() if let URLRequest = request.URLRequest { - let task = session.dataTaskWithRequest(URLRequest) { data, URLResponse, connectionError in + let task = session.dataTaskWithRequest(URLRequest) + + task.completionHandler = Box({ data, URLResponse, connectionError in if let error = connectionError { dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) return @@ -148,7 +175,7 @@ public class API: NSObject, NSURLSessionDelegate { } dispatch_async(mainQueue, { handler(mappedResponse) }) - } + }) task.resume() @@ -161,4 +188,27 @@ public class API: NSObject, NSURLSessionDelegate { return nil } } + + // MARK: NSURLSessionDelegate + public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) { + completionHandler(.PerformDefaultHandling, nil) + } + + // MARK: NSURLSessionTaskDelegate + public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) { + if let dataTask = task as? NSURLSessionDataTask { + if let completionHandler = dataTask.completionHandler?.unbox { + completionHandler(dataTask.responseBuffer, dataTask.response, connectionError) + } + } + } + + // MARK: NSURLSessionDataDelegate + public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) { + completionHandler(.Allow) + } + + public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { + dataTask.responseBuffer.appendData(data) + } } From 516aeb5ad4042842c711539837c46534680faa97 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Fri, 27 Feb 2015 02:35:35 +0900 Subject: [PATCH 09/36] fix creating instance --- APIKit/APIKit.swift | 2 +- APIKitTests/APIInstanceTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 3d802319..3780e5c9 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -90,7 +90,7 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { dispatch_semaphore_wait(instancePairSemaphore, DISPATCH_TIME_FOREVER) let pair: (API, NSURLSession) = instancePairDictionary[className] ?? { - let instance = self.init() + let instance = (self as NSObject.Type)() as API let configuration = self.URLSessionConfiguration() let queue = self.URLSessionDelegateQueue() let session = NSURLSession(configuration: configuration, delegate: instance, delegateQueue: queue) diff --git a/APIKitTests/APIInstanceTests.swift b/APIKitTests/APIInstanceTests.swift index fbdcaa22..08d437e6 100644 --- a/APIKitTests/APIInstanceTests.swift +++ b/APIKitTests/APIInstanceTests.swift @@ -28,7 +28,7 @@ class APIInstanceTests: XCTestCase { } func testDelegateOfSessions() { - assert(Foo.URLSession.delegate, { $0 is Foo }) - assert(Bar.URLSession.delegate, { $0 is Bar }) + assertNotNil(Foo.URLSession.delegate as? Foo) + assertNotNil(Bar.URLSession.delegate as? Bar) } } From c67694bfc41ffa418cd31a9ab9e3945c28e3aff9 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Fri, 27 Feb 2015 03:14:52 +0900 Subject: [PATCH 10/36] move box code into accessor --- APIKit/APIKit.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 3780e5c9..304f9e80 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -40,13 +40,13 @@ private extension NSURLSessionDataTask { } } - private var completionHandler: Box<(NSData!, NSURLResponse!, NSError!) -> Void>? { + private var completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)? { get { - return objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData!, NSURLResponse!, NSError!) -> Void> + return (objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData!, NSURLResponse!, NSError!) -> Void>)?.unbox } set { - objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, newValue, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, Box(newValue), UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) } } } @@ -150,7 +150,7 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { if let URLRequest = request.URLRequest { let task = session.dataTaskWithRequest(URLRequest) - task.completionHandler = Box({ data, URLResponse, connectionError in + task.completionHandler = { data, URLResponse, connectionError in if let error = connectionError { dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) return @@ -175,7 +175,7 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { } dispatch_async(mainQueue, { handler(mappedResponse) }) - }) + } task.resume() @@ -197,9 +197,7 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { // MARK: NSURLSessionTaskDelegate public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) { if let dataTask = task as? NSURLSessionDataTask { - if let completionHandler = dataTask.completionHandler?.unbox { - completionHandler(dataTask.responseBuffer, dataTask.response, connectionError) - } + dataTask.completionHandler?(dataTask.responseBuffer, dataTask.response, connectionError) } } From 9cf33f4cc6fa5e480721218391cfc6cfe491f32f Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sat, 28 Feb 2015 16:52:49 +0900 Subject: [PATCH 11/36] add comment about NS_REQUIRES_SUPER --- APIKit/APIKit.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 304f9e80..7215e145 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -189,23 +189,25 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { } } - // MARK: NSURLSessionDelegate + // MARK: - NSURLSessionDelegate public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) { completionHandler(.PerformDefaultHandling, nil) } - // MARK: NSURLSessionTaskDelegate + // MARK: - NSURLSessionTaskDelegate + // TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift. 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 + // MARK: - NSURLSessionDataDelegate public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) { completionHandler(.Allow) } + // TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift. public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { dataTask.responseBuffer.appendData(data) } From 7412eeff1dcfadc6592ce793725a16aadca38281 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 13:04:20 +0900 Subject: [PATCH 12/36] fix setting completion handler --- APIKit/APIKit.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 7215e145..99624cc0 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -46,7 +46,11 @@ private extension NSURLSessionDataTask { } set { - objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, Box(newValue), UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + if let value = newValue { + objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, Box(value), UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + } else { + objc_setAssociatedObject(self, &dataTaskCompletionHandlerKey, nil, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + } } } } From cbcef9b1ae148456696be15e33baacb14bc10edf Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 13:15:04 +0900 Subject: [PATCH 13/36] add OHHTTPStubs to Cartfile.private --- .gitmodules | 6 +++++- APIKit.xcodeproj/project.pbxproj | 12 ++++++++++++ Cartfile.private | 1 + Cartfile.resolved | 3 ++- Carthage/Checkouts/Assertions | 2 +- Carthage/Checkouts/OHHTTPStubs | 1 + 6 files changed, 22 insertions(+), 3 deletions(-) create mode 160000 Carthage/Checkouts/OHHTTPStubs diff --git a/.gitmodules b/.gitmodules index 0f56426e..6ed82fb5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,4 +2,8 @@ path = Carthage/Checkouts/LlamaKit url = https://github.com/LlamaKit/LlamaKit.git [submodule "Carthage/Checkouts/Assertions"] - url = https://github.com/neilpa/Assertions.git + path = Carthage/Checkouts/Assertions + url = https://github.com/ikesyo/Assertions.git +[submodule "Carthage/Checkouts/OHHTTPStubs"] + path = Carthage/Checkouts/OHHTTPStubs + url = https://github.com/AliSoftware/OHHTTPStubs.git diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index dfee62e1..62c80c19 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -30,6 +30,9 @@ 7F45FD711A94DA2B006863BB /* LlamaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; }; 7F45FD721A94DA2B006863BB /* LlamaKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7F45FD741A94E832006863BB /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD731A94E832006863BB /* Models.swift */; }; + 7FAC25A01AA2C03400E92500 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FAC259E1AA2C01100E92500 /* OHHTTPStubs.framework */; }; + 7FAC25A11AA2C04000E92500 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FAC259C1AA2C00600E92500 /* OHHTTPStubs.framework */; }; + 7FAC25A21AA2C1D500E92500 /* OHHTTPStubs.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FAC259C1AA2C00600E92500 /* OHHTTPStubs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7FCBE9DD1A9734880075AFD9 /* RequestBodyBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCBE9DC1A9734880075AFD9 /* RequestBodyBuilder.swift */; }; 7FCBE9DE1A9734880075AFD9 /* RequestBodyBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCBE9DC1A9734880075AFD9 /* RequestBodyBuilder.swift */; }; 7FCBE9E01A9734950075AFD9 /* ResponseBodyParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCBE9DF1A9734950075AFD9 /* ResponseBodyParser.swift */; }; @@ -102,6 +105,7 @@ files = ( CDB0CCDC1A9B2ED600BADAC5 /* Assertions.framework in CopyFiles */, 7FEC5A231A97001D00B1D3C0 /* LlamaKit.framework in CopyFiles */, + 7FAC25A21AA2C1D500E92500 /* OHHTTPStubs.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -127,6 +131,8 @@ 7F45FD561A94D9A9006863BB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 7F45FD6A1A94D9F9006863BB /* GitHub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHub.swift; sourceTree = ""; }; 7F45FD731A94E832006863BB /* Models.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + 7FAC259C1AA2C00600E92500 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OHHTTPStubs.framework; sourceTree = ""; }; + 7FAC259E1AA2C01100E92500 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OHHTTPStubs.framework; sourceTree = ""; }; 7FCBE9DC1A9734880075AFD9 /* RequestBodyBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestBodyBuilder.swift; sourceTree = ""; }; 7FCBE9DF1A9734950075AFD9 /* ResponseBodyParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseBodyParser.swift; sourceTree = ""; }; 7FEC5A141A96FE2600B1D3C0 /* APIKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = APIKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -143,6 +149,7 @@ buildActionMask = 2147483647; files = ( 7F08699A1A978790001AD3E1 /* APIKit.framework in Frameworks */, + 7FAC25A01AA2C03400E92500 /* OHHTTPStubs.framework in Frameworks */, CDB0CCDE1A9B2F6100BADAC5 /* Assertions.framework in Frameworks */, 7F0869A31A9787E1001AD3E1 /* LlamaKit.framework in Frameworks */, ); @@ -178,6 +185,7 @@ buildActionMask = 2147483647; files = ( 7FEC5A1A1A96FE2600B1D3C0 /* APIKit.framework in Frameworks */, + 7FAC25A11AA2C04000E92500 /* OHHTTPStubs.framework in Frameworks */, CDB0CCDB1A9B2ECD00BADAC5 /* Assertions.framework in Frameworks */, 7FEC5A211A96FFD300B1D3C0 /* LlamaKit.framework in Frameworks */, ); @@ -250,6 +258,7 @@ 7F45FD1B1A94D1B4006863BB /* iOS */ = { isa = PBXGroup; children = ( + 7FAC259C1AA2C00600E92500 /* OHHTTPStubs.framework */, CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */, 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */, ); @@ -259,6 +268,7 @@ 7F45FD1D1A94D1B4006863BB /* Mac */ = { isa = PBXGroup; children = ( + 7FAC259E1AA2C01100E92500 /* OHHTTPStubs.framework */, CDB0CCDD1A9B2F6100BADAC5 /* Assertions.framework */, 7F45FD1E1A94D1B4006863BB /* LlamaKit.framework */, ); @@ -740,6 +750,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = APIKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -762,6 +773,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = APIKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/Cartfile.private b/Cartfile.private index 9d8994b1..2ec37b2a 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1 +1,2 @@ github "ikesyo/Assertions" "swift-1.1" +github "AliSoftware/OHHTTPStubs" "master" diff --git a/Cartfile.resolved b/Cartfile.resolved index cd76c812..20b55e53 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,3 @@ -github "ikesyo/Assertions" "45c8a5c09b4071406aefb1b23e0be9ae3f8aa94c" +github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" +github "AliSoftware/OHHTTPStubs" "02cc9fcc0cdfca79e1968df9e3e072c256e5e593" diff --git a/Carthage/Checkouts/Assertions b/Carthage/Checkouts/Assertions index 15e61d79..fec437ce 160000 --- a/Carthage/Checkouts/Assertions +++ b/Carthage/Checkouts/Assertions @@ -1 +1 @@ -Subproject commit 15e61d79e8858b811d2e061a1bf51e19f533f699 +Subproject commit fec437ce6857259ac8bda3eb255c5782aa798734 diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs new file mode 160000 index 00000000..02cc9fcc --- /dev/null +++ b/Carthage/Checkouts/OHHTTPStubs @@ -0,0 +1 @@ +Subproject commit 02cc9fcc0cdfca79e1968df9e3e072c256e5e593 From 3e9783472c5e22cba1b71e90582755339cd9e719 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 14:24:09 +0900 Subject: [PATCH 14/36] add tests for API --- APIKit.xcodeproj/project.pbxproj | 6 ++ APIKitTests/APITests.swift | 146 +++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 APIKitTests/APITests.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 62c80c19..777f1aed 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 7F0869A61A978BCA001AD3E1 /* URLEncodedSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0869A51A978BCA001AD3E1 /* URLEncodedSerialization.swift */; }; 7F0869A71A978BCA001AD3E1 /* URLEncodedSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0869A51A978BCA001AD3E1 /* URLEncodedSerialization.swift */; }; 7F0869A81A979088001AD3E1 /* APIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD171A94D085006863BB /* APIKit.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 /* APIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F45FD171A94D085006863BB /* APIKit.swift */; }; 7F45FD421A94D1CC006863BB /* LlamaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; }; @@ -114,6 +116,7 @@ /* Begin PBXFileReference section */ 7F0869941A978790001AD3E1 /* APIKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = APIKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7F0869A51A978BCA001AD3E1 /* URLEncodedSerialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLEncodedSerialization.swift; sourceTree = ""; }; + 7F1B190A1AA2CA1300C7AFCF /* APITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APITests.swift; sourceTree = ""; }; 7F30A8551A975BD600A8C136 /* RequestBodyBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestBodyBuilderTests.swift; sourceTree = ""; }; 7F45FCDD1A94D02C006863BB /* APIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = APIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7F45FCE11A94D02C006863BB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -304,6 +307,7 @@ 7F30A8551A975BD600A8C136 /* RequestBodyBuilderTests.swift */, 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */, 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */, + 7F1B190A1AA2CA1300C7AFCF /* APITests.swift */, 7FEC5A161A96FE2600B1D3C0 /* Supporting Files */, ); path = APIKitTests; @@ -524,6 +528,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7F1B190C1AA2CA1300C7AFCF /* APITests.swift in Sources */, 7F0869A01A9787AF001AD3E1 /* RequestBodyBuilderTests.swift in Sources */, 7FED27F21A9B70320091C900 /* APIInstanceTests.swift in Sources */, 7F0869A11A9787AF001AD3E1 /* ResponseBodyParserTests.swift in Sources */, @@ -567,6 +572,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7F1B190B1AA2CA1300C7AFCF /* APITests.swift in Sources */, 7FEC5A191A96FE2600B1D3C0 /* ResponseBodyParserTests.swift in Sources */, 7FED27F11A9B702A0091C900 /* APIInstanceTests.swift in Sources */, 7F30A8561A975BD600A8C136 /* RequestBodyBuilderTests.swift in Sources */, diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift new file mode 100644 index 00000000..4d0c57c1 --- /dev/null +++ b/APIKitTests/APITests.swift @@ -0,0 +1,146 @@ +import Foundation +import APIKit +import XCTest +import Assertions +import OHHTTPStubs + +class APITests: XCTestCase { + class Foo: API { + override class func requestBodyBuilder() -> RequestBodyBuilder { + return .JSON(writingOptions: nil) + } + + override class func responseBodyParser() -> ResponseBodyParser { + return .JSON(readingOptions: nil) + } + + class Endpoint { + class Get: Request { + typealias Response = [String: AnyObject] + + var URLRequest: NSURLRequest? { + return NSURLRequest() + } + + func responseFromObject(object: AnyObject) -> Response? { + return object as? [String: AnyObject] + } + } + } + } + + override func tearDown() { + OHHTTPStubs.removeAllStubs() + super.tearDown() + } + + func testSuccess() { + let dictionary = ["key": "value"] + let data = NSJSONSerialization.dataWithJSONObject(dictionary, options: nil, error: nil)! + + OHHTTPStubs.stubRequestsPassingTest({ request in + return true + }, withStubResponse: { request in + return OHHTTPStubsResponse(data: data, statusCode: 200, headers: nil) + }) + + let expectation = expectationWithDescription("wait for response") + let request = Foo.Endpoint.Get() + + Foo.sendRequest(request) { response in + switch response { + case .Success(let box): + assert(box.unbox, ==, dictionary) + + case .Failure: + XCTFail() + } + + expectation.fulfill() + } + + waitForExpectationsWithTimeout(1.0, handler: nil) + } + + func testFailureOfConnection() { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil) + + OHHTTPStubs.stubRequestsPassingTest({ request in + return true + }, withStubResponse: { request in + return OHHTTPStubsResponse(error: error) + }) + + let expectation = expectationWithDescription("wait for response") + let request = Foo.Endpoint.Get() + + Foo.sendRequest(request) { response in + switch response { + case .Success: + XCTFail() + + case .Failure(let box): + assert(box.unbox, ==, error) + } + + expectation.fulfill() + } + + waitForExpectationsWithTimeout(1.0, handler: nil) + } + + func testFailureOfResponseStatusCode() { + OHHTTPStubs.stubRequestsPassingTest({ request in + return true + }, withStubResponse: { request in + return OHHTTPStubsResponse(data: NSData(), statusCode: 400, headers: nil) + }) + + let expectation = expectationWithDescription("wait for response") + let request = Foo.Endpoint.Get() + + Foo.sendRequest(request) { response in + switch response { + case .Success: + XCTFail() + + case .Failure(let box): + assert(box.unbox.domain, ==, APIKitErrorDomain) + assertEqual(box.unbox.code, 400) + } + + expectation.fulfill() + } + + waitForExpectationsWithTimeout(1.0, handler: nil) + } + + func testFailureOfDecodingResponseBody() { + let data = "{\"broken\": \"json}".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + + OHHTTPStubs.stubRequestsPassingTest({ request in + return true + }, withStubResponse: { request in + return OHHTTPStubsResponse(data: data, statusCode: 200, headers: nil) + }) + + let expectation = expectationWithDescription("wait for response") + let request = Foo.Endpoint.Get() + + Foo.sendRequest(request) { response in + switch response { + case .Success: + XCTFail() + + case .Failure(let box): + let error = box.unbox + assert(error.domain, ==, NSCocoaErrorDomain) + assertEqual(error.code, 3840) + } + + expectation.fulfill() + } + + waitForExpectationsWithTimeout(1.0, handler: nil) + } +} From c09cf6e0d97940ef1f43c9e2ff9afe776b604aa1 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 14:26:59 +0900 Subject: [PATCH 15/36] fix name of mock API class --- APIKitTests/APITests.swift | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift index 4d0c57c1..584340e3 100644 --- a/APIKitTests/APITests.swift +++ b/APIKitTests/APITests.swift @@ -5,7 +5,11 @@ import Assertions import OHHTTPStubs class APITests: XCTestCase { - class Foo: API { + class MockAPI: API { + override class func baseURL() -> NSURL { + return NSURL(string: "https://api.github.com")! + } + override class func requestBodyBuilder() -> RequestBodyBuilder { return .JSON(writingOptions: nil) } @@ -19,7 +23,7 @@ class APITests: XCTestCase { typealias Response = [String: AnyObject] var URLRequest: NSURLRequest? { - return NSURLRequest() + return MockAPI.URLRequest(.GET, "/") } func responseFromObject(object: AnyObject) -> Response? { @@ -45,9 +49,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = Foo.Endpoint.Get() + let request = MockAPI.Endpoint.Get() - Foo.sendRequest(request) { response in + MockAPI.sendRequest(request) { response in switch response { case .Success(let box): assert(box.unbox, ==, dictionary) @@ -72,9 +76,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = Foo.Endpoint.Get() + let request = MockAPI.Endpoint.Get() - Foo.sendRequest(request) { response in + MockAPI.sendRequest(request) { response in switch response { case .Success: XCTFail() @@ -97,9 +101,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = Foo.Endpoint.Get() + let request = MockAPI.Endpoint.Get() - Foo.sendRequest(request) { response in + MockAPI.sendRequest(request) { response in switch response { case .Success: XCTFail() @@ -125,9 +129,9 @@ class APITests: XCTestCase { }) let expectation = expectationWithDescription("wait for response") - let request = Foo.Endpoint.Get() + let request = MockAPI.Endpoint.Get() - Foo.sendRequest(request) { response in + MockAPI.sendRequest(request) { response in switch response { case .Success: XCTFail() From a29e33b5060eccb9007f577cea0fc6047a0609ac Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 14:30:06 +0900 Subject: [PATCH 16/36] move APIInstanceTests to APITests --- APIKit.xcodeproj/project.pbxproj | 8 +------ APIKitTests/APIInstanceTests.swift | 34 ------------------------------ APIKitTests/APITests.swift | 19 +++++++++++++++++ 3 files changed, 20 insertions(+), 41 deletions(-) delete mode 100644 APIKitTests/APIInstanceTests.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 777f1aed..f4b56a7c 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -43,8 +43,6 @@ 7FEC5A1A1A96FE2600B1D3C0 /* APIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FCDD1A94D02C006863BB /* APIKit.framework */; }; 7FEC5A211A96FFD300B1D3C0 /* LlamaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; }; 7FEC5A231A97001D00B1D3C0 /* LlamaKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 7FED27F11A9B702A0091C900 /* APIInstanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */; }; - 7FED27F21A9B70320091C900 /* APIInstanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */; }; CDB0CCDB1A9B2ECD00BADAC5 /* Assertions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */; }; CDB0CCDC1A9B2ED600BADAC5 /* Assertions.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CDB0CCDE1A9B2F6100BADAC5 /* Assertions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDB0CCDD1A9B2F6100BADAC5 /* Assertions.framework */; }; @@ -141,7 +139,6 @@ 7FEC5A141A96FE2600B1D3C0 /* APIKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = APIKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7FEC5A171A96FE2600B1D3C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseBodyParserTests.swift; sourceTree = ""; }; - 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIInstanceTests.swift; sourceTree = ""; }; CDB0CCDA1A9B2ECD00BADAC5 /* Assertions.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Assertions.framework; sourceTree = ""; }; CDB0CCDD1A9B2F6100BADAC5 /* Assertions.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Assertions.framework; sourceTree = ""; }; /* End PBXFileReference section */ @@ -304,10 +301,9 @@ 7FEC5A151A96FE2600B1D3C0 /* APIKitTests */ = { isa = PBXGroup; children = ( + 7F1B190A1AA2CA1300C7AFCF /* APITests.swift */, 7F30A8551A975BD600A8C136 /* RequestBodyBuilderTests.swift */, 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */, - 7FED27F01A9B702A0091C900 /* APIInstanceTests.swift */, - 7F1B190A1AA2CA1300C7AFCF /* APITests.swift */, 7FEC5A161A96FE2600B1D3C0 /* Supporting Files */, ); path = APIKitTests; @@ -530,7 +526,6 @@ files = ( 7F1B190C1AA2CA1300C7AFCF /* APITests.swift in Sources */, 7F0869A01A9787AF001AD3E1 /* RequestBodyBuilderTests.swift in Sources */, - 7FED27F21A9B70320091C900 /* APIInstanceTests.swift in Sources */, 7F0869A11A9787AF001AD3E1 /* ResponseBodyParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -574,7 +569,6 @@ files = ( 7F1B190B1AA2CA1300C7AFCF /* APITests.swift in Sources */, 7FEC5A191A96FE2600B1D3C0 /* ResponseBodyParserTests.swift in Sources */, - 7FED27F11A9B702A0091C900 /* APIInstanceTests.swift in Sources */, 7F30A8561A975BD600A8C136 /* RequestBodyBuilderTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/APIKitTests/APIInstanceTests.swift b/APIKitTests/APIInstanceTests.swift deleted file mode 100644 index 08d437e6..00000000 --- a/APIKitTests/APIInstanceTests.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import Assertions -import APIKit -import XCTest - -class APIInstanceTests: XCTestCase { - class Foo: API { - // NOTE: these are required to avoid segmentation fault of compliler (Swift 1.1) - override class func requestBodyBuilder() -> RequestBodyBuilder { - return .JSON(writingOptions: nil) - } - - override class func responseBodyParser() -> ResponseBodyParser { - return .JSON(readingOptions: nil) - } - } - - class Bar: API { - } - - func testDifferentSessionsAreCreatedForEachClasses() { - assert(Foo.URLSession, !=, Bar.URLSession) - } - - func testSameSessionsAreUsedInSameClasses() { - assertEqual(Foo.URLSession, Foo.URLSession) - assertEqual(Bar.URLSession, Bar.URLSession) - } - - func testDelegateOfSessions() { - assertNotNil(Foo.URLSession.delegate as? Foo) - assertNotNil(Bar.URLSession.delegate as? Bar) - } -} diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift index 584340e3..af50ae5b 100644 --- a/APIKitTests/APITests.swift +++ b/APIKitTests/APITests.swift @@ -33,11 +33,30 @@ class APITests: XCTestCase { } } + class AnotherMockAPI: API { + } + override func tearDown() { OHHTTPStubs.removeAllStubs() super.tearDown() } + // MARK: - instance tests + func testDifferentSessionsAreCreatedForEachClasses() { + assert(MockAPI.URLSession, !=, AnotherMockAPI.URLSession) + } + + func testSameSessionsAreUsedInSameClasses() { + assertEqual(MockAPI.URLSession, MockAPI.URLSession) + assertEqual(AnotherMockAPI.URLSession, AnotherMockAPI.URLSession) + } + + func testDelegateOfSessions() { + assertNotNil(MockAPI.URLSession.delegate as? MockAPI) + assertNotNil(AnotherMockAPI.URLSession.delegate as? AnotherMockAPI) + } + + // MARK: - integration tests func testSuccess() { let dictionary = ["key": "value"] let data = NSJSONSerialization.dataWithJSONObject(dictionary, options: nil, error: nil)! From c2b4754b438b8c5e137f8587aa4f1e21118bee97 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 15:54:28 +0900 Subject: [PATCH 17/36] fix dependencies --- .gitmodules | 2 +- Cartfile.private | 2 +- Cartfile.resolved | 2 +- Carthage/Checkouts/OHHTTPStubs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 6ed82fb5..b623bd0e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,4 +6,4 @@ url = https://github.com/ikesyo/Assertions.git [submodule "Carthage/Checkouts/OHHTTPStubs"] path = Carthage/Checkouts/OHHTTPStubs - url = https://github.com/AliSoftware/OHHTTPStubs.git + url = https://github.com/ishkawa/OHHTTPStubs.git diff --git a/Cartfile.private b/Cartfile.private index 2ec37b2a..05269001 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,2 +1,2 @@ github "ikesyo/Assertions" "swift-1.1" -github "AliSoftware/OHHTTPStubs" "master" +github "ishkawa/OHHTTPStubs" "master" diff --git a/Cartfile.resolved b/Cartfile.resolved index 20b55e53..5c07292a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" -github "AliSoftware/OHHTTPStubs" "02cc9fcc0cdfca79e1968df9e3e072c256e5e593" +github "ishkawa/OHHTTPStubs" "28bc5ec46b92467871b5b2bef4c2bac8b3cdf897" diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs index 02cc9fcc..28bc5ec4 160000 --- a/Carthage/Checkouts/OHHTTPStubs +++ b/Carthage/Checkouts/OHHTTPStubs @@ -1 +1 @@ -Subproject commit 02cc9fcc0cdfca79e1968df9e3e072c256e5e593 +Subproject commit 28bc5ec46b92467871b5b2bef4c2bac8b3cdf897 From dbc1e875460851c052512440c131e1e3a9745d4a Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 16:17:35 +0900 Subject: [PATCH 18/36] fix dependencies --- Cartfile.resolved | 2 +- Carthage/Checkouts/OHHTTPStubs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 5c07292a..b056a4e0 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" -github "ishkawa/OHHTTPStubs" "28bc5ec46b92467871b5b2bef4c2bac8b3cdf897" +github "ishkawa/OHHTTPStubs" "acba4c9163614387410c8e2babfd60a4fb7a05de" diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs index 28bc5ec4..acba4c91 160000 --- a/Carthage/Checkouts/OHHTTPStubs +++ b/Carthage/Checkouts/OHHTTPStubs @@ -1 +1 @@ -Subproject commit 28bc5ec46b92467871b5b2bef4c2bac8b3cdf897 +Subproject commit acba4c9163614387410c8e2babfd60a4fb7a05de From e92c2ac4840826aaa0fca9f0617266184addbd04 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 16:27:41 +0900 Subject: [PATCH 19/36] add verbose flag to carthage bootstrap for debugging --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index d48494ee..cf0c0d3f 100644 --- a/circle.yml +++ b/circle.yml @@ -7,7 +7,7 @@ dependencies: - ./script/import-certificates - sudo gem install xcpretty - brew install carthage - - carthage bootstrap + - carthage bootstrap --verbose test: override: From a45dd5660bd24617983db92433df9649c909b467 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 16:38:42 +0900 Subject: [PATCH 20/36] fix dependencies --- Cartfile.resolved | 2 +- Carthage/Checkouts/OHHTTPStubs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index b056a4e0..d2ec13de 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" -github "ishkawa/OHHTTPStubs" "acba4c9163614387410c8e2babfd60a4fb7a05de" +github "ishkawa/OHHTTPStubs" "143eed6d5a00f97f536c3ce91e867d958d099897" diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs index acba4c91..143eed6d 160000 --- a/Carthage/Checkouts/OHHTTPStubs +++ b/Carthage/Checkouts/OHHTTPStubs @@ -1 +1 @@ -Subproject commit acba4c9163614387410c8e2babfd60a4fb7a05de +Subproject commit 143eed6d5a00f97f536c3ce91e867d958d099897 From b744f8ccbf646d3e3c913d0ae01783542d2753bb Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 16:45:35 +0900 Subject: [PATCH 21/36] update dependencies --- Cartfile.resolved | 2 +- Carthage/Checkouts/OHHTTPStubs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index d2ec13de..17608fd2 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" -github "ishkawa/OHHTTPStubs" "143eed6d5a00f97f536c3ce91e867d958d099897" +github "ishkawa/OHHTTPStubs" "312f88269822b35fb5545ec0d6ff2d27b9663dfd" diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs index 143eed6d..312f8826 160000 --- a/Carthage/Checkouts/OHHTTPStubs +++ b/Carthage/Checkouts/OHHTTPStubs @@ -1 +1 @@ -Subproject commit 143eed6d5a00f97f536c3ce91e867d958d099897 +Subproject commit 312f88269822b35fb5545ec0d6ff2d27b9663dfd From 79c8974edb8a3179ee4ea2d4aa86bf5accfb753e Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 17:31:19 +0900 Subject: [PATCH 22/36] fix dependencies --- Cartfile.resolved | 2 +- Carthage/Checkouts/OHHTTPStubs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 17608fd2..d367a091 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" -github "ishkawa/OHHTTPStubs" "312f88269822b35fb5545ec0d6ff2d27b9663dfd" +github "ishkawa/OHHTTPStubs" "44eba3fd09903861f02ae4fb796b47a0a7d82a8d" diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs index 312f8826..44eba3fd 160000 --- a/Carthage/Checkouts/OHHTTPStubs +++ b/Carthage/Checkouts/OHHTTPStubs @@ -1 +1 @@ -Subproject commit 312f88269822b35fb5545ec0d6ff2d27b9663dfd +Subproject commit 44eba3fd09903861f02ae4fb796b47a0a7d82a8d From 40e3b3a5a15b907afefa01b5ba5d4de70280b8af Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 17:35:30 +0900 Subject: [PATCH 23/36] embed OHHTTPStubs.framework in APIKitTests-Mac --- APIKit.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index f4b56a7c..f25c2cd1 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 7FCBE9DE1A9734880075AFD9 /* RequestBodyBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCBE9DC1A9734880075AFD9 /* RequestBodyBuilder.swift */; }; 7FCBE9E01A9734950075AFD9 /* ResponseBodyParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCBE9DF1A9734950075AFD9 /* ResponseBodyParser.swift */; }; 7FCBE9E11A9734950075AFD9 /* ResponseBodyParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCBE9DF1A9734950075AFD9 /* ResponseBodyParser.swift */; }; + 7FD65B141AA306BB008DCA2C /* OHHTTPStubs.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FAC259E1AA2C01100E92500 /* OHHTTPStubs.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 7FEC5A191A96FE2600B1D3C0 /* ResponseBodyParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FEC5A181A96FE2600B1D3C0 /* ResponseBodyParserTests.swift */; }; 7FEC5A1A1A96FE2600B1D3C0 /* APIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FCDD1A94D02C006863BB /* APIKit.framework */; }; 7FEC5A211A96FFD300B1D3C0 /* LlamaKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F45FD1C1A94D1B4006863BB /* LlamaKit.framework */; }; @@ -82,6 +83,7 @@ files = ( CDB0CCDF1A9B2F6700BADAC5 /* Assertions.framework in CopyFiles */, 7F0869A41A9787E3001AD3E1 /* LlamaKit.framework in CopyFiles */, + 7FD65B141AA306BB008DCA2C /* OHHTTPStubs.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; From 52b146ec7791ca34468f274acdde114c5dc6ecb6 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 17:35:59 +0900 Subject: [PATCH 24/36] Revert "add verbose flag to carthage bootstrap for debugging" This reverts commit e92c2ac4840826aaa0fca9f0617266184addbd04. --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index cf0c0d3f..d48494ee 100644 --- a/circle.yml +++ b/circle.yml @@ -7,7 +7,7 @@ dependencies: - ./script/import-certificates - sudo gem install xcpretty - brew install carthage - - carthage bootstrap --verbose + - carthage bootstrap test: override: From 98608a7c21da5eae4a0a1b8f1b70febdf5a53603 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 17:42:01 +0900 Subject: [PATCH 25/36] fix tests --- APIKitTests/APITests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift index af50ae5b..b8d0de34 100644 --- a/APIKitTests/APITests.swift +++ b/APIKitTests/APITests.swift @@ -103,7 +103,8 @@ class APITests: XCTestCase { XCTFail() case .Failure(let box): - assert(box.unbox, ==, error) + assertEqual(box.unbox.domain, error.domain) + assertEqual(box.unbox.code, error.code) } expectation.fulfill() From 79cca61a66f06e689c83b28b7b5f3a4375e5fae0 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 19:42:14 +0900 Subject: [PATCH 26/36] update dependencies --- Cartfile.resolved | 2 +- Carthage/Checkouts/OHHTTPStubs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index d367a091..3fabf9dd 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ github "ikesyo/Assertions" "fec437ce6857259ac8bda3eb255c5782aa798734" github "LlamaKit/LlamaKit" "v0.5.0" -github "ishkawa/OHHTTPStubs" "44eba3fd09903861f02ae4fb796b47a0a7d82a8d" +github "ishkawa/OHHTTPStubs" "07609ab981e12076a51bd9e2c8f06e4e73355fe2" diff --git a/Carthage/Checkouts/OHHTTPStubs b/Carthage/Checkouts/OHHTTPStubs index 44eba3fd..07609ab9 160000 --- a/Carthage/Checkouts/OHHTTPStubs +++ b/Carthage/Checkouts/OHHTTPStubs @@ -1 +1 @@ -Subproject commit 44eba3fd09903861f02ae4fb796b47a0a7d82a8d +Subproject commit 07609ab981e12076a51bd9e2c8f06e4e73355fe2 From 87359a68c22c0843e210b4bd9bcb82ca48240543 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 20:16:06 +0900 Subject: [PATCH 27/36] add doc about NSURLSessionDelegate --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 0ca43fa2..ea3ee537 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,26 @@ class GitHub: API { } ``` +## Advanced usage + + +## NSURLSessionDelegate + +APIKit creates singleton instances for each subclasses of API and set them as delegates of NSURLSession, +so you can add following features by implementing delegate methods. + +- Hook events of NSURLSession +- Handle authentication challenges +- Convert task to NSURLSessionDownloadTask + +### Overriding methods that implemented by API + +API class also uses delegate methods of NSURLSession to implement wrapper of NSURLSession, so you should call super if you override following methods. + +- `func URLSession(session:task:didCompleteWithError:)` +- `func URLSession(session:dataTask:didReceiveData:)` + + ## License Copyright (c) 2015 Yosuke Ishikawa From b1f4479774773c06adc872dd5f13b057f93e69fc Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 20:16:59 +0900 Subject: [PATCH 28/36] fix section --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea3ee537..6becfce5 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ class GitHub: API { ## Advanced usage -## NSURLSessionDelegate +### NSURLSessionDelegate APIKit creates singleton instances for each subclasses of API and set them as delegates of NSURLSession, so you can add following features by implementing delegate methods. @@ -185,7 +185,7 @@ API class also uses delegate methods of NSURLSession to implement wrapper of NSU - `func URLSession(session:dataTask:didReceiveData:)` -## License +### License Copyright (c) 2015 Yosuke Ishikawa From 3341c6756196d33c585a72c8b2f4ee0cecb935da Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 20:17:24 +0900 Subject: [PATCH 29/36] fix section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6becfce5..141ffff2 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ so you can add following features by implementing delegate methods. - Handle authentication challenges - Convert task to NSURLSessionDownloadTask -### Overriding methods that implemented by API +#### Overriding methods that implemented by API API class also uses delegate methods of NSURLSession to implement wrapper of NSURLSession, so you should call super if you override following methods. From 5c0a1e450f9c38e0a9e68ab25a445cdeb9b1de59 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 20:18:22 +0900 Subject: [PATCH 30/36] fix section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 141ffff2..dc5543e9 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ API class also uses delegate methods of NSURLSession to implement wrapper of NSU - `func URLSession(session:dataTask:didReceiveData:)` -### License +## License Copyright (c) 2015 Yosuke Ishikawa From 7f23e0d9279bcfc9864b3c80cd9cf8cf80abceb5 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Sun, 1 Mar 2015 20:19:10 +0900 Subject: [PATCH 31/36] fix section title --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc5543e9..c170c490 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ so you can add following features by implementing delegate methods. - Handle authentication challenges - Convert task to NSURLSessionDownloadTask -#### Overriding methods that implemented by API +#### Overriding delegate methods implemented by API API class also uses delegate methods of NSURLSession to implement wrapper of NSURLSession, so you should call super if you override following methods. From 5f5f569aa8f4dae6ed88e57cacaea7967f02605e Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 9 Mar 2015 22:15:26 +0900 Subject: [PATCH 32/36] remove unnecessary delegate method --- APIKit/APIKit.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 99624cc0..fdd5e1c3 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -193,11 +193,6 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { } } - // MARK: - NSURLSessionDelegate - public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void) { - completionHandler(.PerformDefaultHandling, nil) - } - // MARK: - NSURLSessionTaskDelegate // TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift. public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) { From 96478abd9b4dabd412e84eaf37af4da40192489e Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 9 Mar 2015 22:17:25 +0900 Subject: [PATCH 33/36] remove unnecessary delegate method --- APIKit/APIKit.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index fdd5e1c3..743d497b 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -202,10 +202,6 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { } // MARK: - NSURLSessionDataDelegate - public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) { - completionHandler(.Allow) - } - // TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift. public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { dataTask.responseBuffer.appendData(data) From 4910a77d6bfd487664f3d76b24542c6e14aecbc7 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 9 Mar 2015 22:19:58 +0900 Subject: [PATCH 34/36] fix word on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c170c490..21c385de 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ so you can add following features by implementing delegate methods. - Hook events of NSURLSession - Handle authentication challenges -- Convert task to NSURLSessionDownloadTask +- Convert a data task to NSURLSessionDownloadTask #### Overriding delegate methods implemented by API From 4207801da636ff51f0c6aaf5bbb08094f750e3f9 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 9 Mar 2015 22:23:10 +0900 Subject: [PATCH 35/36] fix coding style --- APIKitTests/APITests.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/APIKitTests/APITests.swift b/APIKitTests/APITests.swift index b8d0de34..89c56b17 100644 --- a/APIKitTests/APITests.swift +++ b/APIKitTests/APITests.swift @@ -103,8 +103,9 @@ class APITests: XCTestCase { XCTFail() case .Failure(let box): - assertEqual(box.unbox.domain, error.domain) - assertEqual(box.unbox.code, error.code) + let error = box.unbox + assertEqual(error.domain, error.domain) + assertEqual(error.code, error.code) } expectation.fulfill() @@ -129,8 +130,9 @@ class APITests: XCTestCase { XCTFail() case .Failure(let box): - assert(box.unbox.domain, ==, APIKitErrorDomain) - assertEqual(box.unbox.code, 400) + let error = box.unbox + assertEqual(error.domain, APIKitErrorDomain) + assertEqual(error.code, 400) } expectation.fulfill() From b006e77edc69e716f7538ef313635568be91f047 Mon Sep 17 00:00:00 2001 From: Yosuke Ishikawa Date: Mon, 9 Mar 2015 22:47:39 +0900 Subject: [PATCH 36/36] fix type of completion handler to avoid use of IUO --- APIKit/APIKit.swift | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/APIKit/APIKit.swift b/APIKit/APIKit.swift index 743d497b..f68befe3 100644 --- a/APIKit/APIKit.swift +++ b/APIKit/APIKit.swift @@ -40,9 +40,9 @@ private extension NSURLSessionDataTask { } } - private var completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)? { + private var completionHandler: ((NSData?, NSURLResponse?, NSError?) -> Void)? { get { - return (objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData!, NSURLResponse!, NSError!) -> Void>)?.unbox + return (objc_getAssociatedObject(self, &dataTaskCompletionHandlerKey) as? Box<(NSData?, NSURLResponse?, NSError?) -> Void>)?.unbox } set { @@ -167,18 +167,24 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate { dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) return } - - let mappedResponse: Result = self.responseBodyParser().parseData(data).flatMap { rawResponse in - if let response = request.responseFromObject(rawResponse) { - return success(response) - } else { - let userInfo = [NSLocalizedDescriptionKey: "failed to create model object from raw object."] - let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) - return failure(error) + + if let data = data { + let mappedResponse: Result = self.responseBodyParser().parseData(data).flatMap { rawResponse in + if let response = request.responseFromObject(rawResponse) { + return success(response) + } else { + let userInfo = [NSLocalizedDescriptionKey: "failed to create model object from raw object."] + let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) + return failure(error) + } + } - + dispatch_async(mainQueue, { handler(mappedResponse) }) + } else { + let userInfo = [NSLocalizedDescriptionKey: "unable to get response body despite NSURLSession raised no error."] + let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo) + dispatch_async(mainQueue, { handler(.Failure(Box(error))) }) } - dispatch_async(mainQueue, { handler(mappedResponse) }) } task.resume()