Christos Sotiriou

REST Client in Swift with Promises

Diving deeper into Swift, I am examining different ways of improving my architecture, and making the best decisions when creating new applications. For me, architecture and expressiveness in the code is much more important that a complicated algorithm that boosts performance over 100%.

One thing I am called to do very often, is set up an HTTP client that will handle network operations, access tokens, and perform bandwidth throttling. So, I would like to share with you my idea of approaching the creation of an HTTP client with Swift.

PromiseKit for making your code more… promising! I am a very strong applicant of promises, both in Javascript and iOS, and I strongly recommend it for making asynchronous code clearer and more scalable, without compromising flexibility.

Lets suppose you have this kind of API:

JavaScript

1

2

3

4

5

{

"error":null,

"response":"…",

"success":true

}

Let’s suppose that all API responses have this format where:

Error holds an explanation of the error that happened in the server (missing arguments, invalid tokens, etc). Let’s imagine that error is an object holds two values: “errorMessage” and “errorCode”, both strings that are returned from the server.

Response holds an object indicating the response depending on the request (“for example, the response can be an array of objects, indicating a product list, or the user’s profile details)

Success is a boolean indicating that the operation went well. If success is “false” the “error” will hold an explanation of what went wrong.

For this example, I opted to use the root object containing ‘success’, ‘error’ and ‘response’, since I usually like to distinguish between logical errors, and server errors. For example, trying to login with invalid credentials is a logical error, and would return ‘false’ in the ‘success’ field, with an error object in the ‘error’ field. And an invalid kind of argument given, or any server error, would be an error 500, or something similar. Everything else, is a success, accompanied by a ‘response’ object.

Simple HTTP client implementation (code)

We will use a simple Swift Enum as the router for our application. By having an enum, we can have autocompletion for our server routes, and avoid redundant duplicate strings when trying to access a server resource / path.

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

publicenumHTTPRouter{

private staticletbaseURLString="http://example.com/mobileapi"

caseArticles

caseCheckSessionStatus

public varURLString: String{

letpath:String={

switchself{

case.Articles:

return"feed/Articles"

}

}()

returnHTTPRouter.baseURLString+path;

}

}

Before we start implementing our HTTP Client, we should make the most basic parseable object. Here’s what we know so far, that will play a role into our model design:

The base response is the same. There is always an “error”, “response” and “success” property in the response.

Those base properties are only used to determine if there is an error during the request to the server. If the request is successful, we don’t need them anymore.

If the request is successful, what we care about, is the object contained inside the “response” object.

If the request fails, we show the error, and we don’t call the “success” callback, in case we had any.

Based on these assumptions, let’s begin modelling our response objects using ObjectMapper’s functionality.

We model the basic response as a generic, since the “response” object can be any type of object. Node that the type of T is defined to be a Mappable object. This is by design. It is our responsibility to provide mappable objects to the “response” objects in order for it to be parsed.

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

classAPIResponse<T:Mappable>: Mappable{

varsuccess:Bool=false

varresponse:T?

varerror:APIError?

required init?(_map:Map){

}

funcmapping(map:Map){

success<-map["success"]

response<-map["response"]

error<-map["error"]

}

}

classAPIError: Mappable

{

varerrorName:String?

varerrorCode:String?

required init?(_map:Map){

}

funcmapping(map:Map){

errorName<-map["errorMessage"]

errorCode<-map["errorCode"]

}

}

We also define an error class. Although I tend to refrain myself from subclassing NSError, in this case, I opted to do it, in order to help me explain better this use case.

How it works

The general concept is that when downloading the data, it is parsed and made into an object, which is then returned inside a Promise. The “magic” happens when determining the class and kind of parsing for the object to be returned.

This is done through specialising the T parameter when calling HTTPClient’s ‘get()’ or ‘post()’. By specializing it, the mapper knows which mapping class to call when downloading the data.

Consider the following JSON:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

{

"success":true,

"error":null,

"response":{

"total":102314,

"articles":[

{

"title":"Article title 1",

"author":"Billy bob",

"date":115948273

},

{

"title":"Article title 2",

"author":"Benny bee",

"date":1152948273

},

{

"title":"Article title 3",

"author":"Jenny Kais",

"date":115948273

},

{

"title":"Article title 4",

"author":"Harboreus Me",

"date":115948273

}

]

}

}

If we want to map it, we can map it like this, with ObjectMapper;

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

classAPIArticle: Mappable{

vartitle:String?

varauthor:String?

vardate:NSDate?

required init?(_map:Map){

}

funcmapping(map:Map){

title<-map["title"]

author<-map["author"]

date<-map["date"]

}

}

classAPIResponseArticles: Mappable{

vartotal:Int=0

vararticles=[APIArticle]()

required init?(_map:Map){

}

funcmapping(map:Map){

articles<-map["articles"]

total<-map["total"]

}

}

We need to only map the “response” content. Remember that the outer element, the one which contains “success” and “error” is already parsed. It will be used to determine if an error has happened when the JSON arrives, but after that step, we only need the content of the ‘response’ object to be returned to us.

We can now use our HTTPClient, and parse our first response.

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//make a request to get the articles, then specify in the 'then' block that you want the server response

//to be parsed as 'APIResponseArticles'

//You need to specify it, otherwise the compiler will not know exactly how to parse the response, and will

//not compile the code.

//The 'response' is always optional. Some APIs can return 'null' as a valid response, making you to rely on the

//value of the status (200), or, in this case, the server may respond with success = true, but with no content

Conclusion

You can now chain HTTP requests, automatically parse the responses, and have a nice and clean HTTP client as the basis of all your requests. I suppose that this methodology can work for SOAP Requests, too, with a few modifications.

Depending on the structure of your API, you will probably have to change your approach with parsing.

ABOUT THE AUTHOR

Christos Sotiriou

PREVIOUS POST

NEXT POST

About

Christos Sotiriou

Software engineer with strong analytical skills and experience in both mobile development and backend environments. Creative. Communicative. Always welcoming opportunities to adapt to new software environments and learn new things.