Problem:
I need to loop through a Firebase query call.
My loop is initiated before the Firebase call as it holds the uids needed.
for uid in following_uids {
}
What is the proper way to loop variable uid into the Firebase reference & query?
for uid in following_uids {
let fbRef = ref.child("users").child(uid).child("posts")
//query firebase call
}
Replication:
func getRecentPostsFromFollowers(following_uids: [String]) {
for uid in following_uids {// first loop
let fbRef = ref.child("users").child(uid).child("posts")
fbRef.queryOrderedByKey().queryLimited(toLast: 5).observeSingleEvent(of: .value, with: {snapshot in
if(snapshot.exists()){
let values = snapshot.children.compactMap { ($0 as? DataSnapshot)?.value }
for postData in values {
guard let restDict = (postData as AnyObject) as? [String: Any] else { continue }
//do with data
}
}
})
}//end first loop
print("Completion handler - Loop Done")
}
PSEUDO code:
func getFollowerUIDS(completion: #escaping (_ followers: [String]) -> Void) {
for uid in following_uids {
let fbRef = ref.child("users").child(uid).child("posts")
//query firebase call
}
completion(value)
}
Specs:
Xcode Version 14.2 (14C18)
iOS
I'm assuming you'd like to combine the results from all user posts and return them as a single array of posts.
When you call observeSingleEvent on your document reference, the second parameter is a type of closure. This is essentially just another function that the Firebase SDK will call when it has the data ready for you.
The idea is that: because it may take some time to fetch this data, we don't want to block the rest of your function/code from running while the network call is taking place.
This means you will likely see the end of the loop "Completion handler - Loop Done" called before your data is made available.
This means your getRecentPostsFromFollowers function will return before any of your closures are called with data.
In order to allow callers of your getRecentPostsFromFollowers function to know when the data is ready, you can add your own completion handler to provide this data back to them.
This is a closure that you will call when you know all the data is ready.
However, because there's multiple closures with data that we need to combine, we can use something like a DispatchGroup for this purpose.
We'll combine the posts in an allPosts variable and return the data when it's combined from all the requests.
We need to lock around the access to allPosts as observeSingleEvent's completion handler can run on any thread, and multiple threads could try to access this variable at once.
typealias PostData = [String: Any]
func getRecentPostsFromFollowers(following_uids: [String], completion: #escaping ([PostData]) -> Void) {
let group = DispatchGroup()
let lock = NSLock()
var allPosts = [PostData]()
for uid in following_uids {
let fbRef = ref.child("users").child(uid).child("posts")
group.enter()
fbRef.queryOrderedByKey().queryLimited(toLast: 5).observeSingleEvent(of: .value, with: {snapshot in
defer { group.leave() }
if(snapshot.exists()){
let values = snapshot.children.compactMap { ($0 as? DataSnapshot)?.value }
lock.lock()
defer { lock.unlock() }
for postData in values {
guard let restDict = (postData as AnyObject) as? PostData else { continue }
allPosts.append(restData)
}
}
})
}
group.notify(queue: .main) {
completion(allPosts)
}
}
Side Notes
Swift async/await is a more modern way to handle asynchronous-based tasks like this without some of the pitfalls in this solution, it's worth looking into.
Performing a separate Firestore query for each user to retrieve all their data is not very efficient.
This is because each query requires a separate network request and will cost you at least one document read per query made, regardless if there are results or not per-user.
You should consider structuring your data within your Firestore database so you can return all the data you require in a single query.
This may involve denormalizing some of your data to allow for more efficient queries.
I apologize if this question is simple or the problem is obvious as I am still a beginner in programming.
I am looping over an array and trying to make an async Firestore call. I am using a DispatchGroup in order to wait for all iterations to complete before calling the completion.
However, the Firestore function is not even getting called. I tested with print statements and the result is the loop iterations over the array have gone through with an enter into the DispatchGroup each time and the wait is stuck.
func getUserGlobalPlays(username: String, fixtureIDs: [Int], completion: #escaping (Result<[UserPlays]?, Error>) -> Void) {
let chunkedArray = fixtureIDs.chunked(into: 10)
var plays: [UserPlays] = []
let group = DispatchGroup()
chunkedArray.forEach { ids in
group.enter()
print("entered")
DispatchQueue.global().async { [weak self] in
self?.db.collection("Users").document("\(username)").collection("userPlays").whereField("fixtureID", in: ids).getDocuments { snapshot, error in
guard let snapshot = snapshot, error == nil else {
completion(.failure(error!))
return
}
for document in snapshot.documents {
let fixtureDoc = document.data()
let fixtureIDx = fixtureDoc["fixtureID"] as! Int
let choice = fixtureDoc["userChoice"] as! Int
plays.append(UserPlays(fixtureID: fixtureIDx, userChoice: choice))
}
group.leave()
print("leaving")
}
}
}
group.wait()
print(plays.count)
completion(.success(plays))
}
There are a few things going on with your code I think you should fix. You were dangerously force-unwrapping document data which you should never do. You were spinning up a bunch of Dispatch queues to make the database calls in the background, which is unnecessary and potentially problematic. The database call itself is insignificant and doesn't need to be done in the background. The snapshot return, however, can be done in the background (which this code doesn't do, so you can add that if you wish). And I don't know how you want to handle errors here. If one document gets back an error, your code sends back an error. Is that how you want to handle it?
func getUserGlobalPlays(username: String,
fixtureIDs: [Int],
completion: #escaping (_result: Result<[UserPlays]?, Error>) -> Void) {
let chunkedArray = fixtureIDs.chunked(into: 10)
var plays: [UserPlays] = []
let group = DispatchGroup()
chunkedArray.forEach { id in
group.enter()
db.collection("Users").document("\(username)").collection("userPlays").whereField("fixtureID", in: id).getDocuments { snapshot, error in
if let snapshot = snapshot {
for doc in snapshot.documents {
if let fixtureIDx = doc.get("fixtureIDx") as? Int,
let choice = doc.get("choice") as? Int {
plays.append(UserPlays(fixtureID: fixtureIDx, userChoice: choice))
}
}
} else if let error = error {
print(error)
// There was an error getting this one document. Do you want to terminate
// the entire function and pass back an error (through the completion
// handler)? Or do you want to keep going and parse whatever data you can
// parse?
}
group.leave()
}
}
// This is the completion handler of the Dispatch Group.
group.notify(queue: .main) {
completion(.success(plays))
}
}
I would like to simulate async and await request from Javascript to Swift 4. I searched a lot on how to do it, and I thought I found the answer with DispatchQueue, but I don't understand how it works.
I want to do a simple stuff:
if let items = result.value {
var availableBornes = [MGLPointFeature]()
for item in items {
guard let id = item.id else { continue }
let coordinate = CLLocationCoordinate2D(latitude: Double(coor.x), longitude: Double(coor.y))
// ...
// This is an asynchronous request I want to wait
await _ = directions.calculate(options) { (waypoints, routes, error) in
guard error == nil else {
print("Error calculating directions: \(error!)")
return
}
// ...
if let route = routes?.first {
let distanceFormatter = LengthFormatter()
let formattedDistance = distanceFormatter.string(fromMeters: route.distance)
item.distance = formattedDistance
// Save feature
let feature = MGLPointFeature()
feature.attributes = [
"id": id,
"distance": formattedDistance
]
availableBornes.append(feature)
}
}
}
// This should be called after waiting for the async requests
self.addItemsToMap(availableBornes: availableBornes)
}
What should I do?
Thanks to vadian's comment, I found what I expected, and it's pretty easy. I use DispatchGroup(), group.enter(), group.leave() and group.notify(queue: .main){}.
func myFunction() {
let array = [Object]()
let group = DispatchGroup() // initialize
array.forEach { obj in
// Here is an example of an asynchronous request which use a callback
group.enter() // wait
LogoRequest.init().downloadImage(url: obj.url) { (data) in
if (data) {
group.leave() // continue the loop
}
}
}
group.notify(queue: .main) {
// do something here when loop finished
}
}
We have to await!
The async-await Swift Evolution proposal SE-0296 async/await was accepted after 2 pitches and revision modifications recently on December 24th 2020. This means that we will be able to use the feature in Swift 5.5. The reason for the delay is due to backwards-compatibility issues with Objective-C, see SE-0297 Concurrency Interoperability with Objective-C. There are many side-effects and dependencies of introducing such a major language feature, so we can only use the experimental toolchain for now. Because SE-0296 had 2 revisions, SE-0297 actually got accepted before SE-0296.
General Use
We can define an asynchronous function with the following syntax:
private func raiseHand() async -> Bool {
sleep(3)
return true
}
The idea here is to include the async keyword alongside the return type since the call site will return (BOOL here) when complete if we use the new await keyword.
To wait for the function to complete, we can use await:
let result = await raiseHand()
Synchronous/Asynchronous
Defining synchronous functions as asynchronous is ONLY forward-compatible - we cannot declare asynchronous functions as synchronous. These rules apply for function variable semantics, and also for closures when passed as parameters or as properties themselves.
var syncNonThrowing: () -> Void
var asyncNonThrowing: () async -> Void
...
asyncNonThrowing = syncNonThrowing // This is OK.
Throwing functions
The same consistency constraints are applied to throwing functions with throws in their method signature, and we can use #autoclosures as long as the function itself is async.
We can also use try variants such as try? or try! whenever we await a throwing async function, as standard Swift syntax.
rethrows unfortunately still needs to go through Proposal Review before it can be incorporated because of radical ABI differences between the async method implementation and the thinner rethrows ABI (Apple wants to delay the integration until the inefficiencies get ironed out with a separate proposal).
Networking callbacks
This is the classic use-case for async/await and is also where you would need to modify your code:
// This is an asynchronous request I want to wait
await _ = directions.calculate(options) { (waypoints, routes, error) in
Change to this:
func calculate(options: [String: Any]) async throws -> ([Waypoint], Route) {
let (data, response) = try await session.data(from: newURL)
// Parse waypoints, and route from data and response.
// If we get an error, we throw.
return (waypoints, route)
}
....
let (waypoints, routes) = try await directions.calculate(options)
// You can now essentially move the completion handler logic out of the closure and into the same scope as `.calculate(:)`
The asynchronous networking methods such as NSURLSession.dataTask now has asynchronous alternatives for async/await. However, rather than passing an error in the completion block, the async function will throw an error. Thus, we have to use try await to enable throwing behaviour. These changes are made possible because of SE-0297 since NSURLSession belongs to Foundation which is still largely Objective-C.
Code impacts
This feature really cleans up a codebase, goodbye Pyramid of Doom 👋!
As well as cleaning up the codebase, we improve error handling for nested networking callbacks since the error and result are separated.
We can use multiple await statements in succession to reduce the dependency on DispatchGroup. 👋 to Threading Deadlocks when synchronising DispatchGroups across different DispatchQueues.
Less error-prone because the API is clearer to read. Not considering all exit paths from a completions handler, and conditional branching means subtle bugs can build up that are not caught at compile time.
async / await is not back-deployable to devices running < iOS 13, so we have to add if #available(iOS 13, *) checks where supporting old devices. We still need to use GCD for older OS versions.
(Note: Swift 5 may support await as you’d expect it in ES6!)
What you want to look into is Swift's concept of "closures". These were previously known as "blocks" in Objective-C, or completion handlers.
Where the similarity in JavaScript and Swift come into play, is that both allow you to pass a "callback" function to another function, and have it execute when the long-running operation is complete. For example, this in Swift:
func longRunningOp(searchString: String, completion: (result: String) -> Void) {
// call the completion handler/callback function
completion(searchOp.result)
}
longRunningOp(searchString) {(result: String) in
// do something with result
}
would look like this in JavaScript:
var longRunningOp = function (searchString, callback) {
// call the callback
callback(err, result)
}
longRunningOp(searchString, function(err, result) {
// Do something with the result
})
There's also a few libraries out there, notably a new one by Google that translates closures into promises: https://github.com/google/promises. These might give you a little closer parity with await and async.
You can use this framework for Swift coroutines - https://github.com/belozierov/SwiftCoroutine
Unlike DispatchSemaphore, when you call await it doesn’t block the thread but only suspends coroutine, so you can use it in the main thread as well.
func awaitAPICall(_ url: URL) throws -> String? {
let future = URLSession.shared.dataTaskFuture(for: url)
let data = try future.await().data
return String(data: data, encoding: .utf8)
}
func load(url: URL) {
DispatchQueue.main.startCoroutine {
let result1 = try self.awaitAPICall(url)
let result2 = try self.awaitAPICall2(result1)
let result3 = try self.awaitAPICall3(result2)
print(result3)
}
}
In iOS 13 and up, you can now do this using Combine. Future is analogous to async and the flatMap operator on publishers (Future is a publisher) is like await. Here's an example, loosely based on your code:
Future<Feature, Error> { promise in
directions.calculate(options) { (waypoints, routes, error) in
if let error = error {
promise(.failure(error))
}
promise(.success(routes))
}
}
.flatMap { routes in
// extract feature from routes here...
feature
}
.receiveOn(DispatchQueue.main) // UI updates should run on the main queue
.sink(receiveCompletion: { completion in
// completion is either a .failure or it's a .success holding
// the extracted feature; if the process above was successful,
// you can now add feature to the map
}, receiveValue: { _ in })
.store(in: &self.cancellables)
Edit: I went into more detail in this blog post.
You can use semaphores to simulate async/await.
func makeAPICall() -> Result <String?, NetworkError> {
let path = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: path) else {
return .failure(.url)
}
var result: Result <String?, NetworkError>!
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
result = .success(String(data: data, encoding: .utf8))
} else {
result = .failure(.server)
}
semaphore.signal()
}.resume()
_ = semaphore.wait(wallTimeout: .distantFuture)
return result
}
And here is example how it works with consecutive API calls:
func load() {
DispatchQueue.global(qos: .utility).async {
let result = self.makeAPICall()
.flatMap { self.anotherAPICall($0) }
.flatMap { self.andAnotherAPICall($0) }
DispatchQueue.main.async {
switch result {
case let .success(data):
print(data)
case let .failure(error):
print(error)
}
}
}
}
Here is the article describing it in details.
And you can also use promises with PromiseKit and similar libraries
Async/await is now officially supported in Swift.
It would yield be something like this
func myFunction() async throws {
let array: [Object] = getObjects()
let images = try await withThrowingTaskGroup(of: Data.self, returning: [Data].self) { group in
array.forEach { object in
group.async {
try await LogoRequest().downloadImage(url: object.url)
}
}
return try await group.reduce([], {$0 + [$1]})
}
// at this point all `downloadImage` are done, and `images` is populated
_ = images
}
Use async/ await below like this,
enum DownloadError: Error {
case statusNotOk
case decoderError
}
Method Call
override func viewDidLoad() {
super.viewDidLoad()
async{
do {
let data = try await fetchData()
do{
let json = try JSONSerialization.jsonObject(with: data, options:.mutableLeaves)
print(json)
}catch{
print(error.localizedDescription)
}
}catch{
print(error.localizedDescription)
}
}
}
func fetchData() async throws -> Data{
let url = URL(string: "https://www.gov.uk/bank-holidays.json")!
let request = URLRequest(url:url)
let (data,response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else{
throw DownloadError.statusNotOk
}
return data
}
Here you can see a basic difference, how the callBack replacing by async/await
Basically, scene A and scene B = Call API by the closure.
Scene C and scene D= Calling API by Async/Await.
Scene E = Serial API call by nested closure.
Scene F= Serial API call by Async/Await.
Scene G = Parallel API call by Async/Await.
Parallel API call
I am new to async and sync mechanism.
In my code I have to perform one code line only after the other is done.
It looks something like this:
func something(){
let workerQueue = DispatchQueue.global(qos: .userInitiated)
workerQueue.async{
let info = getInfoFromTheWeb()//I need the info value in order to perform the next line
saveToDB(info: info)
DispatchQueue.main.async {
//update a label text in the ui after getting and saving that info
}
}
}
Your professional thoughts please..
Your should DispatchGroup. By using DispatchGroup one function/line of code will wait until the other function completes execution.
For example
let myGroup = DispatchGroup()
myGroup.enter()
let info = getInfoFromTheWeb()
When you get info from simple call
myGroup.leave()
When your call leave() function following code will be execute
myGroup.notify(queue: DispatchQueue.main) {
saveToDB(info: info)
/// Update UI elements
}
Try something like this:
func something() {
let workerQueue = DispatchQueue.global(qos: .userInitiated)
workerQueue.async{
if let info = getInfoFromTheWeb() {
saveToDB(info: info)
DispatchQueue.main.async {
//update a label text in the ui after getting and saving that info
}
}
}
I'm building an app, where I need to load data in chunks, I mean first load 5 items and then proceed with another 5, but I can't figure out how to do that. At the moment I chunk up my list of items, so I get a list of lists with 5 items in each. Right now the for-loop just fires away with requests, but I want to wait for the response and then proceed in the for loop.
I use alamofire, and my code looks like this.
private func requestItemsForField(items: [Item], completion: #escaping (_ measurements: Array<Measurement>?, _ success: Bool) -> ()) {
let userPackageId = UserManager.instance.selectedUserPackage.id
let params = ["userPackageId": userPackageId]
for field in fields {
let url = apiURL + "images/\(field.id)"
let queue = DispatchQueue(label: "com.response-queue", qos: .utility, attributes: [.concurrent])
Alamofire.request(url, method: .get, parameters: params, headers: headers()).responseArray(queue: queue, completionHandler: { (response: DataResponse<[Item]>) in
if let items = response.result.value as [Item]? {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "itemsLoadedNotification"), object: nil)
completion(items, true)
}
else {
print("Request failed with error: \(response.result.error)")
completion(nil, false)
}
})
}
}
This is where i chunk up my list, and pass it to the above.
private func fetchAllMeasurements(completion: #escaping (_ measurements: [Item]?, _ done: Bool) -> ()) {
let fieldSet = FieldStore.instance.data.keys
var fieldKeys = [Item]()
for field in fieldSet {
fieldKeys.append(field)
}
// Create chunks of fields to load
let fieldChunks = fieldKeys.chunkify(by: 5)
var measurementsAll = [Measurement]()
for fields in fieldChunks {
requestItemsForField(fields: fields, completion: { (measurements, success) in
if let currentMeasurement = measurements {
measurementsAll.append(contentsOf: currentMeasurement)
}
completion(measurementsAll, true)
}
})
}
}
you need to get number of measurements you will have (for example server has 34 measurements) with your request and then code something like
var serverMeasurementsCount = 1 //should be for first request
func requestData() {
if self.measurements.count < self.serverMeasurementsCount {
...requestdata { data in
self.serverMeasurementsCount = data["serverMeasurementsCount"]
self.measurements.append(..yourData)
self.requestData()
}
}
or call requestData not inside completion handler or somewhere else
edit: fixed code a bit (serverMeasurementsCount = 1)
Instead of using a for loop, it sounds like you need do something like var index = 0 to start with, and call requestItemsForField() sending in fieldChunks[index] as the first parameter. Then in the completion handler, check to see whether there's another array element, and if so, call requestItemsForField() again, this time sending in fieldChunks[index+1] as the first parameter.
One solution would be to make a new recursive function to populate the items, add a new Bool parameter in closure as isComplete. then call the function on completion of isComplete boolean. to break the recursive function, add a global static variable of itemsCountMax, if itemCountMax == itemsCount break the recursive function.