How to implement spinning wheel during a backend job? - ios

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.

Related

How to call multiple functions in order one after another as long as the previous is complete

I am trying to call 3 functions in order but each function needs to have been completed before the next should run. Each function has a completion handler that calls another function upon completion. After reading lots online about dispatch queues I though this may be the best way to approach it, that's if I am understanding it correctly of course. When I run my code Each function is called in order but not when the previous has been completed. In the first function I am downloading an image from firebase but the second function gets called before the image has downloaded. I've taken out specifics in my code but this is what I have so far.
typealias COMPLETION = () -> ()
let functionOne_completion = {
print("functionOne COMPLETED")
}
let functionTwo_completion = {
print("functionTwo COMPLETED")
}
let functionThree_completion = {
print("functionThree COMPLETED")
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue(label: "com.myApp.myQueue")
queue.sync {
functionOne(completion: functionOne_completion)
functionTwo(completion: functionTwo_completion)
functionThree(completion: functionThree_completion)
}
func functionOne(completion: #escaping COMPLETION) {
print("functionOne STARTED")
completion()
}
func functionTwo(completion: #escaping COMPLETION) {
print("functionTwo STARTED")
completion()
}
func functionThree(completion: #escaping COMPLETION) {
print("functionThree STARTED")
completion()
}
You could use DispatchGroup
DispatchQueue.global().async {
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
functionOne { dispatchGroup.leave() }
dispatchGroup.wait() //Add reasonable timeout
dispatchGroup.enter()
functionTwo { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.enter()
functionThree { dispatchGroup.leave() }
dispatchGroup.wait()
dispatchGroup.notify(queue: .main) {
//All tasks are completed
}
}
You need to call the second function on the completion of the first.
Something like:
func first(_ completion : #escaping()->()){
print("first")
completion()
}
func second(_ completion : #escaping()->()){
print("second")
}
func third(){
print("third")
}
override func viewDidLoad(){
....
first{
self.second{
self.third()
}
}
}
So when your image download gets finished, inside the completion block where you get the callback of download completion, you should call your second method/block passed as argument which in turn will call your second method.

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
})

How to runlong running tasks in iOS which are dependent on each other

