What is the best way to write a completion handler - ios

I am currently working on implementing In App Purchases in my app and after restoring purchases i would like to call a completion to perform an action of displaying an alert to the user. I was doing it this way and found a post that says it might not even be executed. How can I properly structure this.
func restoreIAPPurchases(completion: (() -> Void)) {
if !self.canMakePayments {
return
}
self.paymentQueue.restoreCompletedTransactions()
completion()
}
let alertController = UIAlertController.vy_alertControllerWithTitle(nil, message: "Restore will reprocess your existing subscription. You will not be charged", actionSheet: false)
alertController.addAction("Ok")
alertController.addActionWithTitle("Restore", style: .default) {
IAPService.shared.restoreIAPPurchases {
UIAlertController.vy_showAlertFrom(self, title: "Restore complete", message: "Successfully restored purchase")
}
}
alertController.presentFrom(self)

"I was doing it this way and found a post that says it might not even be executed"
It might not be executed because you don't call the completion handler on all paths.
As Sh_Khan mentioned in his answer, you don't really need a completion handler here, you need to use the delegate methods to be informed when it completes and whether it was successful or not. But your particular issue with your specific code is that you are not calling completion in the if statement.
if !self.canMakePayments {
return
}
Should probably be
guard canMakePayments else {
completion()
return
}
In the code you had, if canMakePayments is false then your completion code will not execute.

The result is asynchonous here
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue)
or
func paymentQueue(_ queue: SKPaymentQueue,
restoreCompletedTransactionsFailedWithError error: Error)

Suppose I'm writing code for login and need Completion Hander for wait/call back after request completed.
//MARK:- #Properties
var signInCompletionHandler : ((_ result : AnyObject?, _ error : NSError?) -> Void)?
var viewController : UIViewController?
//MARK:- call login method with completion handler.
func login(withViewControler viewController : UIViewController, completionHandler : #escaping (_ result : AnyObject?, _ error : NSError?) -> Void) {
// Write your logic here.
}

Related

Best place to call `completionHandler` received in `handleEventsForBackgroundURLSession` in case of uploading?

I am using URLSessionConfiguration.background for uploading and downloading content while my app is not foreground. The code that I'm using to create a background session is as below:
private var session: URLSession!
private override init() {
super.init()
let config = URLSessionConfiguration.background(withIdentifier: Constants.backgroundSessionIdentifer)
config.sessionSendsLaunchEvents = true
session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
In application delegate, I receive a completion handler and store it as a property of UploadDownloadService which is a singleton.
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
Logger.debug(#function)
UploadDownloadService.shared.backgroundCompletionHandler = completionHandler
}
I'm executing the completion handler in urlSessionDidFinishEvents as apple demonstrated. My code is given below:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
Logger.debug(#function)
delegate?.urlSessionDidFinishEventsCalled()
DispatchQueue.main.async {
[weak self] in
guard let backgroundCompletionHandler = self?.backgroundCompletionHandler else {
return
}
Logger.debug("Executing background completion handler.")
backgroundCompletionHandler()
self?.backgroundCompletionHandler = nil
self?.delegate?.didCallBackgroundCompletionHandler()
}
}
Now what I've found is urlSession(_ session:, downloadTask:, didFinishDownloadingTo location:) is called before urlSessionDidFinishEvents so I am sure that my downloaded file is saved to a permanent location before I call the completion handler inside urlSessionDidFinishEvents.
But I'm confused about the upload case. I didn't find any delegate method that is called before urlSessionDidFinishEvents method. The only delegate method I found for upload that is called when the app is awake is urlSession(_ session:, task:, didCompleteWithError:). Which is called after urlSessionDidFinishEvents.
Now in this scenario, I've two questions in mind.
1. From where should I call the completion handler so that I'm sure all of my post-processing is done after upload(In this case I want to query the database, update and send a message. I'm building a chat application)? Is it okay to delay the completion handler call while I do my processings and call it from outside of the urlSessionDidFinishEvents method?
2. How much time do I get approximately after I call the completion handler?
Note: I've done a lot of googling before posting, found a single related question here. But the only answer(as of now) on that post doesn't answer my question. So I've decided to ask the question elaborately(describing my scenario) here.

How to use typealias when getting data from server

