0

First of all, I am just a beginner who is currently developing an app with the Swift language, so please don't mind my question too much because I really need to know and I am having trouble with maintaining the code that I constructed.

It's about the async delegate pattern.

Here is my API class. Assume that there are many API classes like that which makes async calls.

protocol InitiateAPIProtocol{
     func didSuccessInitiate(results:JSON)
     func didFailInitiate(err:NSError)
}

class InitiateAPI{

    var delegate : InitiateAPIProtocol
    init(delegate: InitiateAPIProtocol){
         self.delegate=delegate
    }

    func post(wsdlURL:String,action:String,soapMessage : String){

         let request = NSMutableURLRequest(URL: NSURL(string: wsdlURL)!)
         let msgLength  = String(soapMessage.characters.count)
         let data = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

         request.HTTPMethod = "POST"
         request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
         request.addValue(msgLength, forHTTPHeaderField: "Content-Length")
         request.addValue(action, forHTTPHeaderField: "SOAPAction")
         request.HTTPBody = data

         let task = session.dataTaskWithRequest(request) {
              data, response, error in

            if error != nil {
                 self.delegate.didFailInitiate(error!)
                 return
            }

            let jsonData = JSON(data: data)
            self.delegate.didSuccessInitiate(jsonData)
        }
        task.resume()

}

func doInitiate(token : String){

    let soapMessage = “”
// WSDL_URL is the main wsdl url i will request.

action = “”

    post(WSDL_URL, action: action, soapMessage: soapMessage)
}
}

Here is my ViewController:

class ViewController : UIViewController,InitiateAPIProtocol{
    var initiateAPI : InitiateAPI!
    var token : String = “sWAFF1”

    override func viewWillAppear(animated: Bool) {
            // Async call start
            initiateAPI = InitiateAPI(delegate:self)
            initiateAPI.doInitiate(token)
    }

    // Here comes call back
    func didSuccessInitiate(results: JSON) {
       //handle results
    }

    func didFailInitiate(err: NSError) {
       //handle errors
    }
}

My problem is I said that there are many API classes like that, so if one view controller handles 4 API classes, I have to handle many protocol delegates methods as I extend the view controller. There will be many delegates method below of view controller. If other view controllers call the same API and have to handle the same delegates, I have a problem maintaining the code because every time I change some delegate parameters, I have to fix the code at all view controllers which use those API classes.

Is there any other good way to handle async call?

If my question seems a little complex, please leave a comment, I will reply and explain it clearly.

Thiha Aung
  • 5,036
  • 8
  • 36
  • 79
  • 1
    There is no way to really make this simpler. If you have 4 api calls you will get 4 sets of JSON information that you will react 4 times with... meaning you will either need 4 different delegates or 4 closures or 1 delegate where you tag 4 different API classes and you test to see what kind of api operation was made.... either way if you change anything you will have to update it across every place in the app that you make that api call. – A'sa Dickens Apr 14 '16 at 20:04
  • Yeah,thats what i am having...If 4 controllers call the same api class,i have to handle their delegates on 4 controllers.So,If I make changes to those delegates parameters,i will have to edit at all 4 controllers.Pretty bad to me.Any suggest is appreciated. – Thiha Aung Apr 14 '16 at 20:07
  • Actually, there _are several_ approaches which solve such kinds of problems - which are concise, comprehensible and easy to use. Just don't use delegates and no NSNotificationCenter ;) – CouchDeveloper Apr 14 '16 at 22:54
  • @CouchDeveloper , mind show me one of approaches? – Thiha Aung Apr 15 '16 at 04:51
  • If you make a protocol that defines how an API is suppose to configure a given entity, lets say a tableView, then you can pass in an object complying with that protocol (the view controller) in to the API object's initializer and then you can configure it after the download happens. That could eliminate you having to write the same thing in 4 different controllers. I'd suggest just handling it in 4 different locations on account each location could be unique to that scene. – A'sa Dickens Apr 15 '16 at 12:17
  • @A'saDickens,thanks for the help.In order to make me more useful,example is appreciated. – Thiha Aung Apr 15 '16 at 12:18
  • I don't suggest it, so i'm not going to write an example. What you have i think is the most flexible and best for your situation. If you have to update what the API return does in 4 locations that sucks, but this allows each object to configure the return how it needs it even if they handle it all the same. – A'sa Dickens Apr 15 '16 at 12:24

