Skip to content

Commit 092fd42

Browse files
committed
Merge branch 'develop/0.4'
2 parents e23fe9a + d6d02cf commit 092fd42

File tree

3 files changed

+59
-78
lines changed

3 files changed

+59
-78
lines changed

APIKit/APIKit.swift

+43-54
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,13 @@ private extension NSURLSessionDataTask {
5656
}
5757

5858
// use private, global scope variable until we can use stored class var in Swift 1.2
59-
private var instancePairDictionary = [String: (API, NSURLSession)]()
60-
private let instancePairSemaphore = dispatch_semaphore_create(1)
59+
private let internalDefaultURLSession = NSURLSession(
60+
configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
61+
delegate: URLSessionDelegate(),
62+
delegateQueue: nil
63+
)
6164

62-
public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
65+
public class API {
6366
// configurations
6467
public class func baseURL() -> NSURL {
6568
fatalError("API.baseURL() must be overrided in subclasses.")
@@ -72,47 +75,13 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
7275
public class func responseBodyParser() -> ResponseBodyParser {
7376
return .JSON(readingOptions: nil)
7477
}
75-
76-
public class func URLSessionConfiguration() -> NSURLSessionConfiguration {
77-
return NSURLSessionConfiguration.defaultSessionConfiguration()
78-
}
79-
80-
public class func URLSessionDelegateQueue() -> NSOperationQueue? {
81-
// nil indicates NSURLSession creates its own serial operation queue.
82-
// see doc of NSURLSession.init(configuration:delegate:delegateQueue:) for more details.
83-
return nil
84-
}
85-
86-
// prevent instantiation
87-
override private init() {
88-
super.init()
89-
}
90-
91-
// create session and instance of API for each subclasses
92-
private final class var instancePair: (API, NSURLSession) {
93-
let className = NSStringFromClass(self)
94-
95-
dispatch_semaphore_wait(instancePairSemaphore, DISPATCH_TIME_FOREVER)
96-
let pair: (API, NSURLSession) = instancePairDictionary[className] ?? {
97-
let instance = (self as NSObject.Type)() as API
98-
let configuration = self.URLSessionConfiguration()
99-
let queue = self.URLSessionDelegateQueue()
100-
let session = NSURLSession(configuration: configuration, delegate: instance, delegateQueue: queue)
101-
let pair = (instance, session)
102-
instancePairDictionary[className] = pair
103-
return pair
104-
}()
105-
dispatch_semaphore_signal(instancePairSemaphore)
106-
107-
return pair
108-
}
109-
110-
public final class var instance: API {
111-
return instancePair.0
78+
79+
public class var defaultURLSession: NSURLSession {
80+
return internalDefaultURLSession
11281
}
113-
114-
public final class var URLSession: NSURLSession {
115-
return instancePair.1
82+
83+
public class var acceptableStatusCodes: [Int] {
84+
return [Int](200..<300)
11685
}
11786

11887
// build NSURLRequest
@@ -146,13 +115,22 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
146115
}
147116
}
148117

149-
// send request and build response object
118+
// In Swift 1.1, we could not omit `URLSession` argument of `func send(request:URLSession(=default):handler(=default):)`
119+
// with trailing closure, so we provide following 2 methods
120+
// - `func sendRequest(request:handler(=default):)`
121+
// - `func sendRequest(request:URLSession:handler(=default):)`.
122+
// In Swift 1.2, we can omit default arguments with trailing closure, so they should be replaced with
123+
// - `func sendRequest(request:URLSession(=default):handler(=default):)`
150124
public class func sendRequest<T: Request>(request: T, handler: (Result<T.Response, NSError>) -> Void = {r in}) -> NSURLSessionDataTask? {
151-
let session = URLSession
125+
return sendRequest(request, URLSession: defaultURLSession, handler: handler)
126+
}
127+
128+
// send request and build response object
129+
public class func sendRequest<T: Request>(request: T, URLSession: NSURLSession, handler: (Result<T.Response, NSError>) -> Void = {r in}) -> NSURLSessionDataTask? {
152130
let mainQueue = dispatch_get_main_queue()
153131

154132
if let URLRequest = request.URLRequest {
155-
let task = session.dataTaskWithRequest(URLRequest)
133+
let task = URLSession.dataTaskWithRequest(URLRequest)
156134

157135
task.completionHandler = { data, URLResponse, connectionError in
158136
if let error = connectionError {
@@ -161,10 +139,15 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
161139
}
162140

163141
let statusCode = (URLResponse as? NSHTTPURLResponse)?.statusCode ?? 0
164-
if !contains(200..<300, statusCode) {
165-
let userInfo = [NSLocalizedDescriptionKey: "received status code that represents error"]
166-
let error = NSError(domain: APIKitErrorDomain, code: statusCode, userInfo: userInfo)
167-
dispatch_async(mainQueue, { handler(.Failure(Box(error))) })
142+
if !contains(self.acceptableStatusCodes, statusCode) {
143+
let error: NSError = {
144+
switch self.responseBodyParser().parseData(data) {
145+
case .Success(let box): return self.responseErrorFromObject(box.unbox)
146+
case .Failure(let box): return box.unbox
147+
}
148+
}()
149+
150+
dispatch_async(mainQueue) { handler(failure(error)) }
168151
return
169152
}
170153

@@ -176,8 +159,8 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
176159
let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo)
177160
return failure(error)
178161
}
179-
180162
}
163+
181164
dispatch_async(mainQueue, { handler(mappedResponse) })
182165
}
183166

@@ -193,17 +176,23 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
193176
}
194177
}
195178

