Wednesday, 14 September 2016

Simple, Effective and Robust Network model for iOS applications in Swift (using Protocol Oriented Programming)

Hi,

Most of the time every single app that we develop in iOS usually deals with tons of network calls. We always take the shelter of third party frameworks such as Alamofire for swift or AFNetworking for Objective-C to handle such network calls.

Though these frameworks do a great job by taking care of all the boiler plate codes for us, we usually end up writing web service calls in multiple ViewControllers or in Controller classes, which will result in code duplication and also will raise the issue with maintenance of the code.

Issues with maintenance of the code?? But How??

Lets assume you are following MVC pattern and you have Controller class for each ViewController, which behaves as a mediator or a broker between ViewController and Data source, so its quite apt to keep the web service calls related to LoginViewController in its Controller.

So Lets create a LoginController class,

Whats happening in here???
Simple! We have a method name loginUser which takes username, password and success block as its arguments and assume that our loginUser method simply tells LoginViewController whether the login was successful or not ( I know thats a dumb thing to do!! but for this demo its fine I believe).

All that loginUser method does is, it hits the url "http://idontknowurl.com/getUser" using a .GET method and passes

["username" : "abcd",
 "password" : "1234"]

as its parameter, parses the login response, does all the magic it has to do to deal with success and failure cases, configures the loggedInUser instance and finally tells LoginViewController whether the login was successful or not by executing the closure with a boolean parameter.

Now lets see the User class,


User class is pretty straight forward, there can only be one logged in user per session, so I have a static variable named loggedInUser and have private implementation of init() to make sure that there can only be one instance of loggedInUser through out the application life cycle, declaring private init() ensures uniqueness of the loggedInUser instance for the entire life cycle.

User class has a method named configureUser as the name suggests, all it does is to configure the unique loggedInUser instance.

Now where is the issue here ??

Though there is an inherent issue here with this model, it is not significant enough to get noted easily, so lets try adding two more classes to understand the issue, what say?

As we are done with login now, lets consider fetching user profile info, Savy ?

Aye Captain!!

Lets create a class called UserProfileViewController as usual we will create a Controller class for it, lets call it as UserProfileController and will move fetching user profile info web service call to this class.

Now that we have user profile info, lets fetch the friends list for the logged in user, So lets create FriendsListViewController, and create its associated controller class and name it as FriendsListController,

now assume that fetchFriendsList fetches all the friends of the user based on the friendsLevel parameter, and in here we are fetching all the contacts of the user based on his gmail id.

Issue :

1. Did you see whats happening here, same code,
is being repeated in almost every single controller, don't you think that needs to optimised and kept in a single place?

2. Parameters like userId is being repeated in almost all the requests, isn't it redundant to place such parameters for all requests? Don't you think these parameters be declared as a default parameter and all requests should simply just get it by default ?

3. Authorisation headers like ["username" : "username", "password" : "password"] is common for all requests except login request, again is it not un-necessary to place these code for each requests again and again, isn't there a better way to declare them as default headers ?

4. What if tomorrow your server team says, there is another default parameter/header parameter that you need to send for each request, say ["xyz" : "abc"], what will you do ? You will open up every single controller and add this the default parameter to each request !!! What if your app is network call intense and you have 100 such calls happening, so you gonna change in 100 places and test every single web service call to ensure you din mess up anything ?? "May God help you :P"

5. Finally, what if tomorrow apple changes the underlying network API's itself, like it did with the release of iOS8, NSURLConnection was replaced by NSURLSession, or the third party framework you were using decides to change the network API itself, like AFNetworking 2.0 changed its API to be compatible with apple's network API changes,  don't you think you are kinda screwed!! You will have to now re-visit every controller that has network call and update every single API.

Fine! How to solve it then ?

There is no absolutely correct or the perfect solution to it, as designing your application architecture is a very creative task and it is even possible that every developer might perceive and design their app in a way that they find it feasible. Your application arctitecture evolves with your experience in developing the app, with your evolving skill set and finally with your evolving perspective towards the coding paradigm as a whole.

Am not the expert here, but here is how I design my Network model and it works pretty much awesome to me.

Before we go ahead and design, this network model can be designed easily using traditional inheritance, but because we are dealing with swift, which prefers a protocol oriented programming over object oriented programming, I am gonna extensively make use of protocols and structs.

Now What the hell is Protocol Oriented Programming ???