3 Answers3

4

Delegates (OOP) and "completion handlers" (function like programming) just don't fit well together.

In order to increase comprehension and to make the code more concise, an alternative approach is required. One of this approach has been already proposed by @PEEJWEEJ using solely completion handlers.

Another approach is using "Futures or Promises". These greatly extend the idea of completion handlers and make your asynchronous code look more like synchronous.

Futures work basically as follows. Suppose, you have an API function that fetches users from a remote web service. This call is asynchronous.

// Given a user ID, fetch a user:
func fetchUser(id: Int) -> Future<User> {
    let promise = Promise<User>()
    // a) invoke the asynchronous operation.
    // b) when it finished, complete the promise accordingly:
    doFetchAsync(id, completion: {(user, error) in 
        if error == nil {
            promise.fulfill(user!)
        } else {
            promise.reject(error!)
        }
    })
    return.promise.future
}

First, the important fact here is, that there is no completion handler. Instead, the asynchronous function returns you a future. A future represents the eventual result of the underlying operation. When the function fetchUser returns, the result is not yet computed, and the future is in a "pending" state. That is, you cannot obtain the result immediately from the future. So, we have to wait?? - well not really, this will be accomplished similar to an async function with a completion handler, i.e. registering a "continuation":

In order to obtain the result, you register a completion handler:

fetchUser(userId).map { user in
    print("User: \(user)")
}.onFailure { error in
    print("Error: \(error)")
}

It also handles errors, if they occur.

The function map is the one that registered the continuation. It is also a "combinator", that is it returns another future which you can combine with other functions and compose more complex operations.

When the future gets finally completed, the code continues with the closure registered with the future.

If you have two dependent operations, say OP1 generates a result which should be used in OP2 as input, and the combined result should be returned (as a future), you can accomplish this in a comprehensive and concise manner:

let imageFuture = fetchUser(userId).flatMap { user in
    return user.fetchProfileImage() 
}
imageFuture.onSuccess { image in
    // Update table view on main thread:
    ...
}

This was just a very short intro into futures. They can do much more for you.

If you want to see futures in action, you may start the Xcode playgrounds "A Motivating Example" in the third party library FutureLib (I'm the author). You should also examine other Future/Promise libraries, for example BrightFutures. Both libraries implement Scala-like futures in Swift.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
1

Have you looked into NSNotificationCenter?

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/

You'll be able to post events from your api class, then each view controller would subscribe to the events and be notified accordingly

Does that make sense? There are lots of good examples of this pattern: https://stackoverflow.com/a/24049111/2678994
https://stackoverflow.com/a/28269217/2678994

I've updated your code below:

class InitiateAPI{
    // 
    // var delegate : InitiateAPIProtocol
    // init(delegate: InitiateAPIProtocol){
    //      self.delegate=delegate
    // }

    func post(wsdlURL:String,action:String,soapMessage : String){

        let request = NSMutableURLRequest(URL: NSURL(string: wsdlURL)!)
        let msgLength  = String(soapMessage.characters.count)
        let data = soapMessage.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)

        request.HTTPMethod = "POST"
        request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
        request.addValue(msgLength, forHTTPHeaderField: "Content-Length")
        request.addValue(action, forHTTPHeaderField: "SOAPAction")
        request.HTTPBody = data

        let task = session.dataTaskWithRequest(request) {
        data, response, error in

        if error != nil {
            //  self.delegate.didFailInitiate(error!)

            /* Post notification with error */
            NSNotificationCenter.defaultCenter().postNotificationName("onHttpError", object: error)
            return
        }

        let jsonData = JSON(data: data)
            // self.delegate.didSuccessInitiate(jsonData)

            /* Post notification with json body */
            NSNotificationCenter.defaultCenter().postNotificationName("onHttpSuccess", object: jsonData)
        }
        task.resume()

    }

    func doInitiate(token : String){

        let soapMessage = “”
        // WSDL_URL is the main wsdl url i will request.

        action = “”

        post(WSDL_URL, action: action, soapMessage: soapMessage)
    }
}

