Skip to content

Commit d6d02cf

Browse files
committed
Merge pull request #14 from ishkawa/feature/no-singleton-API-instance
Add defaultURLSession and its delegate instead of instancePair
2 parents d65dc02 + cd24709 commit d6d02cf

File tree

3 files changed

+33
-72
lines changed

3 files changed

+33
-72
lines changed

Diff for: APIKit/APIKit.swift

+25-51
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,9 @@ 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
112-
}
113-
114-
public final class var URLSession: NSURLSession {
115-
return instancePair.1
78+
79+
public class var defaultURLSession: NSURLSession {
80+
return internalDefaultURLSession
11681
}
11782

11883
public class var acceptableStatusCodes: [Int] {
@@ -150,13 +115,22 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
150115
}
151116
}
152117

153-
// 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):)`
154124
public class func sendRequest<T: Request>(request: T, handler: (Result<T.Response, NSError>) -> Void = {r in}) -> NSURLSessionDataTask? {
155-
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? {
156130
let mainQueue = dispatch_get_main_queue()
157131

158132
if let URLRequest = request.URLRequest {
159-
let task = session.dataTaskWithRequest(URLRequest)
133+
let task = URLSession.dataTaskWithRequest(URLRequest)
160134

161135
task.completionHandler = { data, URLResponse, connectionError in
162136
if let error = connectionError {
@@ -201,24 +175,24 @@ public class API: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
201175
return nil
202176
}
203177
}
204-
178+
205179
public class func responseErrorFromObject(object: AnyObject) -> NSError {
206180
let userInfo = [NSLocalizedDescriptionKey: "received status code that represents error"]
207181
let error = NSError(domain: APIKitErrorDomain, code: 0, userInfo: userInfo)
208182
return error
209183
}
184+
}
210185

186+
public class URLSessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {
211187
// MARK: - NSURLSessionTaskDelegate
212-
// TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift.
213188
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError connectionError: NSError?) {
214189
if let dataTask = task as? NSURLSessionDataTask {
215190
dataTask.completionHandler?(dataTask.responseBuffer, dataTask.response, connectionError)
216191
}
217192
}
218193

219194
// MARK: - NSURLSessionDataDelegate
220-
// TODO: add attributes like NS_REQUIRES_SUPER when it is available in future version of Swift.
221195
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
222196
dataTask.responseBuffer.appendData(data)
223-
}
197+
}
224198
}

Diff for: APIKitTests/APITests.swift

-15
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,6 @@ class APITests: XCTestCase {
4545
super.tearDown()
4646
}
4747

48-
// MARK: - instance tests
49-
func testDifferentSessionsAreCreatedForEachClasses() {
50-
assert(MockAPI.URLSession, !=, AnotherMockAPI.URLSession)
51-
}
52-
53-
func testSameSessionsAreUsedInSameClasses() {
54-
assertEqual(MockAPI.URLSession, MockAPI.URLSession)
55-
assertEqual(AnotherMockAPI.URLSession, AnotherMockAPI.URLSession)
56-
}
57-
58-
func testDelegateOfSessions() {
59-
assertNotNil(MockAPI.URLSession.delegate as? MockAPI)
60-
assertNotNil(AnotherMockAPI.URLSession.delegate as? AnotherMockAPI)
61-
}
62-
6348
// MARK: - integration tests
6449
func testSuccess() {
6550
let dictionary = ["key": "value"]

Diff for: README.md

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

170170
## Advanced usage
171171

172-
173172
### NSURLSessionDelegate
174173

175-
APIKit creates singleton instances for each subclasses of API and set them as delegates of NSURLSession,
176-
so you can add following features by implementing delegate methods.
174+
You can add custom behaviors of `NSURLSession` by following steps:
175+
176+
1. Create a subclass of `URLSessionDelegate` (e.g. `MyAPIURLSessionDelegate`).
177+
2. Implement additional delegate methods in it.
178+
3. Override `defaultURLSession` of `API` and return `NSURLSession` that has `MyURLSessionDelegate` as its delegate.
179+
180+
This can add following features:
177181

178182
- Hook events of NSURLSession
179183
- Handle authentication challenges
180184
- Convert a data task to NSURLSessionDownloadTask
181185

182-
#### Overriding delegate methods implemented by API
183-
184-
API class also uses delegate methods of NSURLSession to implement wrapper of NSURLSession, so you should call super if you override following methods.
186+
NOTE: `URLSessionDelegate` also implements delegate methods of `NSURLSession` to implement wrapper of `NSURLSession`, so you should call super if you override following methods.
185187

186188
- `func URLSession(session:task:didCompleteWithError:)`
187189
- `func URLSession(session:dataTask:didReceiveData:)`

0 commit comments

Comments
 (0)