Skip to content

Commit 214b903

Browse files
Merge pull request #5 from teepsllc/feature/promises
Promises
2 parents d8d5e16 + 07876ff commit 214b903

File tree

9 files changed

+161
-44
lines changed

9 files changed

+161
-44
lines changed

BuckoNetworking.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
6ABB45021EF86C0900E344AF /* DecodableEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7D02921E4CC12900E66FD0 /* DecodableEndpoint.swift */; };
2323
6ABB45041EF86DE200E344AF /* BuckoNetworking.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A7D02751E4CBEFC00E66FD0 /* BuckoNetworking.h */; settings = {ATTRIBUTES = (Public, ); }; };
2424
6AEC6FDE1E84B05500BBD3F7 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AEC6FDC1E84B05500BBD3F7 /* Alamofire.framework */; };
25+
6AFA9DED1FF68A0D00FDC188 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AFA9DEC1FF68A0D00FDC188 /* PromiseKit.framework */; };
26+
6AFA9DEF1FF68A3400FDC188 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AFA9DEE1FF68A3400FDC188 /* PromiseKit.framework */; };
2527
/* End PBXBuildFile section */
2628

2729
/* Begin PBXContainerItemProxy section */
@@ -60,13 +62,16 @@
6062
6ABB44FB1EF8661100E344AF /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = "<group>"; };
6163
6AEC6FDB1E84B05500BBD3F7 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = "<group>"; };
6264
6AEC6FDC1E84B05500BBD3F7 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
65+
6AFA9DEC1FF68A0D00FDC188 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/iOS/PromiseKit.framework; sourceTree = "<group>"; };
66+
6AFA9DEE1FF68A3400FDC188 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = "<group>"; };
6367
/* End PBXFileReference section */
6468

