0

I need to call an API to upload a photo, this API returns an ID of the photo. Then, I need to get that ID and use it as a parameter for another API.

The problem is, the second API gets called before the first API has a chance to complete (and return the ID). What can be done about this?

I'm using Alamofire 4 and Swift 3.

My code:

 // First API - Upload file
func uploadFile(url: String, image: UIImage, callback: @escaping (JSON) -> ()) {

    let imageData = UIImagePNGRepresentation(image)

    let URL2 = try! URLRequest(url: url, method: .post, headers: header)

    Alamofire.upload(multipartFormData: { (multipartFormData) in

        multipartFormData.append(imageData!, withName: "photo", fileName: "picture.png", mimeType: "image/png")

    }, with: URL2, encodingCompletion: { (result) in
        switch result {
        case .success(let upload, _, _):
            upload.responseJSON
                {
                    response in

                    switch response.result {
                    case .success(let value):
                        let json = JSON(value)
                        callback(json)
                    case .failure(let error):
                        print(error)
                    }
            }
        case .failure(let encodingError):
            print(encodingError)
        }
    })
}

 // Second API
 func Post(url: String, parameters: [String:Any], callback: @escaping (JSON) -> ()) {

    Alamofire.request(url, method: .post, parameters: parameters.asParameters(), encoding: ArrayEncoding(), headers: header).responseData { (response) in
        switch response.result {
        case .success(let value):
            callback(json)
        case .failure(let error):
            print(error)
        }
    }
}

// Calling the First API
var uploadedImage: [String:String]!
uploadFile(url: baseUrl, image: image, callback: { (json) in
            DispatchQueue.main.async {
                    uploadedImage = ["filename": json["data"]["photo"]["filename"].stringValue, "_id": json["data"]["photo"]["_id"].stringValue]
                }
        })

// Calling the Second API
Post(url: baseUrl, parameters: uploadedImage) { (json) in
        DispatchQueue.main.async {
            self.activityIndicator.stopAnimating() 
        }
}
Dev-iL
  • 23,742
  • 7
  • 57
  • 99
NST
  • 115
  • 10
  • Generally, the way to avoid concurrency in such cases is putting the call to the 2nd API in the "success callback" of the 1st (meaning the 1st must've finished by then). – Dev-iL Feb 19 '17 at 08:05
  • @Dev-iL You are right. but In my case, sometimes there is no image to upload, so I need to jump in to the second API immediately. – NST Feb 19 '17 at 08:07
  • Then either make some `if`/`else` to skip the 1st request or perhaps call the 1st with some null parameters that would either fail or succeed immediately, then still put the call to the 2nd in the callback.... – Dev-iL Feb 19 '17 at 08:10
  • @Dev-iL are you sure that there is no another way? – NST Feb 19 '17 at 08:14
  • Heisenberg's principle tells us we can never be sure.... I am fairly certain this method will work though... :) – Dev-iL Feb 19 '17 at 08:16
  • @Dev-iL Thank you – NST Feb 19 '17 at 08:22

1 Answers1

0

In order to avoid the race condition that you're describing, you must be able to serialize the two calls somehow. The solutions that come to mind are either barriers, blocking calls, or callbacks. Since you're already using asynchronous (non-blocking) calls, I will focus on the last item.

I hope that a pseudocode solution would be helpful to you.

Assuming the 1st call is always performed before the 2nd, you would do something like:

firstCall(paramsForFirstOfTwo){
  onSuccess {
    secondCall(paramsForSuccess)
  }

  onFailure {
    secondCall(paramsForFailure)
  }
}

However, if the 1st call is optional, you could do:

if (someCondition){
  // The above example where 1->2
} else {
  secondCall(paramsForNoFirstCall)
}

If you must perform the 1st call a certain amount of times before the 2nd is performed, you can:

let n = 3; var v = 0; // assuming these are accessible from within completedCounter() 
firstCall1(...){/* after running code specific to onSuccess or onFailure call c..C..() */}
firstCall2(...){/* same as previous */}
firstCall3(...){/* same as previous */}

function completedCounter(){
  // v must be locked (synchronized) or accessed atomically! See: 
  //    http://stackoverflow.com/q/30851339/3372061 (atomics)
  //    http://stackoverflow.com/q/24045895/3372061 (locks)
  lock(v){ 
    if (v < n)
      v += 1
    else 
      secondCall(paramsBasedOnResultsOfFirstCalls);
  }

}    
Dev-iL
  • 23,742
  • 7
  • 57
  • 99
  • @Dev_iL Thank you for your effort. Still have a problem, because there are four optional photos to be uploaded before the second API. I think this approach is not suitable for my case – NST Feb 19 '17 at 08:49
  • @user3316585 How was I supposed to know that? In any case, you could have a counter of failed/successful requests and have each callback call a method that either increments the counter, or if it had reached the target value - issue the 2nd call. – Dev-iL Feb 19 '17 at 08:55