Skip to content

Commit 5639b96

Browse files
committed
call completion blocks on specified GCD queues using EventDispatcher class.
1 parent 0487e88 commit 5639b96

File tree

8 files changed

+172
-14
lines changed

8 files changed

+172
-14
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4+
## [0.3.0](https://github.com/MLSDev/TRON/releases/tag/0.3.0)
5+
6+
Completion blocks are now handled by new `EventDispatcher` class, which is responsible for running completion blocks on predefined GCD-queue.
7+
8+
Default behaviour - all parsing is made on QOS_CLASS_USER_INITIATED queue, success and failure blocks are called on main queue.
9+
10+
You can specify `processingQueue`, `successDeliveryQueue` and `failureDeliveryQueue` on `dispatcher` property of TRON. After request creation you can modify queues using `dispatcher` property on APIRequest, or even replace EventDispatcher with a custom one.
11+
412
## [0.2.1](https://github.com/MLSDev/TRON/releases/tag/0.2.1)
513

614
Added public initializer for NetworkActivityPlugin

Source/Core/APIRequest.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ public class APIRequest<Model: ResponseParseable, ErrorModel: ResponseParseable>
135135
/// API stub to be used when stubbing this request
136136
public var apiStub = APIStub<Model, ErrorModel>()
137137

138+
/// `EventDispatcher` instance, responsible for calling success and failure completion blocks on specified GCD-queues.
139+
public var dispatcher : EventDispatcher
140+
138141
/// Delegate property that is used to communicate with `TRON` instance.
139142
weak var tronDelegate : TronDelegate?
140143

@@ -154,6 +157,7 @@ public class APIRequest<Model: ResponseParseable, ErrorModel: ResponseParseable>
154157
self.stubbingEnabled = tron.stubbingEnabled
155158
self.headerBuilder = tron.headerBuilder
156159
self.urlBuilder = tron.urlBuilder
160+
self.dispatcher = tron.dispatcher
157161
}
158162

