Continuing where we left off in Bite #93 with our Alamofire Router, today we'll take a look at creating a Custom Response Serializer.
These are the mechanisms through which the Alamofire HTTP library translates the HTTP responses it receives into types that are more friendly to work with.
Alamofire ships with a few built-in serializers for common things like raw data, text, JSON and property lists. Today we'll be taking a look at adding our own. Let's get started.
We want to make it super easy to translate Alamofireresponses into structs that conform to the Decodableprotocol from the Decodable JSON parsing library. (Bite #86).
First we'll extend Request and add a new genericresponseCollectionfunction that returns a Response struct.
Notice that in the generic part of the function definition we allow any type T that confirms to the Decodable protocol.
Now the fun part. We'll first serialize the response using the built-in JSON response serializer, then switch on its result, returning the failure case if it exists.
If the JSON was parsed successfully, we'll try to decode using Decodable.
Now we can try both our Routerand our new custom response serializer out together:
The responseCollectionfunction handles arrays of objects. Making a responseObjectserializer function to handle single object responses is almost identical. Simply replace all occurrences of [T] with T.
Alamofire 3.0 will be released soon so now is a good time to start looking at how best to build an API client for our app. We'll be covering different areas of this topic periodically over the next few weeks.
Today we'll start by looking at one part of the equation: routing.
We've talked about routing before in the context of iOS URL schemes and libraries like JLRoutes (Bite #62). This is a bit different. We'll be creating a Router type that will help us unify and simplify our code when we make requests by generating URL requests from enum cases.
We'll start by declaring a new enum type that conforms to the URLRequestConvertableprotocol (which comes from Alamofire). Then we'll define a few different cases that each correspond to an endpoint on our HTTP API. We'll add a computed route property that returns a tuple to translate from our enum cases into URL path and parameter values.
Finally, we'll finish conforming to URLRequestCovertable by adding a computed property that encodes our URL and parameters into an NSMutableURLRequest. Neat.
Now we can use our new Router to write some super clean Alamofirerequest code like this:
Generics were new to many iOS developers when they debuted in Swift 1.0 last year. They are a way of specifying parts of your code can work with different kinds of objects of a given type. but you don't want to hardcode one single type. What if you wanted to say something like βthis function accepts any object of some type and returns an object of that same type, but I don't care what type that is.β It ends up looking like this:
funcaGenericFunction<SomeType>(thing:SomeType)->SomeType{returnthing}aGenericFunction("Hello")// returns the String "Hello"
Whoa! Where did those < > brackets come from? That's how you tell the Swift compiler that you are writing a generic function. The SomeType can be any name you want, and you can use it in your code in place of a concrete type. What if you wanted your new generic function to only accept types that conformed to the a protocol? Simple:
Functions are great, but what about types themselves? The concepts behind Generics work exactly the same there. Let's look at this by making a generic DataSource struct that will power a table view controller:
letsomething=Product()switchsomething{caseisProduct:print("Found a product")caseletpersonasPerson:print("Found a person: \(person.name)")default:print("Found an unknown thing.")}
Where
letcontentOffset=(0,30.0)switchcontentOffset{caselet(_,y)wherey<0:print("Scrolled up")caselet(_,y)where(0...60)~=y:print("scrolling top area")default:println("just scrolling")}
Swift 2
New in Swift 2, you can now use any Swift pattern as the clause of an if statement:
letcomments=7ifcase1...10=commentCount{print("some comments")}elseifcase11..100=commentCount{print("lots of comments")}
And in Swift 2's new error handling:
do{trysend(message:"Hello")}catchSendError.Offline{print("user is offline")}catchSendError.RateLimited{print("stop spamming")}catchSendError.Blocked(letreason){print("user was blocked because \(reason)")}
Protocol Extensions are a new feature of Swift 2. They allow you to add new functions (complete with implementations) to any class that implements a protocol. The functionality is best explained with a simple example. Letβs say you wanted to add a way to shuffle arrays.
In Swift 1.2 and earlier, youβd probably add a shuffle function using something like this:
As you can see here, we're also able to use Swiftβs fantastic pattern-matching support to limit our extension to only mutable collections that use an Int as their index.
So, we're crafting our brand new view controller and BAM! we see this:
Let's try to break down how we got here:
Subclasses only inherit their superclass's initializersif certain conditions are met.
If our subclass doesn't define any designated initializers, it automatically inherits all of its superclass's designated initializers.
If not though, then it inherits none of them.
We subclassed UIViewController.
UIViewController implements NSCoding.
NSCoding requires init(coder:) be implemented.
By default UIViewController implements init(coder:) for us, and we don't need to do a thing.
But now, we defined our own new designated initalizer called init(), so we've stopped meeting the conditions for inheriting all of our superclass's designated initializers, and now have to override, implement and properly call super on init(coder:) all by ourselves!