179+
public class func responseErrorFromObject(object: AnyObject) -> NSError {
180+
let userInfo = [NSLocalizedDescriptionKey: "received status code that represents error"]
181+
let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo)
182+
return error
183+
}
184+
}
185+
186+
public class URLSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
196187
// MARK: - NSURLSessionTaskDelegate
197-
// TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift.
198188
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) {
199189
if let dataTask = task as? NSURLSessionDataTask {
200190
dataTask.completionHandler?(dataTask.responseBuffer, dataTask.response, connectionError)
201191
}
202192
}
203193

204194
// MARK: - NSURLSessionDataDelegate
205-
// TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift.
206195
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
207196
dataTask.responseBuffer.appendData(data)
208-
}
197+
}
209198
}

APIKitTests/APITests.swift

+8-18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class APITests: XCTestCase {
1717
override class func responseBodyParser() -> ResponseBodyParser {
1818
return .JSON(readingOptions: nil)
1919
}
20+
21+
override class func responseErrorFromObject(object: AnyObject) -> NSError {
22+
return NSError(domain: "MockAPIErrorDomain", code: 10000, userInfo: nil)
23+
}
2024

2125
class Endpoint {
2226
class Get: Request {
@@ -41,21 +45,6 @@ class APITests: XCTestCase {
4145
super.tearDown()
4246
}
4347

44-
// MARK: - instance tests
45-
func testDifferentSessionsAreCreatedForEachClasses() {
46-
assert(MockAPI.URLSession, !=, AnotherMockAPI.URLSession)
47-
}
48-
49-
func testSameSessionsAreUsedInSameClasses() {
50-
assertEqual(MockAPI.URLSession, MockAPI.URLSession)
51-
assertEqual(AnotherMockAPI.URLSession, AnotherMockAPI.URLSession)
52-
}
53-
54-
func testDelegateOfSessions() {
55-
assertNotNil(MockAPI.URLSession.delegate as? MockAPI)
56-
assertNotNil(AnotherMockAPI.URLSession.delegate as? AnotherMockAPI)
57-
}
58-
5948
// MARK: - integration tests
6049
func testSuccess() {
6150
let dictionary = ["key": "value"]
@@ -118,7 +107,8 @@ class APITests: XCTestCase {
118107
OHHTTPStubs.stubRequestsPassingTest({ request in
119108
return true
120109
}, withStubResponse: { request in
121-
return OHHTTPStubsResponse(data: NSData(), statusCode: 400, headers: nil)
110+
let data = NSJSONSerialization.dataWithJSONObject([:], options: nil, error: nil)!
111+
return OHHTTPStubsResponse(data: data, statusCode: 400, headers: nil)
122112
})
123113

124114
let expectation = expectationWithDescription("wait for response")
@@ -131,8 +121,8 @@ class APITests: XCTestCase {
131121

132122
case .Failure(let box):
133123
let error = box.unbox
134-
assertEqual(error.domain, APIKitErrorDomain)
135-
assertEqual(error.code, 400)
124+
assertEqual(error.domain, "MockAPIErrorDomain")
125+
assertEqual(error.code, 10000)
136126
}
137127

138128
expectation.fulfill()

README.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -183,19 +183,21 @@ class GitHub: API {
183183

184184
## Advanced usage
185185

186-
187186
### NSURLSessionDelegate
188187

189-
APIKit creates singleton instances for each subclasses of API and set them as delegates of NSURLSession,
190-
so you can add following features by implementing delegate methods.
188+
You can add custom behaviors of `NSURLSession` by following steps:
189+
190+
1. Create a subclass of `URLSessionDelegate` (e.g. `MyAPIURLSessionDelegate`).
191+
2. Implement additional delegate methods in it.
192+
3. Override `defaultURLSession` of `API` and return `NSURLSession` that has `MyURLSessionDelegate` as its delegate.
193+
194+
This can add following features:
191195

192196
- Hook events of NSURLSession
193197
- Handle authentication challenges
194198
- Convert a data task to NSURLSessionDownloadTask
195199

196-
#### Overriding delegate methods implemented by API
197-
198-
API class also uses delegate methods of NSURLSession to implement wrapper of NSURLSession, so you should call super if you override following methods.
200+
NOTE: `URLSessionDelegate` also implements delegate methods of `NSURLSession` to implement wrapper of `NSURLSession`, so you should call super if you override following methods.
199201

200202
- `func URLSession(session:task:didCompleteWithError:)`
201203
- `func URLSession(session:dataTask:didReceiveData:)`

0 commit comments

Comments
 (0)