159163
/**
@@ -193,6 +197,7 @@ public class APIRequest<Model: ResponseParseable, ErrorModel: ResponseParseable>
193197
let allPlugins = plugins + (tronDelegate?.plugins ?? [])
194198
alamofireRequest.validate().handleResponse(success,
195199
failure: failure,
200+
dispatcher : dispatcher,
196201
responseBuilder: responseBuilder,
197202
errorBuilder: errorBuilder,
198203
plugins: allPlugins)
@@ -207,8 +212,10 @@ extension NSData {
207212
}
208213

209214
extension Alamofire.Request {
210-
func handleResponse<Model: ResponseParseable, ErrorModel: ResponseParseable>(success: Model.ModelType -> Void,
215+
func handleResponse<Model: ResponseParseable, ErrorModel: ResponseParseable>(
216+
success: Model.ModelType -> Void,
211217
failure: (APIError<ErrorModel> -> Void)?,
218+
dispatcher: EventDispatcher,
212219
responseBuilder: ResponseBuilder<Model>,
213220
errorBuilder: ErrorBuilder<ErrorModel>, plugins: [Plugin]) -> Self
214221
{
@@ -219,17 +226,21 @@ extension Alamofire.Request {
219226
$0.requestDidReceiveResponse(urlRequest, response,data,error)
220227
}
221228

222-
guard error == nil else {
223-
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: error))
224-
return
225-
}
226-
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), {
229+
dispatcher.processResponse {
230+
guard error == nil else {
231+
dispatcher.deliverFailure {
232+
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: error))
233+
}
234+
return
235+
}
227236
let object : AnyObject
228237
do {
229238
object = try (data ?? NSData()).parseToAnyObject()
230239
}
231240
catch let jsonError as NSError {
232-
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: jsonError))
241+
dispatcher.deliverFailure {
242+
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: jsonError))
243+
}
233244
return
234245
}
235246

@@ -238,13 +249,15 @@ extension Alamofire.Request {
238249
model = try responseBuilder.buildResponseFromJSON(object)
239250
}
240251
catch let parsingError as NSError {
241-
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: parsingError))
252+
dispatcher.deliverFailure {
253+
failure?(errorBuilder.buildErrorFromRequest(urlRequest, response: response, data: data, error: parsingError))
254+
}
242255
return
243256
}
244-
dispatch_async(dispatch_get_main_queue(), {
257+
dispatcher.deliverSuccess {
245258
success(model)
246-
})
247-
})
259+
}
260+
}
248261
}
249262
}
250263
}

Source/Core/EventDispatcher.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// EventDelivery.swift
3+
// TRON
4+
//
5+
// Created by Denys Telezhkin on 28.01.16.
6+
// Copyright © 2015 - present MLSDev. All rights reserved.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall be included in
16+
// all copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
26+
import Foundation
27+
28+
/// Class, responsible for dispatching events on different GCD queues.
29+
public class EventDispatcher {
30+
31+
/// Queue, used for processing response, received from the server. Defaults to QOS_CLASS_USER_INITIATED queue
32+
public var processingQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
33+
34+
/// Queue, used to deliver success completion blocks. Defaults to dispatch_get_main_queue().
35+
public var successDeliveryQueue = dispatch_get_main_queue()
36+
37+
/// Queue, used to deliver failure completion blocks. Defaults to dispatch_get_main_queue().
38+
public var failureDeliveryQueue = dispatch_get_main_queue()
39+
40+
func processResponse(processingBlock: Void -> Void) {
41+
dispatch_async(processingQueue, processingBlock)
42+
}
43+
44+
func deliverSuccess(deliveryBlock : Void -> Void) {
45+
dispatch_async(successDeliveryQueue, deliveryBlock)
46+
}
47+
48+
func deliverFailure(deliveryBlock: Void -> Void) {
49+
dispatch_async(failureDeliveryQueue, deliveryBlock)
50+
}
51+
}

Source/Core/MultipartAPIRequest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public class MultipartAPIRequest<Model: ResponseParseable, ErrorModel: ResponseP
103103
failure?(apiError)
104104
} else if case .Success(let request, _, _) = completion {
105105
let allPlugins = self.plugins + (self.tronDelegate?.plugins ?? [])
106-
request.progress(progress).validate().handleResponse(success, failure: failure, responseBuilder: self.responseBuilder, errorBuilder: self.errorBuilder, plugins: allPlugins)
106+
request.progress(progress).validate().handleResponse(success, failure: failure, dispatcher: self.dispatcher, responseBuilder: self.responseBuilder, errorBuilder: self.errorBuilder, plugins: allPlugins)
107107
cancellableCallback(request)
108108
}
109109
}

Source/Core/TRON.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ public class TRON : TronDelegate {
4848
/// Alamofire.Manager instance used to send network requests
4949
public let manager : Alamofire.Manager
5050

51+
/// Object, responsible for calling success and failure completion blocks on specified GCD queues. When `APIRequest` object is created, it is automatically configured with current dispatcher instance.
52+
public var dispatcher = EventDispatcher()
53+
5154
/**
5255
Initializes `TRON` with given base URL, Alamofire.Manager instance, and array of global plugins.
5356

TRON.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'TRON'
3-
s.version = '0.2.1'
3+
s.version = '0.3.0'
44
s.license = 'MIT'
55
s.summary = 'Lightweight network abstraction layer, written on top of Alamofire and SwiftyJSON'
66
s.homepage = 'https://github.com/MLSDev/TRON'

TRON.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@
108108
9A90CC901C5FABBD00472DB8 /* user.json in Resources */ = {isa = PBXBuildFile; fileRef = 9A90CC8C1C5FABBD00472DB8 /* user.json */; };
109109
9A90CC911C5FABBD00472DB8 /* user.json in Resources */ = {isa = PBXBuildFile; fileRef = 9A90CC8C1C5FABBD00472DB8 /* user.json */; };
110110
9A90CC921C5FABBD00472DB8 /* user.json in Resources */ = {isa = PBXBuildFile; fileRef = 9A90CC8C1C5FABBD00472DB8 /* user.json */; };
111+
9AE82A701C98102B007A610E /* EventDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE82A6F1C98102B007A610E /* EventDispatcher.swift */; };
112+
9AE82A711C98102B007A610E /* EventDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE82A6F1C98102B007A610E /* EventDispatcher.swift */; };
113+
9AE82A721C98102B007A610E /* EventDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE82A6F1C98102B007A610E /* EventDispatcher.swift */; };
114+
9AE82A731C98102B007A610E /* EventDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AE82A6F1C98102B007A610E /* EventDispatcher.swift */; };
111115
9AEE0BAD1C5A4E15003222A8 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEE0BA01C5A4E0A003222A8 /* Alamofire.framework */; };
112116
9AEE0BC61C5A4E33003222A8 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEE0BB91C5A4E2D003222A8 /* SwiftyJSON.framework */; };
113117
9AEE0BDC1C5A4FBF003222A8 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AEE0BD11C5A4FB5003222A8 /* Nimble.framework */; };
@@ -561,6 +565,7 @@
561565
9A90CCAC1C5FB98E00472DB8 /* OS X.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "OS X.plist"; path = "Supporting files/OS X.plist"; sourceTree = "<group>"; };
562566
9A90CCAD1C5FB98E00472DB8 /* tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = tvOS.plist; path = "Supporting files/tvOS.plist"; sourceTree = "<group>"; };
563567
9A90CCAE1C5FB98E00472DB8 /* watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = watchOS.plist; path = "Supporting files/watchOS.plist"; sourceTree = "<group>"; };
568+
9AE82A6F1C98102B007A610E /* EventDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EventDispatcher.swift; path = Source/Core/EventDispatcher.swift; sourceTree = "<group>"; };
564569
9AEE0B951C5A4E09003222A8 /* Alamofire.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire.xcodeproj; path = Carthage/Checkouts/Alamofire/Alamofire.xcodeproj; sourceTree = "<group>"; };
565570
9AEE0BAE1C5A4E2D003222A8 /* SwiftyJSON.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftyJSON.xcodeproj; path = Carthage/Checkouts/SwiftyJSON/SwiftyJSON.xcodeproj; sourceTree = "<group>"; };
566571
9AEE0BC71C5A4FB5003222A8 /* Nimble.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Nimble.xcodeproj; path = Carthage/Checkouts/Nimble/Nimble.xcodeproj; sourceTree = "<group>"; };
@@ -723,6 +728,7 @@
723728
9A154F921C67A6AB00B33F01 /* APIStub.swift */,
724729
9A154F931C67A6AB00B33F01 /* MultipartAPIRequest.swift */,
725730
9A154F941C67A6AB00B33F01 /* TRON.swift */,
731+
9AE82A6F1C98102B007A610E /* EventDispatcher.swift */,
726732
9A90CC331C5FAB5A00472DB8 /* Builders */,
727733
9A154FC81C67A6DF00B33F01 /* Mappers */,
728734
9A90CC341C5FAB5F00472DB8 /* Plugins */,
@@ -1482,6 +1488,7 @@
14821488
9A154F961C67A6AB00B33F01 /* APIRequest.swift in Sources */,
14831489
9A154FC51C67A6CB00B33F01 /* Plugin.swift in Sources */,
14841490
9A154FAA1C67A6BA00B33F01 /* ErrorBuilder.swift in Sources */,
1491+
9AE82A711C98102B007A610E /* EventDispatcher.swift in Sources */,
14851492
9A154F9E1C67A6AB00B33F01 /* MultipartAPIRequest.swift in Sources */,
14861493
9A154FB61C67A6BA00B33F01 /* ResponseBuilder.swift in Sources */,
14871494
9A13FA821C662C2D00A54352 /* SwiftyJSONDecodable.swift in Sources */,
@@ -1516,6 +1523,7 @@
15161523
9A154F971C67A6AB00B33F01 /* APIRequest.swift in Sources */,
15171524
9A154FC61C67A6CB00B33F01 /* Plugin.swift in Sources */,
15181525
9A154FAB1C67A6BA00B33F01 /* ErrorBuilder.swift in Sources */,
1526+
9AE82A721C98102B007A610E /* EventDispatcher.swift in Sources */,
15191527
9A154F9F1C67A6AB00B33F01 /* MultipartAPIRequest.swift in Sources */,
15201528
9A154FB71C67A6BA00B33F01 /* ResponseBuilder.swift in Sources */,
15211529
9A13FA831C662C2D00A54352 /* SwiftyJSONDecodable.swift in Sources */,
@@ -1550,6 +1558,7 @@
15501558
9A154F981C67A6AB00B33F01 /* APIRequest.swift in Sources */,
15511559
9A154FC71C67A6CB00B33F01 /* Plugin.swift in Sources */,
15521560
9A154FAC1C67A6BA00B33F01 /* ErrorBuilder.swift in Sources */,
1561+
9AE82A731C98102B007A610E /* EventDispatcher.swift in Sources */,
15531562
9A154FA01C67A6AB00B33F01 /* MultipartAPIRequest.swift in Sources */,
15541563
9A154FB81C67A6BA00B33F01 /* ResponseBuilder.swift in Sources */,
15551564
9A13FA841C662C2D00A54352 /* SwiftyJSONDecodable.swift in Sources */,
@@ -1581,6 +1590,7 @@
15811590
9A154FC41C67A6CB00B33F01 /* Plugin.swift in Sources */,
15821591
9A154FC01C67A6CB00B33F01 /* NetworkLoggerPlugin.swift in Sources */,
15831592
9A154FA11C67A6AB00B33F01 /* TRON.swift in Sources */,
1593+
9AE82A701C98102B007A610E /* EventDispatcher.swift in Sources */,
15841594
9A154F991C67A6AB00B33F01 /* APIStub.swift in Sources */,
15851595
9A154F9D1C67A6AB00B33F01 /* MultipartAPIRequest.swift in Sources */,
15861596
9A154F951C67A6AB00B33F01 /* APIRequest.swift in Sources */,