I am trying to get user data from a server. The application does not have to show any views until the data is loaded.
I read about typealias and I don't understand how to use it.
What I want: when data is loaded, move on to next step. If failed, load data again.
Here's how I declare typealias
typealias onCompleted = () -> ()
typealias onFailed = () -> ()
Here is my request code
func getUserData(_ completed: #escaping onCompleted, failed: #escaping onFailed){
let fullURL = AFUtils.getFullURL(AUTHURL.getUserData)
AFNetworking.requestGETURL(fullURL, params: nil, success: {
(JSONResponse) -> Void in
if let status = JSONResponse["status"].string {
switch status{
case Status.ok:
completed()
break
default:
failed()
break
}
}
})
}
But how could I use this on my view controller when calling getUserData?
Assuming your custom AFNetworking.requestGETURLs completion handler is called on the main queue:
func viewDidLoad() {
super.viewDidLoad()
getUserData({
//do somthing and update ui
}) {
//handle error
}
}
Edit:
How I understand your comment, you actually want to name your completion and error block parameters. If so, change the method to :
func getUserData(completion completed: #escaping onCompleted, error failed: #escaping onFailed){ ... }
and call it like this:
getUserData(completion: {
//do somthing and update ui
}, error: {
//handle error
})

Pooling completion handlers together such that method completes once multiple closures are executed

I have a scenario where I want to perform three distinct asynchronous tasks in parallel. Once all three tasks are complete, I then want the calling method to be aware of this and to call its own completion handler.
Below is a very simplified version of the logic for this:
class ViewController: UIViewController {
func doTasks(with object: Object, completionHandler: () -> Void) {
// Once A, B & C are done, then perform a task
wrapupTask()
// When task is complete, call completionHandler
completionHandler()
}
}
fileprivate extension ViewController {
func taskA(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskB(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
func taskC(with object: Object, completionHandler: () -> Void) {
// Do something
completionHandler()
}
}
I could easily chain the handlers together, but then the task will likely take longer and the code will suck.
Another item I considered was a simple counter that incremented each time a task completed, and then once it hit 3, would then call the wrapupTask() via something like this:
var count: Int {
didSet {
if count == 3 {
wrapupTask()
}
}
}
Another option I have considered is to create an operation queue, and to then load the tasks into it, with a dependency for when to run my wrap up task. Once the queue is empty, it will then call the completion handler. However, this seems like more work than I'd prefer for what I want to accomplish.
My hope is that there is something better that I am just missing.
Just to pick up on what OOPer said, you are looking for DispatchGroup. In the following, the calls to taskA, taskB, and taskC are pseudo-code, but everything else is real:
func doTasks(with object: Object, completionHandler: () -> Void) {
let group = DispatchGroup()
group.enter()
taskA() {
// completion handler
group.leave()
}
group.enter()
taskB() {
// completion handler
group.leave()
}
group.enter()
taskC() {
// completion handler
group.leave()
}
group.notify(queue: DispatchQueue.main) {
// this won't happen until all three tasks have finished their completion handlers
completionHandler()
}
}
Every enter is matched by a leave at the end of the asynchronous completion handler, and only when all the matches have actually executed do we proceed to the notify completion handler.

How to implement spinning wheel during a backend job?

I have a need to implement a spinning wheel during a backend job. I have my backend-job in a separate class.
class ViewControllerA: UITableViewController {
// Code
var GetBackendRecordObj = GetBackendRecord(initparam:param);
// CODE TO START ANIMATION (SPINNING WHEEL)
self.view.addSubview(self.activityIndicator)
self.activityIndicator.bringSubview(toFront: self.view)
self.activityIndicator.startAnimating()
// CODE TO CALL THE BACKEND IS IN ANOTHER CLASS
GetBackendRecordObj.fetch_record()
}
class GetBackendRecord{
var transaction_id: String = ""
var current_email_id: String = ""
init(initparam: String) {
self.initparam = initparam
}
func fetch_record (){
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
// NEED CODE TO STOP ANIMATION (SPINNING WHEEL)THAT WAS STARTED IN VIEWCONTROLLERA
})
}
}
}
How can I access the UITableViewcontroller after the backend call is done, so I can stop the animation. OR If there is a better way to start / stop animation when executing a backend-job (in a separate class) please let me know.
Add a completion handler to fetch_record:
func fetch_record(_ completionHandler: #escaping () -> Swift.Void) {
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
completionHandler()
})
}
}
}
When calling it in your ViewController, you can specify what to do after completion:
GetBackendRecordObj.fetch_record() {
self.activityIndicator.stopAnimating()
}
If you need to know when the response returns so that you can stop the loader, you need to add a completion handler to the method that makes your internet call. You should generally have completion handlers on most methods that make internet calls, especially if you need UI things to happen only once you have gotten a response.
So for the fetchRecord function:
I have added a completion handler to this call. Preferably you would hand something off here just after #escaping(something like a dictionary or an array if it is a JSON response) and then process that response in another method. But if you want the code to process the response in this method with the threading that you've set up here, then I've written it accordingly.
func fetch_record(withCompletion comp: #escaping () ->()){
do{
DispatchQueue.global(qos: .userInitiated).async {
//code
DispatchQueue.main.async { () -> Void in
//code to process response from backend
//this tells whatever called this method that it is done
comp()
})
}
}
}
Then in your view controller when you call GetBackendRecordObj.fetch_record()
GetBackendRecordObj.fetch_record(withCompletion: { [weak self] () in
//when it hits this point, the process is done
self?.activityIndicator.stopAnimating()
}
Instead of activityIndicator better to use MBProgressHUD.
https://github.com/jdg/MBProgressHUD
To show MBProgressHUD
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "Loading"
To hide MBProgressHUD
DispatchQueue.main.async { () -> Void in
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
})
You can implement show MBProgressHUD in seperate class from where you initiate back end job and hide code once your back end process finish.

Is it okay to segue, without calling a completion handler, if not needed?

Say I have a function that has a completion handler, then calls another function, with a completion handler like this:
func register(withCompletion complete: #escaping (() -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
if error != nil {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
else {
complete()
}
})
}
In the event of an error, it will segue away with calling complete().
Am I ok to segue away like this, without calling complete()? I do not need to return from this function as I'm now wanting to go to another View Controller.
Thanks.
This is a bad idea. A completion handler should be called no matter what. The caller is waiting for a response. It wants to know when it is done. That's the whole point of having a completion handler.
In your case (like many other cases), it would be much better if the completion handler accepted a boolean parameter (and/or an error parameter). This way the completion handler provides some basic information about the success or failure of the method.
Try like this
override func viewDidLoad() {
super.viewDidLoad()
register { (error) in
if error == nil {
// do what you want in success case
} else {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
}
}
func register(withCompletion complete: #escaping ((_ error: Error?) -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
complete(Error)
})
}
Thanks:)

Resources