Swift - How to call a function after a function is complete? - ios

i am new in swift.
I have been trying to google around.
My question is, how can i call a function after a function is complete?
So far i am using a delay, but sometimes the delay is not in sync.
The problem i try to solve is to download a URL from firebase database, and then proceed to download a image from Firebase.

You have to use a closure.
func funcA() {
funcB(){
//Manage completion handler
}
}
func funcB(completion: () -> Void) {
completion()
}

Related

When should I do user interface work on the main thread while using Firebase?

I heard that I should always do user interface work on the main thread while reading data from Firebase.
I tried to do self.tableView.reloadData on the background thread inside a Firebase observe(.value) function and the app did not crash or freeze.
databaseReference.observe(.value) { [weak self] snapshot in
guard let self = self else { return }
self.tableView.reloadData()
}
I tried to do the same work inside getData function instead of observe(.value) function and the app crashed.
databaseReference.getData { [weak self] error, snapshot in
guard let self = self else { return }
self.tableView.reloadData()
}
So why the app crashed inside getData function and it did not crash inside observe(.value) function?
firebaser here
While the Firebase SDK performs its network and other I/O on a background thread, it actually calls your callback on the main thread.
But it seems that getData does not do that here, which is a bug. So thanks for catching and reporting that. 🙏
I filed bug #8245 on the Github repo, so check there for progress.
UI updates (tableView.reloadData, for instance) must be done on the main thread only not dependant if Firebase or another third-party framework is used.
If you are not sure if a method of a third-party framework (as Firebase is) will execute its closure on the main thread you can check if you are on the main thread and dispatch execution onto it if needed.
For example, you can use this simple extension for Thread class to run any code that needs to be run on the main thread.
extension Thread {
class func runOnMainThread(_ closure: #escaping () -> Void) {
if Thread.isMainThread {
closure()
} else {
DispatchQueue.main.async(execute: closure)
}
}
}
Then you can use it inside Firebase completion handlers as following:
Thread.runOnMainThread {
self.tableView.reloadData()
}

Cancel firebase function

Is it possible to cancel Firebase HTTPS Callable function during it's request?
I have a function with some predictive search. It is called each time when user inputs a character in a search field.
Code:
func startSearch(_ query: String, completion: #escaping (_ results: [SearcheResults]) -> Void) {
let data = [
"query": query
]
functions.httpsCallable("startSearch").call(data) { (result, error) in
if error != nil {
completion([])
} else if let data = result?.data {
// some data manipulations
completion(elements)
}
}
}
Or maybe somehow dismiss earlier completions? Because for now, if user is very rapid and enter text, for example "Berlin" - completion will fire 6 times. I'd like to have a way to cancel a function or cancel previous completions.
Thanks in advance.
You should try debounce, basically in debounce before you fire a request you wait for short span(eg:- 2 secs), and if user types in that span again, timer is reset to again 2 secs,check the link
Once the call is made it cannot be cancelled, It will Either get executed to completition or timedout.
Once you invoke a callable function, it can't be canceled. The function will run to completion or timeout. You will need to be sure on the client that you really want to invoke the function. You are by no means obliged to consume the result (you can ignore it if you want), but the transaction will complete, unless the client app dies in the process. In that case, the function on the backend will still complete, but it will just not be able to deliver the response.

Wait for Firebase to load before returning from a function

I have a simple function loading data from Firebase.
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
Currently this function returns nil always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:
typealias DataClosure = (Data?, Error?) -> Void
/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {
static var downloadManager = DownloadManager()
private lazy var session: URLSession = {
return URLSession.shared
}()
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.
That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.
Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from #Raghav7890's answer to use a typealias:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler: #escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler:#escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}
Making Duncan answer more precise. You can make the function like this
func loadFromFireBase(completionHandler:#escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
You can return the songArray in a completion handler block.

iOS Concurrency issue: method returned before got the pedometer data

like the code below, when I want to return a pedometer data through a method for some connivence, but the method returned earlier than the data be retrieved.I think this maybe a concurrency issue. How could I return the data in a right way for future use?Thx
func queryPedometerTodayTotalData() -> Int {
var pedometerDataOfToday: CMPedometerData?
self.queryPedometerDataFromDate(NSDate.today()!, toDate: NSDate(), withHandler: { (pedometerData, error) in
pedometerDataOfToday = pedometerData!
print("this print after got data in a background thread:\(pedometerDataOfToday)")
})
print("This should print before last print, and there haven't got the data now: \(pedometerDataOfToday)")
return pedometerDataOfToday
}
You're right about it being a concurrency issue. You should use the result inside the handler of the queryPedometerDataFromDate.
One way of achieving this would be to use a completion block for your queryPedometerTodayTotalData method instead of having it return a value, like this:
func queryPedometerTodayTotalData(completion:((CMPedometerData?)->())) {
var pedometerDataOfToday: CMPedometerData?
self.queryPedometerDataFromDate(NSDate.today()!, toDate: NSDate(), withHandler: { (pedometerData, error) in
pedometerDataOfToday = pedometerData!
completion(pedometerData)
})
}
func testQueryPedometerTodayTotalData() {
self.queryPedometerTodayTotalData { (data) in
print(data)
}
}
It is a concurrency issue. queryPedometerDataFromDate is an asynchronous method. it executes the completion block whenever iOS deems it (which would usually be after it has retrieved the data) so that is why the 2nd print line prints first and doesnt return your result.
You need to either use the pedometerData from the completion block inside the completion block, or call a method/delegate that will handle the result of the completion block.

Keep my function from blocking functionality while getting info

I have a function with a completion handler that works to download videos from youtube's API.
On the first run, it works great. On subsequent runs, since the videos are stored in the device, and shown before the array of videos is updated, I would like for the user to be able to interact with the table, while it is updated (if required). However, while the information is being downloaded, the interaction with the table or the app is blocked.
I guess this has something to do with Grand Central Dispatch, but I don't know how to use it.
Networking().getPlaylists() { (result: String) -> () in
self.activityView.removeFromSuperview()
print("should reload data")
self.tableView.reloadData()
}
}
Could somebody give me some pointers?
Ideally your network code would execute on a background thread:
func getPlaylists() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//Do network stuff
})
}
but also before you call your completion, make sure you flip back to the main thread before you do UI logic like updating your tableview:
func getPlaylists() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//Do network stuff
dispatch_async(dispatch_get_main_queue()) {
completion(results)
}
})
}
It was actually not Alamofire that was blocking my UI (it is actually an async function), but it was SwiftyJSON while I was parsing. I solved it by dispatching_async but over the parsing section and not the rest of the function!
See more here: Alamofire http json request block ui
Thanks!

Resources