If you have not heard of this phrase before, and yet you are developing the app in swift, then there is very good chance that you might have misunderstood the Swift completely.

Though explaining the Protocol Oriented Programming is not in the scope of this tutorial, I strongly recommend you to go through WWDC 2015 video on "Protocol Oriented Programming in Swift". Here is the link to the video : https://developer.apple.com/videos/play/wwdc2015/408/

If you don't prefer spending your 45 minutes, watching the WWDC video, you can still continue to read the blog, but you might leave your self wondering, why the hell blogger is writing so much of code to achieve something, which he could have simply achieved by using inheritance, but believe me its worth trying something new :)

Is it necessary to design it in Protocol Oriented Programming ??  

Absolutely not!! I believe whether to use Object Oriented Programming or Protocol Oriented Programming is absolutely a trade off, In certain situation sticking with traditional Object Oriented Programming might make more sense than achieving the same thing with Protocol Oriented Programming.

But trying out something new, is always the perk of this job!! Isn't it? Hence designing this model in complete compliance with Protocol Oriented Programming.

Enough of this theory, Lets get started!!!

First principal of Protocol Oriented Programming, start with struct not with class!!
Lets create a BaseRequest struct then

Now that should be straight forward, I have everything that is needed to create a Alamofire Request as a property of struct named BaseRequest. init() method of the BaseRequest provides the default values for the parameters like baseURL, method,parameterEncoding,parameters and headers.

Default value for the baseURL is being read from the Constant class, method is by default set to .GET, parameterEncoding by default set to URL Encoding.

Whats important here though, is the initialisation of parameters and header fields. As we have seen in the above examples, excluding loginRequest all other request requires the headers and excluding loginRequest all other API's expect "userId" in parameter. So I am checking if the logedInUser instance has been initialised properly or not, if yes then I'll add username and password to default headers else I set the headers to empty dictionary. Similarly, If  logedInUser instance has been initialised properly, then I'll set userId as default parameter else I initialise it with empty dictionary. I hope it makes sense :)

Now BaseRequest looks great, but it in itself is of no use, because it is a struct, no other struct can inherit it,  so all the initialisations that we have made so far is only limited to BaseRequest struct itself.

Wait! So everything we wrote till now is useless?

Nope! here comes the protocol, corner stone of swift programming language! After all, Swift is a Protocol Oriented Program man!!

Lets create a BaseRequestProtocol,

Hmmm, whats that now!! It is a very simple protocol with a property of type BaseRequest.

Again, though the protocol in itself is of little help here, but it does not actually do what we want to achieve, isn't it ?

But then, we are dealing with swift, and Protocols in swift can provide default implementations as well. Woooooo hoooooo!!! Yeah, thats a great news now.

Lets go ahead and write a default implementation of BaseRequestProtocol

Now wait a second, thats a hell lot of a code there!! What does that even mean??

Simple, by declaring the property baseRequest in BaseRequestProtocol we mandated all the class/struct that extends  BaseRequest protocol should also declare a variable of type BaseRequest, and because setter and getter is exposed, all the class/struct that extends the BaseRequestProtocol can provide their own values to this property.

Now in default extension, we declare multiple variables whose setter and getter we override. By providing these variables in default implementation and not declaring it in protocol declaration we made these variables available to all the class/struct that extends the  BaseRequestProtocol and yet need not declare these  variables, they can access these variables as if they have been inherited from the parent/super class.

Now we are getting somewhere!!

All other setter getter are pretty much self explainable, they read the value of parameter from BaseRequest and return it in getter and set the new value passed to the appropriate BaseRequest property.

Though most of the setter and getter are simple, what is most important to notice is the implementation of parameters and headers properties setter and getter.  Setter and getter of the headers and parameters properties are identical, so it is enough, if we analyse any one of them.

Lets consider the setter and getter of Parameters property,
Getter is pretty much simple, simply return the parameter property of baseRequest, setter has a little trick, remember we set the userId as default if the logedInUser is set with appropriate values, now assume that I have to hit a API, which does not take any parameter at all, but because your logedInUser is configured userId comes by default, now most of the API at web end ignores such parameters, but what if you are stuck with some psychopath web API developer who says "Just Don't send it!!!" how to remove it then ? Simple, in the setter of parameters I am checking if the newValue is nil or not , if it is nil, then I'll clear the default parameters from baseRequest, else I simply update whatever user passed to baseRequest's parameter property.