I want to run some tasks which are dependent on each other so should be performed in an order. Currently, it blocks my UI thread and also there is some issue in ordering.
Couple of questions regarding this:
Tasks are not performed in correct order. What change to be made if we want them to be performed one after other
Is the code optimised in terms of memory usage and resource consumption? How it can be made more optimised?
Do we need global queues inside function call also as shown in code below?
Here are my code details. I have created some serial queues as follows:
var Q0_sendDisplayName=dispatch_queue_create("Q0_sendDisplayName",DISPATCH_QUEUE_SERIAL)
var Q1_fetchFromDevice=dispatch_queue_create("fetchFromDevice",DISPATCH_QUEUE_SERIAL)
var Q2_sendPhonesToServer=dispatch_queue_create("sendPhonesToServer",DISPATCH_QUEUE_SERIAL)
I have an idea that serial queues perform tasks in order so i have called my tasks on serial queues. Here is my code:
dispatch_sync(Q0_sendDisplayName,
{
self.sendNameToServer(displayName){ (result) -> () in
dispatch_sync(self.Q1_fetchFromDevice,
{
self.SyncfetchContacts({ (result) -> () in
dispatch_sync(self.Q2_sendPhonesToServer,
{ self.SyncSendPhoneNumbersToServer(self.syncPhonesList, completion: { (result) in
//.......
//....
The code inside these functions is also running on global queue. Dont know if it is a correct way to code. I have used completion handlers to notify that method has completed executing. Here is the code of function1:
func sendNameToServer(var displayName:String,completion:(result:Bool)->())
{
Alamofire.request(.POST,"\(urlToSendDisplayName)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
return completion(result: true) //......
Here is the code of function2. This function is long as it reads whole contacts book so i have placed it inside global queue(dont know if it is right way). I call completion handler on main queue. Here is code:
func SyncfetchContacts(completion:(result:Bool)->())
{
let contactStore = CNContactStore()
var keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey, CNContactImageDataKey]
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)){
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
}
dispatch_async(dispatch_get_main_queue())
{
completion(result: true)
}
}
//........
Here is the code for function3 which again inside has a global queue(dont know if its right) and calls completion handler on main queue.
func SyncSendPhoneNumbersToServer(phones:[String],completion: (result:Bool)->()){
Alamofire.request(.POST,"\(url)",headers:header,parameters:["display_name":displayName]).responseJSON{
response in
switch response.result {
case .Success:
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0))
{
//enter some large data in database in a loop
dispatch_async(dispatch_get_main_queue())
{
return completion(result: true)
}
}//......
In SyncfetchContacts you are calling the completion handler before contactStore.enumerateContactsWithFetchRequest has finished, outside of its completion closure.
Simply move it in there:
func SyncfetchContacts(completion:(result:Bool)->()) {
...
do {
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if (contact.isKeyAvailable(CNContactPhoneNumbersKey)) {
for phoneNumber:CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value as! CNPhoneNumber
}
}
// here ...
dispatch_async(dispatch_get_main_queue()) {
completion(result: true)
}
}
// ... not here.
// dispatch_async(dispatch_get_main_queue()) {
// completion(result: true)
// }
}
}

True if completionhandler is done

My app is heavily dependent on the data that is coming. I want it to run the activity indicator and disable user interaction on the view while the data is being downloaded.
Is there a way to check or return something when the completion handler is done?
typealias CompletionHandler = (success:Bool) -> Void
func downloadFileFromURL(url: NSURL,completionHandler: CompletionHandler) {
**download code**
let flag = true
true if download succeed,false otherwise
completionHandler(success: flag)
}
How to use it.
downloadFileFromURL(NSURL(string: "url_str")!, { (success) -> Void in
**When download completes,control flow goes here.**
if success {
} else {
}
})
Define a property, which keeps completion handler, and call it when all the data is obtained:
var didObtainAllData: (() -> Void)?
func obtainData() {
....
// When data is obtained.
didObtainAllData?()
}
You can write
func processingTask(condition: String, completionHandler:(finished: Bool)-> Void) ->Void {
}
Use
processingTask("test") { (finished) in
if finished {
// To do task you want
}
}

How to use callbacks to make Asynchronous calls in Swift so that main UI does not Hang?

I am trying to make Asynchronous call (to Parse) and during the call I DON'T want my Main UI to get hung.
Below is the function I am trying to call my ViewController. Below that function is the code I am using from my ViewController.
In the line sleep(4) line the main UI of the ViewController LoginVC does get stuck. Am I not using callbacks for the Asynchronous call correctly?
class Utils {
func logIntoWebService(uname: String, pwd: String, completion: ((result:Bool?) -> Void)!) {
PFUser.logInWithUsernameInBackground(uname, password:pwd) {
(user, error) -> Void in
if error == nil {
if user != nil {
// Yes, User Exists
//UI IS STUCK DURING THIS SLEEP(10)
sleep(10)
completion(result: true)
} else {
completion(result: false)
}
} else {
completion(result: false)
}
}
}
}
I am calling the above function from my ViewController. Below is the call from my ViewController
class LoginVC: UIViewController {
var mUtils: Utils = Utils()
mUtils.loggedInStatus({ (result) -> Void in
println("Response from logInToParse = " + String(stringInterpolationSegment: result))
})
}
You are getting confuse between background threads and completion handler.
logInWithUsernameInBackground is a async function that runs in the background however all the code that runs in the completion handler runs back in the main thread:
completion: ((result:Bool?) -> Void)!) {//The code here runs in the main thread}
So basically from the time that your application start communicate with Parse.com, until the result was back, that code was running asynchronous in a background thread, when it finish and your application received the response it runs in the completion block back in the main thread.
Now lets say you really want to run some code in the background in the completion block, than you would use something like:
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
println("Now this code will run in the background thread")
})
All the new quality of service classes are:
QOS_CLASS_USER_INTERACTIVE
QOS_CLASS_USER_INITIATED
QOS_CLASS_UTILITY
QOS_CLASS_BACKGROUND
Or in your case:
PFUser.logInWithUsernameInBackground(uname, password:pwd) {
(user, error) -> Void in
if error == nil {
if user != nil {
// Yes, User Exists
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
sleep(10)
})
completion(result: true)
} else {
completion(result: false)
}
} else {
completion(result: false)
}
}
For more information see Apple documentation

Resources