Your view controller class:

class ViewController : UIViewController { //,InitiateAPIProtocol{
    var initiateAPI : InitiateAPI!
    var token : String = “sWAFF1”

    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.didSuccessInitiate(_:)), name: "onHttpSuccess", object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.didFailInitiate(_:)), name: "onHttpError", object: nil)
    }

    override func viewWillAppear(animated: Bool) {
        // Async call start
        initiateAPI = InitiateAPI(delegate:self)
        initiateAPI.doInitiate(token)
    }

    override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)

        /* Remove listeners when view controller disappears */
        NSNotificationCenter.defaultCenter().removeObserver(self, name: "onHttpSuccess", object: nil)
        NSNotificationCenter.defaultCenter().removeObserver(self, name: "onHttpError", object: nil)
    }

    // Here comes call back
    func didSuccessInitiate(notification : NSNotification) { //results: JSON) {

        if let payload = notification.object as? JSON {

            //handle results
        }
    }

    func didFailInitiate(notification : NSNotification) { //err: NSError) {

        if let payload = notification.object as? NSError {

            //handle errors
        }
    }
}
Community
  • 1
  • 1
DTHENG
  • 188
  • 1
  • 7
  • Any example with comment is appreciated.Thanks for the help.I am looking forward to your answer. – Thiha Aung Apr 14 '16 at 20:03
  • I think its the same as my delegate pattern.But,it use NSNotificationCenter.I accept.But,as you can see there are two methods "didSuccessInitiate" and "didFailInitiate".These callback will have to include every time i make Initiate sync call on each view controller where i gonna use.So,if one change,i have to change it all at every view controller that it use those callback.So,is there anyway that i can make it to use at every controller? – Thiha Aung Apr 14 '16 at 20:35
  • Ah i see what you are saying, you could make the ViewController class the base of all your other controllers, that way they will still subscribe to the events, but the implementation will stay in one spot, so like: class NewViewController : ViewController { ... – DTHENG Apr 14 '16 at 20:43
  • Assume,i call same async call on 3 view controllers.So,i have to include same 2 call back on 3 view controllers.If those call back have same json response handling,why i cant handle it at one class and pass the value to those 3 controllers. – Thiha Aung Apr 14 '16 at 20:43
  • Yes,the implememtation stay in one spot.got it.Because all the callback have same json handling.So,if i gonna edit,i can edit in one place. – Thiha Aung Apr 14 '16 at 20:44
1

Instead of using a delegate, you could (should?) use closers/functions:

func post(/*any other variables*/ successCompletion: (JSON) -> (), errorCompletion: (NSError) ->()){

    /* do whatever you need to*/

    /*if succeeds*/
    successCompletion("")

    /*if fails*/
    errorCompletion(error)
}

// example using closures
post({ (data) in
    /* handle Success*/
    }) { (error) in
        /* handle error */
}

// example using functions
post(handleData, errorCompletion: handleError)

func handleData(data: JSON) {

}

func handleError(error: NSError) {

}

This would also give you the option to handle all the errors with one function.

Also, it's ideal to parse your JSON into their desired objects before returning them. This keeps your ViewControllers clean and makes it clear where the parsing will occur.

GetSwifty
  • 7,568
  • 1
  • 29
  • 46