6569
/* Begin PBXFrameworksBuildPhase section */
6670
6A7D02431E4CB6FE00E66FD0 /* Frameworks */ = {
6771
isa = PBXFrameworksBuildPhase;
6872
buildActionMask = 2147483647;
6973
files = (
74+
6AFA9DED1FF68A0D00FDC188 /* PromiseKit.framework in Frameworks */,
7075
6AEC6FDE1E84B05500BBD3F7 /* Alamofire.framework in Frameworks */,
7176
);
7277
runOnlyForDeploymentPostprocessing = 0;
@@ -83,6 +88,7 @@
8388
isa = PBXFrameworksBuildPhase;
8489
buildActionMask = 2147483647;
8590
files = (
91+
6AFA9DEF1FF68A3400FDC188 /* PromiseKit.framework in Frameworks */,
8692
6ABB44FD1EF8661100E344AF /* Alamofire.framework in Frameworks */,
8793
);
8894
runOnlyForDeploymentPostprocessing = 0;
@@ -152,6 +158,8 @@
152158
6A7D02D41E4CC5E900E66FD0 /* Frameworks */ = {
153159
isa = PBXGroup;
154160
children = (
161+
6AFA9DEC1FF68A0D00FDC188 /* PromiseKit.framework */,
162+
6AFA9DEE1FF68A3400FDC188 /* PromiseKit.framework */,
155163
6ABB44FA1EF8661100E344AF /* SwiftyJSON.framework */,
156164
6ABB44FB1EF8661100E344AF /* Alamofire.framework */,
157165
6AEC6FDB1E84B05500BBD3F7 /* SwiftyJSON.framework */,

BuckoNetworking/Bucko.swift

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@ public protocol BuckoErrorHandler: class {
1515

1616
public typealias BuckoResponseClosure = ((DataResponse<Any>) -> Void)
1717
public typealias BuckoDataResponseClosure = ((DataResponse<Data>) -> Void)
18-
@available(*, deprecated, message: "Use HTTPMethod instead")
19-
public typealias HttpMethod = HTTPMethod
20-
@available(*, deprecated, message: "Use HTTPHeaders instead")
21-
public typealias HttpHeaders = HTTPHeaders
22-
@available(*, deprecated, message: "Use ParameterEncoding instead")
23-
public typealias Encoding = ParameterEncoding
24-
@available(*, deprecated, message: "Use URLEncoding instead")
25-
public typealias UrlEncoding = URLEncoding
26-
@available(*, deprecated, message: "Use JSONEncoding instead")
27-
public typealias JsonEncoding = JSONEncoding
28-
@available(*, deprecated, message: "Use Parameters instead")
29-
public typealias Body = Parameters
3018

3119
public struct Bucko {
3220
/**
@@ -155,4 +143,44 @@ public struct Bucko {
155143
print(request.description)
156144
return request
157145
}
146+
147+
public func request(endpoint: Endpoint) -> Promise<DataResponse<Any>> {
148+
return Promise { seal in
149+
request(endpoint: endpoint) { response in
150+
151+
if response.result.isSuccess {
152+
seal.fulfill(response)
153+
} else {
154+
if let responseError = response.result.value {
155+
do {
156+
let json = try JSONSerialization.data(withJSONObject: responseError, options: [])
157+
seal.reject(BuckoError(apiError: json))
158+
} catch {
159+
seal.reject(response.result.error!)
160+
}
161+
} else {
162+
seal.reject(response.result.error!)
163+
}
164+
}
165+
}
166+
}
167+
}
168+
169+
public func requestData(endpoint: Endpoint) -> Promise<Data> {
170+
return Promise { seal in
171+
requestData(endpoint: endpoint) { response in
172+
173+
if response.result.isSuccess {
174+
seal.fulfill(response.result.value!)
175+
} else {
176+
177+
if let responseError = response.result.value {
178+
seal.reject(BuckoError(apiError: responseError))
179+
} else {
180+
seal.reject(response.result.error!)
181+
}
182+
}
183+
}
184+
}
185+
}
158186
}

BuckoNetworking/BuckoError.swift

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
// Copyright © 2017 Teeps. All rights reserved.
77
//
88

9-
import Foundation.NSError
9+
private let buckoErrorData = "BuckoErrorData"
1010

1111
public final class BuckoError: NSError {
12-
13-
fileprivate enum Code: Int {
12+
private enum Code: Int {
1413
case api = 1
1514
case validation
1615
case service // an error caused by a third party service
@@ -19,7 +18,7 @@ public final class BuckoError: NSError {
1918
case unknown
2019
}
2120

22-
fileprivate static var domain: String {
21+
private static var domain: String {
2322
return Bundle.main.bundleIdentifier!
2423
}
2524

@@ -28,6 +27,10 @@ public final class BuckoError: NSError {
2827
self.init(code: .api, reason: reason, description: description)
2928
}
3029

30+
convenience init(apiError data: Data) {
31+
self.init(code: .api, reason: "API Error", description: nil, data: data)
32+
}
33+
3134
convenience init(validationError reason: String, description: String? = nil) {
3235
self.init(code: .validation, reason: reason, description: description)
3336
}
@@ -48,11 +51,16 @@ public final class BuckoError: NSError {
4851
self.init(code: .auth, reason: reason, description: description)
4952
}
5053

51-
fileprivate convenience init(code: Code, reason: String, description: String? = nil) {
52-
self.init(domain: BuckoError.domain, code: code.rawValue, userInfo: [
53-
NSLocalizedFailureReasonErrorKey: reason,
54-
NSLocalizedDescriptionKey: description ?? reason
55-
])
54+
private convenience init(code: Code, reason: String, description: String? = nil, data: Data? = nil) {
55+
self.init(
56+
domain: BuckoError.domain,
57+
code: code.rawValue,
58+
userInfo: [
59+
NSLocalizedFailureReasonErrorKey: reason,
60+
NSLocalizedDescriptionKey: description ?? reason,
61+
buckoErrorData: data ?? NSNull()
62+
]
63+
)
5664
}
5765

5866
// MARK: - Common errors
@@ -63,5 +71,20 @@ public final class BuckoError: NSError {
6371
static func unknown() -> BuckoError {
6472
return BuckoError(code: .unknown, reason: "An unknown error occurred")
6573
}
74+
}
75+
76+
public extension Error {
77+
var data: Data? {
78+
let error = self as NSError
79+
guard let data = error.userInfo[buckoErrorData] as? Data else { return nil }
80+
81+
return data
82+
}
6683

84+
var json: Any? {
85+
guard let data = data,
86+
let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil }
87+
88+
return json
89+
}
6790
}

BuckoNetworking/BuckoNetworking.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
@import Foundation;
1010
@import Alamofire;
11+
@import PromiseKit;
1112

1213
//! Project version number for BuckoNetworking.
1314
FOUNDATION_EXPORT double BuckoNetworkingVersionNumber;

BuckoNetworking/Protocols/DecodableEndpoint.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
// Copyright © 2017 Teeps. All rights reserved.
77
//
88

9-
import Alamofire
10-
119
public protocol DecodableEndpoint: Endpoint {
1210
associatedtype ResponseType: Decodable
1311
}
@@ -34,4 +32,13 @@ public extension DecodableEndpoint {
3432

3533
return request
3634
}
35+
36+
public func request() -> Promise<ResponseType> {
37+
return Bucko.shared.requestData(endpoint: self).then { data in
38+
return Promise { seal in
39+
let result = try JSONDecoder().decode(ResponseType.self, from: data)
40+
seal.fulfill(result)
41+
}
42+
}
43+
}
3744
}

BuckoNetworking/Protocols/Endpoint.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,13 @@ public extension Endpoint {
8484

8585
return request
8686
}
87+
88+
public func request<T: Decodable>(responseType: T.Type) -> Promise<T> {
89+
return Bucko.shared.requestData(endpoint: self).then { data in
90+
return Promise { seal in
91+
let result = try JSONDecoder().decode(T.self, from: data)
92+
seal.fulfill(result)
93+
}
94+
}
95+
}
8796
}

Cartfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
github "Alamofire/Alamofire" ~> 4.4
2-
1+
github "Alamofire/Alamofire" ~> 4.6.0
2+
github "mxcl/PromiseKit" ~> 6.1.0

Cartfile.resolved

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
github "Alamofire/Alamofire" "4.6.0"
2+
github "mxcl/PromiseKit" "6.1.0"

README.md

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ After developing a number of applications, we noticed that everyone's networking
88
### Dependencies
99
------
1010

11-
We ended up going with [Alamofire](https://github.com/Alamofire/Alamofire) instead of `URLSession` for a few reasons. Alamofire is asynchronous by nature, has session management, reduces boilerplate code, and is very easy to use.
11+
* [Alamofire](https://github.com/Alamofire/Alamofire) - We ended up going with Alamofire instead of `URLSession` for a few reasons. Alamofire is asynchronous by nature, has session management, reduces boilerplate code, and is very easy to use.
12+
13+
* [PromiseKit](https://github.com/mxcl/PromiseKit) - We use Promises because they simplify asynchronous programming and separate successful and failed responses, allowing you to focus on each part in their own individual closures.
1214

1315
### Installation
1416
------
@@ -33,7 +35,7 @@ github "teepsllc/BuckoNetworking" ~> 2.0.0
3335
```
3436

3537
1. Run `carthage update --platform iOS --no-use-binaries` to build the framework.
36-
1. On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop `BuckoNetworking.framework` from the [Carthage/Build]() folder on disk. You will also need to drag `Alamofire.framework` into your project.
38+
1. On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop `BuckoNetworking.framework` from the [Carthage/Build]() folder on disk. You will also need to drag `Alamofire.framework` and `PromiseKit.framework` into your project.
3739
1. On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script in which you specify your shell (ex: `/bin/sh`), add the following contents to the script area below the shell:
3840

3941
```sh
@@ -45,6 +47,7 @@ github "teepsllc/BuckoNetworking" ~> 2.0.0
4547
```
4648
$(SRCROOT)/Carthage/Build/iOS/BuckoNetworking.framework
4749
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
50+
$(SRCROOT)/Carthage/Build/iOS/PromiseKit.framework
4851
```
4952
This script works around an [App Store submission bug](http://www.openradar.me/radar?id=6409498411401216) triggered by universal binaries and ensures that necessary bitcode-related files and dSYMs are copied when archiving.
5053

@@ -91,10 +94,7 @@ $ pod install
9194

9295
Swift 4 introduced the Codable protocol and the `DecodableEndpoint` in BuckoNetworking uses this to the max!
9396

94-
9597
```swift
96-
import BuckoNetworking
97-
9898
struct User: Decodable {
9999
var name: String
100100
var phoneNumber: String
@@ -107,17 +107,29 @@ struct User: Decodable {
107107

108108
struct UserService: DecodableEndpoint {
109109
typealias ResponseType = User
110-
var baseURL: String = "https://example.com/"
111-
var path: String = "users/"
112-
var method: HTTPMethod = .post
113-
var parameters: Parameters {
114-
var parameters = Parameters()
115-
parameters["first_name"] = "Bucko"
116-
return parameters
110+
var baseURL: String { return "https://example.com" }
111+
var path: String { return "/users" }
112+
var method: HTTPMethod { return .get }
113+
var body: Parameters { return Parameters() }
114+
var headers: HTTPHeaders { return HTTPHeaders() }
115+
}
116+
117+
UserService().request().then { users in
118+
// Do something with users
119+
users.count
120+
}.catch { error in
121+
122+
if let json = error.json {
123+
// Use json
124+
} else {
125+
// Some other error occurred that doesn't include json
117126
}
118-
var headers: HttpHeaders = ["Authorization" : "Bearer SOME_TOKEN"]
119127
}
128+
```
129+
130+
If you don't want to use Promises, BuckoNetworking also provides normal closures:
120131

132+
```swift
121133
UserService().request { (user, error) in
122134
guard let user = user else {
123135
// Do Error
@@ -143,6 +155,20 @@ enum UserService: Endpoint {
143155
var headers: HTTPHeaders { return HTTPHeaders() }
144156
}
145157

158+
// Use your Endpoint
159+
UserService.index.request(responseType: [User].self).then { users in
160+
// Do something with users
161+
users.count
162+
}.catch { error in
163+
164+
if let json = error.json {
165+
// Use json
166+
} else {
167+
// Some other error occurred that doesn't include json
168+
}
169+
}
170+
171+
// Or without Promises
146172
UserService.index.request(responseType: [User].self) { (users, error) in
147173
guard let users = users else {
148174
// Do Error
@@ -163,7 +189,7 @@ If you don't want to use `Codable`, you can instead use the `Endpoint` protocol,
163189
```swift
164190
import BuckoNetworking
165191

166-
// Create an endpoint
192+
// Create an Endpoint
167193
struct UserCreateService: Endpoint {
168194
var baseURL: String = "https://example.com/"
169195
var path: String = "users/"
@@ -176,11 +202,18 @@ struct UserCreateService: Endpoint {
176202
var headers: HttpHeaders = ["Authorization" : "Bearer SOME_TOKEN"]
177203
}
178204

179-
// Use your endpoint
205+
// Use your Endpoint
206+
Bucko.shared.request(UserCreateService()).then { response in
207+
// Response successful!
208+
}.catch { error in
209+
// Failure
210+
}
211+
212+
// Or without Promises
180213
Bucko.shared.request(UserCreateService()) { response in
181214
if response.result.isSuccess {
182215
// Response successful!
183-
let json = Json(response.result.value!)
216+
// Convert `response.result.value!` to JSON
184217
} else {
185218
// Failure
186219
}
@@ -193,7 +226,7 @@ Bucko.shared.request(UserCreateService()) { response in
193226
```swift
194227
import BuckoNetworking
195228

196-
// Create an endpoint
229+
// Create an Endpoint
197230
enum UserService {
198231
case getUsers
199232
case getUser(id: String)
@@ -242,11 +275,18 @@ extension UserService: Endpoint {
242275
}
243276
}
244277

245-
// Use your endpoint
278+
// Use your Endpoint
279+
Bucko.shared.request(UserService.getUser(id: "1")).then { response in
280+
// Response successful!
281+
}.catch { error in
282+
// Failure
283+
}
284+
285+
// Or without Promises
246286
Bucko.shared.request(UserService.getUser(id: "1")) { response in
247287
if response.result.isSuccess {
248288
// Response successful!
249-
let json = Json(response.result.value!)
289+
// Convert `response.result.value!` to JSON
250290
} else {
251291
// Failure
252292
}

0 commit comments

Comments
 (0)