Tests/APIRequestTestCase.swift

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import Nimble
1212

1313
class APIRequestTestCase: XCTestCase {
1414

15+
var tron: TRON!
16+
17+
override func setUp() {
18+
super.setUp()
19+
tron = TRON(baseURL: "http://httpbin.org")
20+
}
21+
1522
func testErrorBuilding() {
16-
let tron = TRON(baseURL: "http://httpbin.org")
1723
let request: APIRequest<Int,TronError> = tron.request(path: "status/418")
1824
let expectation = expectationWithDescription("Teapot")
1925
request.performWithSuccess({ _ in
@@ -26,4 +32,71 @@ class APIRequestTestCase: XCTestCase {
2632
waitForExpectationsWithTimeout(5, handler: nil)
2733
}
2834

35+
func testSuccessCallBackIsCalledOnMainThread() {
36+
let request : APIRequest<String,TronError> = tron.request(path: "get")
37+
let expectation = expectationWithDescription("200")
38+
request.performWithSuccess({ _ in
39+
if NSThread.isMainThread() {
40+
expectation.fulfill()
41+
}
42+
}) { _ in
43+
XCTFail()
44+
}
45+
waitForExpectationsWithTimeout(5, handler: nil)
46+
}
47+
48+
func testFailureCallbackIsCalledOnMainThread() {
49+
let request : APIRequest<Int,TronError> = tron.request(path: "status/418")
50+
let expectation = expectationWithDescription("Teapot")
51+
request.performWithSuccess({ _ in
52+
XCTFail()
53+
}) { error in
54+
if NSThread.isMainThread() {
55+
expectation.fulfill()
56+
}
57+
}
58+
waitForExpectationsWithTimeout(5, handler: nil)
59+
}
60+
61+
func testParsingFailureCallbackIsCalledOnMainThread() {
62+
let request : APIRequest<Int,TronError> = tron.request(path: "html")
63+
let expectation = expectationWithDescription("Parsing failure")
64+
request.performWithSuccess({ _ in
65+
XCTFail()
66+
}) { error in
67+
if NSThread.isMainThread() {
68+
expectation.fulfill()
69+
}
70+
}
71+
waitForExpectationsWithTimeout(5, handler: nil)
72+
}
73+
74+
func testSuccessBlockCanBeCalledOnBackgroundThread() {
75+
tron.dispatcher.successDeliveryQueue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
76+
let request : APIRequest<Int,TronError> = tron.request(path: "get")
77+
let expectation = expectationWithDescription("200")
78+
request.performWithSuccess({ _ in
79+
if !NSThread.isMainThread() {
80+
expectation.fulfill()
81+
}
82+
}) { _ in
83+
XCTFail()
84+
}
85+
waitForExpectationsWithTimeout(5, handler: nil)
86+
}
87+
88+
func testFailureCallbacksCanBeCalledOnBackgroundThread() {
89+
tron.dispatcher.failureDeliveryQueue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
90+
let request : APIRequest<Int,TronError> = tron.request(path: "html")
91+
let expectation = expectationWithDescription("Parsing failure")
92+
request.performWithSuccess({ _ in
93+
XCTFail()
94+
}) { error in
95+
if !NSThread.isMainThread() {
96+
expectation.fulfill()
97+
}
98+
}
99+
waitForExpectationsWithTimeout(5, handler: nil)
100+
}
101+
29102
}

0 commit comments

Comments
 (0)