Look carefully I am updating the values to baseRequest's parameter, So if user overrides some default value, the new value will be applied to baseRequest's parameter, if user adds a new key value pair, it gets added to baseRequest's parameter and finally if user won't provide any value to the default parameter, the existing value will persist.
Note : What If I want to remove only certain keys from default parameter while keeping the rest untouched? Thats easy to implement as well, try it on your own! if you find difficulty you know where to find me :)

Yeah, you are almost done with the request object!!!!
Lets create a LoginRequest using the BaseRequestProtocol, what say ?

Thats it, you have the login request ready! All you need to provide was the parameter that you wanted  to send and specify the endPoint!!

Hmmm thats good, but lets consider creating getUserProfile and getFriendsList request as well.

Thats all, you have your getUserProfileRequest ready, no need to pass userId to parameter, because we have added it as default parameter, so it will be available by default, Same is the case with headers as well.

Now lets consider a little tricky getFriendsList request, now along with userId we need to send friendsLevel as well in parameter.

Thats it!! Just pass the parameter that you want to add to default parameter and rest will be taken care off for you :)

Request is ready, but this request is hardly of any use in itself, its not even Alamofire request!! What we need now is WebServiceManager

Woo hoo! Thats all you need :)
WebService manager makes use of Alamofire Manager to create a AlamofireRequest from the BaseRequestProtocol and finally uses the created alamofire request to hit the web service and get data from server
Note : With a very little tweak to your BaseRequestProtocol, you can also add fileUpload and fileDownload capability to WebServiceManager. Try it your self. If you have an issue, you know how to reach me, Don't you ? :)
Now lets use this WebServiceManager in our LoginController and see how it goes along ?

Now how's that, huh? Still not convinced lets update UserProfileController

finally lets update FriendsListController

Woo hoo !! Thats all you are done with your network model buddy :)

After all these circus, the only question we need answer for is, Is all these code required? What is the benefit that we get with this ? How is it anyway better than the earlier approach ?

Here is my opinion on the issues I had iterated earlier :

1. Did you see whats happening here, same code,
is being repeated in almost every single controller, don't you think that needs to optimised and kept in a single place?
Code is no longer duplicated, Code to create a Alamofire request and Code to hit the web service is kept isolated and in one position. No Controller has to write the same code again and again anymore.

2. Parameters like userId is being repeated in requests like getFriendsList and getUserProfile, isn't it redundant to place such parameters for all requests? Don't you think these parameters be declared as a default parameter and all requests should simply just get it by default ?
3. Authorisation headers like ["username" : "username", "password" : "password"] is common for all requests except login request, again is it not un-necessary to place these code for each requests again and again for all requests, isn't there a better way to declare them as default headers ?

Addition of default headers and parameters to base request ensures that all the requests gets these values by default, and no need to pass them again and again for each request.


4. What if tomorrow your server team says, there is another default parameter/header parameter that you need to send for each request, say ["xyz" : "abc"], what will you do ? You will open up every single controller and add this the default parameter to each request !!! What if your app is network call intense and you have 100 such calls happening, so you gonna change 100 places and test every single web service call to ensure you din mess up anything ?? "May God help you :P"
All you have to do is to add an extra key value pair to the default value of parameter property of BaseRequest. Thats all! All other 100 requests gets it for free!!

 5. Finally, what if tomorrow apple changes the underlying network API's itself, like it did with the release of iOS8, NSURLConnection was replaced by NSURLSession, or the third party framework you were using decides to change the network API itself, like AFNetworking 2.0 changed its API to be compatible with apple's network API changes,  don't you think you are kinda screwed!! You will have to now re-visit every controller that has network call and update every single API.
As mentioned earlier, entire code to create a alamofire request from BaseRequestProtocol and hitting the web service get the data from server is kept isolated and contained in one file in our case WebServiceManager.  If any the underlying API changes for whatever reason, all you have to change is WebServiceManager's createAlamofireRequest or getResponseFromRequest.

Bonus :

Unit testing your Network model is much simpler now, and you can establish the trust to whatever level you prefer very much easily with this network model. Unfortunately how to write unit test is not in the scope of this article, more about it some other time.

I know this isn't the perfect model, but works pretty good for me, I would love to know your opinions on it. Please leave your comment below.

7 comments: