Skip to content

Commit

Permalink
Merge pull request #5 from teepsllc/feature/promises
Browse files Browse the repository at this point in the history
Promises
  • Loading branch information
chayelheinsen authored Feb 21, 2018
2 parents d8d5e16 + 07876ff commit 214b903
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 44 deletions.
8 changes: 8 additions & 0 deletions BuckoNetworking.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
6ABB45021EF86C0900E344AF /* DecodableEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A7D02921E4CC12900E66FD0 /* DecodableEndpoint.swift */; };
6ABB45041EF86DE200E344AF /* BuckoNetworking.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A7D02751E4CBEFC00E66FD0 /* BuckoNetworking.h */; settings = {ATTRIBUTES = (Public, ); }; };
6AEC6FDE1E84B05500BBD3F7 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AEC6FDC1E84B05500BBD3F7 /* Alamofire.framework */; };
6AFA9DED1FF68A0D00FDC188 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AFA9DEC1FF68A0D00FDC188 /* PromiseKit.framework */; };
6AFA9DEF1FF68A3400FDC188 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AFA9DEE1FF68A3400FDC188 /* PromiseKit.framework */; };
/* End PBXBuildFile section */

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

/* Begin PBXFrameworksBuildPhase section */
6A7D02431E4CB6FE00E66FD0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6AFA9DED1FF68A0D00FDC188 /* PromiseKit.framework in Frameworks */,
6AEC6FDE1E84B05500BBD3F7 /* Alamofire.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -83,6 +88,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6AFA9DEF1FF68A3400FDC188 /* PromiseKit.framework in Frameworks */,
6ABB44FD1EF8661100E344AF /* Alamofire.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -152,6 +158,8 @@
6A7D02D41E4CC5E900E66FD0 /* Frameworks */ = {
isa = PBXGroup;
children = (
6AFA9DEC1FF68A0D00FDC188 /* PromiseKit.framework */,
6AFA9DEE1FF68A3400FDC188 /* PromiseKit.framework */,
6ABB44FA1EF8661100E344AF /* SwiftyJSON.framework */,
6ABB44FB1EF8661100E344AF /* Alamofire.framework */,
6AEC6FDB1E84B05500BBD3F7 /* SwiftyJSON.framework */,
Expand Down
52 changes: 40 additions & 12 deletions BuckoNetworking/Bucko.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@ public protocol BuckoErrorHandler: class {

public typealias BuckoResponseClosure = ((DataResponse<Any>) -> Void)
public typealias BuckoDataResponseClosure = ((DataResponse<Data>) -> Void)
@available(*, deprecated, message: "Use HTTPMethod instead")
public typealias HttpMethod = HTTPMethod
@available(*, deprecated, message: "Use HTTPHeaders instead")
public typealias HttpHeaders = HTTPHeaders
@available(*, deprecated, message: "Use ParameterEncoding instead")
public typealias Encoding = ParameterEncoding
@available(*, deprecated, message: "Use URLEncoding instead")
public typealias UrlEncoding = URLEncoding
@available(*, deprecated, message: "Use JSONEncoding instead")
public typealias JsonEncoding = JSONEncoding
@available(*, deprecated, message: "Use Parameters instead")
public typealias Body = Parameters

public struct Bucko {
/**
Expand Down Expand Up @@ -155,4 +143,44 @@ public struct Bucko {
print(request.description)
return request
}

public func request(endpoint: Endpoint) -> Promise<DataResponse<Any>> {
return Promise { seal in
request(endpoint: endpoint) { response in

if response.result.isSuccess {
seal.fulfill(response)
} else {
if let responseError = response.result.value {
do {
let json = try JSONSerialization.data(withJSONObject: responseError, options: [])
seal.reject(BuckoError(apiError: json))
} catch {
seal.reject(response.result.error!)
}
} else {
seal.reject(response.result.error!)
}
}
}
}
}

public func requestData(endpoint: Endpoint) -> Promise<Data> {
return Promise { seal in
requestData(endpoint: endpoint) { response in

if response.result.isSuccess {
seal.fulfill(response.result.value!)
} else {

if let responseError = response.result.value {
seal.reject(BuckoError(apiError: responseError))
} else {
seal.reject(response.result.error!)
}
}
}
}
}
}
41 changes: 32 additions & 9 deletions BuckoNetworking/BuckoError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
// Copyright © 2017 Teeps. All rights reserved.
//

import Foundation.NSError
private let buckoErrorData = "BuckoErrorData"

public final class BuckoError: NSError {

fileprivate enum Code: Int {
private enum Code: Int {
case api = 1
case validation
case service // an error caused by a third party service
Expand All @@ -19,7 +18,7 @@ public final class BuckoError: NSError {
case unknown
}

fileprivate static var domain: String {
private static var domain: String {
return Bundle.main.bundleIdentifier!
}

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

convenience init(apiError data: Data) {
self.init(code: .api, reason: "API Error", description: nil, data: data)
}

convenience init(validationError reason: String, description: String? = nil) {
self.init(code: .validation, reason: reason, description: description)
}
Expand All @@ -48,11 +51,16 @@ public final class BuckoError: NSError {
self.init(code: .auth, reason: reason, description: description)
}

fileprivate convenience init(code: Code, reason: String, description: String? = nil) {
self.init(domain: BuckoError.domain, code: code.rawValue, userInfo: [
NSLocalizedFailureReasonErrorKey: reason,
NSLocalizedDescriptionKey: description ?? reason
])
private convenience init(code: Code, reason: String, description: String? = nil, data: Data? = nil) {
self.init(
domain: BuckoError.domain,
code: code.rawValue,
userInfo: [
NSLocalizedFailureReasonErrorKey: reason,
NSLocalizedDescriptionKey: description ?? reason,
buckoErrorData: data ?? NSNull()
]
)
}

// MARK: - Common errors
Expand All @@ -63,5 +71,20 @@ public final class BuckoError: NSError {
static func unknown() -> BuckoError {
return BuckoError(code: .unknown, reason: "An unknown error occurred")
}
}

public extension Error {
var data: Data? {
let error = self as NSError
guard let data = error.userInfo[buckoErrorData] as? Data else { return nil }

return data
}

var json: Any? {
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) else { return nil }

return json
}
}
1 change: 1 addition & 0 deletions BuckoNetworking/BuckoNetworking.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

@import Foundation;
@import Alamofire;
@import PromiseKit;

//! Project version number for BuckoNetworking.
FOUNDATION_EXPORT double BuckoNetworkingVersionNumber;
Expand Down
11 changes: 9 additions & 2 deletions BuckoNetworking/Protocols/DecodableEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
// Copyright © 2017 Teeps. All rights reserved.
//

import Alamofire

public protocol DecodableEndpoint: Endpoint {
associatedtype ResponseType: Decodable
}
Expand All @@ -34,4 +32,13 @@ public extension DecodableEndpoint {

return request
}

public func request() -> Promise<ResponseType> {
return Bucko.shared.requestData(endpoint: self).then { data in
return Promise { seal in
let result = try JSONDecoder().decode(ResponseType.self, from: data)
seal.fulfill(result)
}
}
}
}
9 changes: 9 additions & 0 deletions BuckoNetworking/Protocols/Endpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,13 @@ public extension Endpoint {

return request
}

public func request<T: Decodable>(responseType: T.Type) -> Promise<T> {
return Bucko.shared.requestData(endpoint: self).then { data in
return Promise { seal in
let result = try JSONDecoder().decode(T.self, from: data)
seal.fulfill(result)
}
}
}
}
4 changes: 2 additions & 2 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "Alamofire/Alamofire" ~> 4.4

github "Alamofire/Alamofire" ~> 4.6.0
github "mxcl/PromiseKit" ~> 6.1.0
1 change: 1 addition & 0 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
github "Alamofire/Alamofire" "4.6.0"
github "mxcl/PromiseKit" "6.1.0"
78 changes: 59 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ After developing a number of applications, we noticed that everyone's networking
### Dependencies
------

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.
* [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.

* [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.

### Installation
------
Expand All @@ -33,7 +35,7 @@ github "teepsllc/BuckoNetworking" ~> 2.0.0
```

1. Run `carthage update --platform iOS --no-use-binaries` to build the framework.
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.
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.
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:

```sh
Expand All @@ -45,6 +47,7 @@ github "teepsllc/BuckoNetworking" ~> 2.0.0
```
$(SRCROOT)/Carthage/Build/iOS/BuckoNetworking.framework
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/PromiseKit.framework
```
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.

Expand Down Expand Up @@ -91,10 +94,7 @@ $ pod install

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


```swift
import BuckoNetworking

struct User: Decodable {
var name: String
var phoneNumber: String
Expand All @@ -107,17 +107,29 @@ struct User: Decodable {

struct UserService: DecodableEndpoint {
typealias ResponseType = User
var baseURL: String = "https://example.com/"
var path: String = "users/"
var method: HTTPMethod = .post
var parameters: Parameters {
var parameters = Parameters()
parameters["first_name"] = "Bucko"
return parameters
var baseURL: String { return "https://example.com" }
var path: String { return "/users" }
var method: HTTPMethod { return .get }
var body: Parameters { return Parameters() }
var headers: HTTPHeaders { return HTTPHeaders() }
}

UserService().request().then { users in
// Do something with users
users.count
}.catch { error in

if let json = error.json {
// Use json
} else {
// Some other error occurred that doesn't include json
}
var headers: HttpHeaders = ["Authorization" : "Bearer SOME_TOKEN"]
}
```

If you don't want to use Promises, BuckoNetworking also provides normal closures:

```swift
UserService().request { (user, error) in
guard let user = user else {
// Do Error
Expand All @@ -143,6 +155,20 @@ enum UserService: Endpoint {
var headers: HTTPHeaders { return HTTPHeaders() }
}

// Use your Endpoint
UserService.index.request(responseType: [User].self).then { users in
// Do something with users
users.count
}.catch { error in

if let json = error.json {
// Use json
} else {
// Some other error occurred that doesn't include json
}
}

// Or without Promises
UserService.index.request(responseType: [User].self) { (users, error) in
guard let users = users else {
// Do Error
Expand All @@ -163,7 +189,7 @@ If you don't want to use `Codable`, you can instead use the `Endpoint` protocol,
```swift
import BuckoNetworking

// Create an endpoint
// Create an Endpoint
struct UserCreateService: Endpoint {
var baseURL: String = "https://example.com/"
var path: String = "users/"
Expand All @@ -176,11 +202,18 @@ struct UserCreateService: Endpoint {
var headers: HttpHeaders = ["Authorization" : "Bearer SOME_TOKEN"]
}

// Use your endpoint
// Use your Endpoint
Bucko.shared.request(UserCreateService()).then { response in
// Response successful!
}.catch { error in
// Failure
}

// Or without Promises
Bucko.shared.request(UserCreateService()) { response in
if response.result.isSuccess {
// Response successful!
let json = Json(response.result.value!)
// Convert `response.result.value!` to JSON
} else {
// Failure
}
Expand All @@ -193,7 +226,7 @@ Bucko.shared.request(UserCreateService()) { response in
```swift
import BuckoNetworking

// Create an endpoint
// Create an Endpoint
enum UserService {
case getUsers
case getUser(id: String)
Expand Down Expand Up @@ -242,11 +275,18 @@ extension UserService: Endpoint {
}
}

// Use your endpoint
// Use your Endpoint
Bucko.shared.request(UserService.getUser(id: "1")).then { response in
// Response successful!
}.catch { error in
// Failure
}

// Or without Promises
Bucko.shared.request(UserService.getUser(id: "1")) { response in
if response.result.isSuccess {
// Response successful!
let json = Json(response.result.value!)
// Convert `response.result.value!` to JSON
} else {
// Failure
}
Expand Down

0 comments on commit 214b903

Please sign in to comment.