TestsTested | ✗ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Aug 2016 |
SPMSupports SPM | ✗ |
Maintained by Jason Welch.
Depends on: | |
Alamofire | >= 3.4 |
SwiftyJSON | >= 2.3 |
BrightFutures | >= 4.1 |
Result | >= 2.0 |
Your data models can be easily Created, Read, Updated, Deleted, and Enumerated from a remote server simply by inheriting from CRUDE’s various protocols. CRUDE-Futures leverages BrightFutures to asychronously load your data models, making requests with the help of Alamofire and mapping the returned JSON with SwiftyJSON.
Protocol | Path Override | Request Type | Convenience Method | Returns |
---|---|---|---|---|
CRUDECreatable | createPath | POST | createOnServer | Self |
createOnServerOkay | Okay | |||
CRUDEReadable | readPath | GET | readFromServer | Self |
readFromServerWithId | Self | |||
CRUDEUpdatabe | updatePath | PUT | updateOnServer | Self |
updateOnServerOkay | Okay | |||
CRUDEDeletable | deletePath | DELETE | deleteFromServer | Okay |
CRUDEEnumeratable | enumeratePath | GET | enumerateFromServer | [Self] |
CRUDE will only work for API calls returning JSON.
Crude-Futures makes use of the following pods…
CRUDE-Futures is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'CRUDE-Futures', '~> 0.1'
The first and most important step is setting up CRUDE for use in your app. import CRUDE_FUTURES
in your AppDelegate
, then call configure
within application(application: didFinishLaunchingWithOptions:)
. For example:
// AppDelegate
CRUDE.configure(baseURL: "https://mysite.com/api", headers: kDefaultHeaders)
If you would like CRUDE to do some kind of logging whenever API calls are made, you can provide a CRUDEResponseLog
block. This can be done through a variable:
let myLogger: CRUDEResponseLog = { response in
if let error = response.result.error {
print("CRUDE FAILURE: \(error.localizedDescription)")
} else
let method = response.request?.HTTPMethod ?? "UNKNOWN"
let urlString = response.request?.URLString ?? "unknown"
print("\(network.response!.statusCode) from \(method) \(urlString)")
}
}
CRUDE.configure(baseURL: "https://mysite.com/api", headers: kDefaultHeaders, responseLoggingBlock: myLogger)
…or by providing the block at the end of your configure call:
CRUDE.configure(baseURL: "https://mysite.com/api", headers: kDefaultHeaders) { response in
print("CRUDE response: \(response)")
}
You can also set the response logging block after configuring CRUDE using the CRUDE.setResponseLoggingBlock
function. You can even set a logger to fire before the request is made using the CRUDE.setRequestLoggingBlock
function. For example:
CRUDE.setRequestLoggingBlock() { method, path, params, headers in
print("CRUDE request \(method) \(path)")
params?.forEach { print("\($0)=\($1)") }
print("HTTP Headers:")
headers.forEach { print("\($0)=\($1)") }
}
If you don’t provide a logging block, a default logger can be used by setting
CRUDE.shouldUseDefaultLogger
totrue
.
While CRUDE is intended for use with structs, it can be used with classes and even managed objects (to an extent).
The first thing you need to do is import CRUDE_Futures
and SwiftyJSON
. Then you state how you intend to you use your model by applying any number of protocols. For a read-only model, you might just use CRUDEReadable
or maybe add CRUDEEnumeratable
if you want to retrieve a bunch at a time. Data entities can be created and destroyed through the use of CRUDECreatable
and CRUDEDeletable
. If you want modify your entities and demand that the server conforms to the new reality you have forged, you can do so with CRUDEUpdatable
.
All of these different protocols conform to CRUDERequestable
, which require a model to set its path
string to let CRUDE know where those models can typically be found. For example static let path = "people"
will tell CRUDE to send requests to “https://mysite.com/api/people”. Optionally, a model can also set an objectKey
string to use whenever the returning JSON has encased all the precious attributes in dictionary with a single key. If you do not set this in your model, it will default to nil
.
In order to easily convert some JSON into a nifty model, it needs to be JSONConvertable
. This means it can be initialized by passing it a JSON
object. Here is what a Person model object might look like:
struct Person: CRUDEReadable {
static let path: String = "person"
let id: Int
let firstName: String
let lastName: String
let favoriteColor: String?
init(_ json: JSON) {
id = json["id_Number"].intValue
firstName = json["first_name"].stringValue
lastName = json["last_name"].stringValue
favoriteColor = json["favorite_color"].string
}
}
Yes, you do have to do all of that one-toone mapping but it can pay off. Let’s say you have a Household
entity that has several people. It can map its people
attribute like so:
people = json["people"].array?.map { Person($0) }.sort { $0.firstName < $1.firstName} ?? []
Look at that! We even sorted them by their first names all on one line! If you are going to making all of your requests and mapping through Household
and not Person
, you can have Person
just adhere to JSONConvertable
.
If you have a model that is going to be doing all the things, you can use CRUDEMappable
in lieu of listing out all five.
While you don’t need the
id
property for creating and enumerating models, it is required forCRUDEReadable
,CRUDEUpdatable
, andCRUDEDeletable
. This is to automatically infer url paths. For example, requesting a person with the id number12345
would go out to “https://mysite.com/api/people/12345”. You can override this path if you like (explained later) but theid
requirement remains.
For updating your remote database with an entity, it will need to be JSONAttributable
. This means it has an inverse mapping of its properties.
var attributes: [String : AnyObject?] {
return [
"id_Number": id,
"first_name": firstName,
"last_name": lastName,
"favorite_color": favoriteColor
]
}
Notice that attributes
contains optional objects. JSONAttributable
provides a computed property called nullifiedAttributes
that will give any nil attributes a value of NSNull
, like potentially favoriteColor
. It also provides a computed property called valuedAttributes
that will automatically remove attributes that don’t have values.
In the example of the Household
model, an entity would contain "people": people.map { $0.nullifiedAttributes }
when computing its attributes
.
CRUDE assumes that your API only updates attributes for parameters it receives and therefore requires an NSNull value for attributes that have been set to nil. If that is not the case (no news is null news) then you can make use of
valuedAttributes
and make that stipulation when updating like so:thisPerson.updateOnServer(valuedAttributesOnly: true)
So what does an API call look like? Some protocols provide static requests, some instance requests, and some both. For instance if you want to get a new person with an id number of 12345:
Person.readFromServerWithId(12345)
…or if you have that person and you just want to make sure you have to most up-to-date version:
self.person.readFromServer()
Keep in mind that CRUDE does not mutate entities when making these requests. Rather it provides a new entity upon completion. Asynchronous calls are handled using the BrightFutures syntax, so a retrieval of the newest version of a person might look like this:
self.person.readFromServer().onSuccess { person in
self.person = person
}.onFailure { error in
print(error)
}.onComplete { _ in
self.refreshControl?.endRefreshing()
}
Notice that while .onComplete
provides a raw Result
, you can dump that if you just want to use this block for clean up code. In this example, we update the view’s refreshControl whether the load was successful or not.
The mappable protocols provide convenience requests for you the explicitly call based on your intent, but you do have access the underlying requests that they use. The most basic method is request
, which will give you a Future with a JSON object.
CRUDE.request(.GET, CRUDE.baseURL + "person/\(self.person.id)")
If you would like the control of a direct request but don’t want the hassle of converting that JSON into an entity, you can use requestObject
for one entity or requestObjectsArray
for an array of entities. Just make sure you cast the returning Future with the desired object.
let request = CRUDE.requestObjectsArray(.GET, CRUDE.baseURL + "person/\(self.person.id)", parameters: queryItems) as Future<[Person], NSError>
request.onSuccess { people in
self.household.people = people
}
Okay
is an empty object with the sole purpose to have something to return .onSuccess
. This is used by any model that is CRUDEDeletable
, since there shouldn’t be any mappable JSON coming back from a DELETE request.
If you want to make a request and you don’t care what is coming back from the server you can use CRUDE.requestForSuccess
, which will also return an Okay
.
CRUDE assumes a simple API structure in which requests relating to a model are made. If your baseURL is “https://mysite.com/api/” then requests for Person
objects should look like this:
As mentioned earlier, all of this is done automatically with the use of path
. However, your API may not be quite so simple. Perhaps an update goes to “https://mysite.com/api/household/people/12345” and retrieving a specific person comes from “https://mysite.com/api/person/12345”. Each of the five protocols has a specific path that you can set for requests of that type. So in this scenario, you would set a value for updatePath
and readPath
, letting path
handle the other three cases.
You can set a specific path staticly:
static let enumeratePath = CRUDE.baseURL + "/household/people"
…or dynamically:
static var enumeratePath: String {
return CRUDE.baseURL + "/households/\(householdID)/people"
}
Always set
path
, providing specific paths if you have any edge cases.
If you want to be able to control the request traffic itself, you can use CRUDERequest
objects instead of the CRUDE
static methods.
Initialize a CRUDERequest
instance the same way you would use the request function. The urlString
is a must, with the option to provide parameters
and/or headers
. To execute the request, you have three options very similar to the three basic CRUDE
static functions…
makeRequestForJSON
instead of request
makeRequestForObject<T: JSONConvertable>
instead of requestObject<T: JSONConvertable>
makeRequestForObjectsArray<T: JSONConvertable>
instead of requestObjectsArray<T: JSONConvertable>
While the request is running, you can use pauseRequest()
to take a break. Then either resumeRequest()
later or give up on it and cancelRequest()
.
You still need to configure CRUDE in your AppDelegate first.
If you want do even more of the work yourself… well then you probably shouldn’t be using this pod. The added conveniences are lost on you. Do you also put on rollerskates to go for a jog? Is your favorite restaurant The Melting Pot^, where you rent kitchen utensils to cook your own dinner? Sure they did a little grocery shopping for you, but really you are doing the bulk of the work.
Just use Alamofire, SwiftyJSON, and BrightFutures. That is what I did in order to put together a bunch of tools you aren’t going to use.
Jason Welch, [email protected]
CRUDE-Futures is available under the MIT license. See the LICENSE file for more info.