How do I make function that create delay in for loop what works on another thread so that main thread will not affects.
Let suppose I have 100s of data in my array and I want to pick data from array one by one and submit that data to process in 1-2 seconds of delay and Main thread should not be affected.
How do i achieve this, I tried many solution but they are not properly working
private func synchDataWithCloudIfAvailable() {
let arrUrl = SwiftFileManager.getListOfFileURLFromDictory(dicrectoryName: KSensor_Directory)
if arrUrl != nil {
if arrUrl!.count > 0 {
self.diskOperationSerialQueue?.async(execute: {
for url in arrUrl! {
let blockOperation = BlockOperation()
blockOperation.addExecutionBlock({
let data = SwiftFileManager.getDataFromFileUrl(url: url)
if data != nil {
//self.uploadDataToCloud(data: data!, localUrl: url)
//SwiftFileManager.deleteFileFromUrl(url: url)
}
})
self.backgroundQueue.addOperation(blockOperation)
usleep(useconds_t(QuaterSecond))
}
})
}
}
do you think usleep(useconds_t(QuaterSecond)) is good idea or is there any another better way of doing
Try this:
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(2), execute: {
print("do something here")
})
Related
I am trying to fetch bunch of data with for in loop function, but it doesn't return data in correct orders. It looks like some data take longer to fetch and so they are mixed up in an array where I need to have all the data in correct order. So, I used DispatchGroup. However, it's not working. Can you please let me know what I am doing wrong here? Spent past 10 hours searching for a solution... below is my code.
#IBAction func parseXMLTapped(_ sender: Any) {
let codeArray = codes[0]
for code in codeArray {
self.fetchData(code)
}
dispatchGroup.notify(queue: .main) {
print(self.dataToAddArray)
print("Complete.")
}
}
private func fetchData(_ code: String) {
dispatchGroup.enter()
print("count: \(count)")
let dataParser = DataParser()
dataParser.parseData(url: url) { (dataItems) in
self.dataItems = dataItems
print("Index #\(self.count): \(self.dataItems)")
self.dataToAddArray.append(self.dataItems)
}
self.dispatchGroup.leave()
dispatchGroup.enter()
self.count += 1
dispatchGroup.leave()
}
The problem with asynchronous functions is that you can never know in which order the blocks return.
If you need to preserve the order, use indices like so:
let dispatchGroup = DispatchGroup()
var dataToAddArray = [String](repeating: "", count: codeArray.count)
for (index, code) in codeArray.enumerated() {
dispatchGroup.enter()
DataParser().parseData(url: url) { dataItems in
dataToAddArray[index] = dataItems
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
print("Complete"
}
Also in your example you are calling dispatchGroup.leave() before the asynchronous block has even finished. That would also yield wrong results.
Using semaphores to eliminate all concurrency solves the order issue, but with a large performance penalty. Dennis has the right idea, namely, rather than sacrificing concurrency, instead, just sort the results.
That having been said, I would probably use a dictionary:
let group = DispatchGroup()
var results: [String: [DataItem]] // you didn't say what `dataItems` was, so I'll assume it's an array of `DataItem` objects; but this detail isn't material to the broader question
for code in codes {
group.enter()
DataParser().parseData(url: url) { dataItems in
results[code] = dataItems // if parseData doesn't already uses the main queue for its completion handler, then dispatch these two lines to the main queue
group.leave()
}
}
group.notify(queue: .main) {
let sortedResults = codes.compactMap { results[$0] } // this very efficiently gets the results in the right order
// do something with sortedResults
}
Now, I might advise constraining the degree of concurrency (e.g. maybe you want to constrain this to the number of CPUs or some reasonable fixed number (e.g. 4 or 6). That is a separate question. But I would advise against sacrificing concurrency just to get the results in the right order.
In this case, using DispatchSemaphore:
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
for code in codeArray {
self.fetchData(code)
semaphore.wait()
}
}
private func fetchData(_ code: String) {
print("count: \(count)")
let dataParser = DataParser()
dataParser.parseData(url: url) { (dataItems) in
self.dataItems = dataItems
print("Index #\(self.count): \(self.dataItems)")
self.dataToAddArray.append(self.dataItems)
semaphore.signal()
}
}
I have using a dispatch group wait() that block my a for loop from completing the code until a set of urlsession tasks (in another loop with completion handler) to be completed before appending new element to my array
the current code will finish the first loop before the second loop of urlClass.selectfoodURL is completed
I want to append the array in meal history after my urlfood for loop is completed
on of the problem in my approach of using dispatch groups is the wait(), when my select food is called the urlsession stuck and doesn’t complete with group.wait
func userSnackHistoryArray() {
let group = DispatchGroup()
let Arrays // array of dictionary
for array in Arrays {
var generateMeal = MealDetails() // struct type
do {
let aa = try JSONDecoder().decode(userSnack.self, from: array)
generateMeal.names = convertToJsonFile.type
for name in generateMeal.names!{
group.enter()
urlClass.selectfoodURL(foodName: name){ success in
generateMeal.units!.append(allVariables.selectedUnit)
group.leave()
}
}
// my select food is called but the urlsession stuck and doesnt complete with group.wait is active
// group.wait()
mealHistory.append(generateMeal)
} catch { }
}
group.notify(queue: .main){
print("complete")
}
}
I have shortened my code to focus on the problem ,, I can split my code into two functions and solve the problem , but I want to use only one function
any suggestions or ideas ?
Rather than waiting, you should just create a local array of values to be added, and then add them when it’s done:
func retrieveSnacks() {
var snacksToAdd: [Snack] = []
let group = DispatchGroup()
...
for url in urls {
group.enter()
fetchSnack(with: url) { result in
dispatchPrecondition(condition: .onQueue(.main)) // note, I’m assuming that this closure is running on the main queue; if not, dispatch this appending of snacks (and `leave` call) to the main queue
if case .success(let snack) = result {
snacksToAdd.append(snack)
}
group.leave()
}
}
// when all the `leave` calls are called, only then append the results
group.notify(queue: .main) {
self.snacks += snacksToAdd
// trigger UI update, or whatever, here
}
}
Note, the above does not assure that the objects are added in the original order. If you need that, you can use a dictionary to build the temporary results and then append the results in sorted order:
func retrieveSnacks() {
var snacksToAdd: [URL: Snack] = [:]
let group = DispatchGroup()
...
for url in urls {
group.enter()
fetchSnack(with: url) { result in
if case .success(let snack) = result {
snacksToAdd[url] = snack
}
group.leave()
}
}
group.notify(queue: .main) {
let sortedSnacks = urls.compactMap { snacksToAdd[$0] }
self.snacks += sortedSnacks
// trigger UI update, or whatever, here
}
}
Finally, I might suggest adopting a completion handler pattern:
func retrieveSnacks(completion: #escaping ([Snack]) -> Void) {
var snacksToAdd: [URL: Snack] = [:]
let group = DispatchGroup()
...
for url in urls {
group.enter()
fetchSnack(with: url) { result in
if case .success(let snack) = result {
snacksToAdd[url] = snack
}
group.leave()
}
}
group.notify(queue: .main) {
let sortedSnacks = urls.compactMap { snacksToAdd[$0] }
completion(sortedSnacks)
}
}
retrieveSnacks { addedSnacks in
self.snacks += addedSnacks
// update UI here
}
This pattern ensures that you don’t entangle your network-related code with your UI code.
I apologize that the above is somewhat refactored from your code snippet, but there wasn’t enough there for me to illustrate what precisely it would look like. But hopefully the above illustrates the pattern and you can see how you’d apply it to your code base. So, don’t get lost in the details, but focus on the basic pattern of building records to be added in a local variable and only update the final results in the .notify block.
FWIW, this is the method signature for the method that the above snippets are using to asynchronously fetch the objects in question.
func fetchSnack(with url: URL, completion: #escaping (Result<Snack, Error>) -> Void) {
...
// if async fetch not successful
DispatchQueue.main.async {
completion(.failure(error))
}
// if successful
DispatchQueue.main.async {
completion(.success(snack))
}
}
I'm kind of new to programming in general, so I have this maybe simple question. Actually, writing helps me to identify the problem faster.
Anyway, I have an app with multiple asynchronous calls, they are nested like this:
InstagramUnoficialAPI.shared.getUserId(from: username, success: { (userId) in
InstagramUnoficialAPI.shared.fetchRecentMedia(from: userId, success: { (data) in
InstagramUnoficialAPI.shared.parseMediaJSON(from: data, success: { (media) in
guard let items = media.items else { return }
self.sortMediaToCategories(media: items, success: {
print("success")
// Error Handlers
Looks horrible, but that's not the point. I will investigate the Promise Kit once I get this working.
I need the sortMediaToCategories to wait for completion and then reload my collection view. However, in the sortMediaToCategories I have another nested function, which is async too and has a for in loop.
func sortMediaToCategories(media items: [StoryData.Items],
success: #escaping (() -> Swift.Void),
failure: #escaping (() -> Swift.Void)) {
let group = DispatchGroup()
group.enter()
for item in items {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: { group.notify(queue: .global(), execute: {
self.collectionView.reloadData()
group.leave()
}) },
failure: { print("error") })
//....
I can't afford the collection view to reload every time obviously, so I need to wait for loop to finish and then reload.
I'm trying to use Dispatch Groups, but struggling with it. Could you please help me with this? Any simple examples and any advice will be very appreciated.
The problem you face is a common one: having multiple asynchronous tasks and wait until all are completed.
There are a few solutions. The most simple one is utilising DispatchGroup:
func loadUrls(urls: [URL], completion: #escaping ()->()) {
let grp = DispatchGroup()
urls.forEach { (url) in
grp.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
// handle error
// handle response
grp.leave()
}.resume()
}
grp.notify(queue: DispatchQueue.main) {
completion()
}
}
The function loadUrls is asynchronous and expects an array of URLs as input and a completion handler that will be called when all tasks have been completed. This will be accomplished with the DispatchGroup as demonstrated.
The key is, to ensure that grp.enter() will be called before invoking a task and grp.leave is called when the task has been completed. enter and leave shall be balanced.
grp.notify finally registers a closure which will be called on the specified dispatch queue (here: main) when the DispatchGroup grp balances out (that is, its internal counter reaches zero).
There are a few caveats with this solution, though:
All tasks will be started nearly at the same time and run concurrently
Reporting the final result of all tasks via the completion handler is not shown here. Its implementation will require proper synchronisation.
For all of these caveats there are nice solutions which should be implemented utilising suitable third party libraries. For example, you can submit the tasks to some sort of "executer" which controls how many tasks run concurrently (match like OperationQueue and async Operations).
Many of the "Promise" or "Future" libraries simplify error handling and also help you to solve such problems with just one function call.
You can reloadData when the last item calls the success block in this way.
let lastItemIndex = items.count - 1
for(index, item) in items.enumerated() {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else {return}
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: {
if index == lastItemIndex {
DispatchQueue.global().async {
self.collectionView.reloadData()
}
}
},
failure: { print("error") })
}
You have to move the group.enter() call inside your loop. Calls to enter and leave have to be balanced. If your callbacks of the mediaToStorageDistribution function for success and failure are exclusive you also need to leave the group on failure. When all blocks that called enter leave the group notify will be called. And you probably want to replace the return in your guard statement with a break, to just skip items with missing URLs. Right now you are returning from the whole sortMediaToCatgories function.
func sortMediaToCategories(media items: [StoryData.Items], success: #escaping (() -> Void), failure: #escaping (() -> Void)) {
let group = DispatchGroup()
for item in items {
if item.media_type == 1 {
guard let url = URL(string: (item.image_versions2?.candidates?.first!.url)!) else { break }
group.enter()
mediaToStorageDistribution(withImageUrl: url,
videoUrl: nil,
mediaType: .jpg,
takenAt: item.taken_at,
success: { group.leave() },
failure: {
print("error")
group.leave()
})
}
}
group.notify(queue: .main) {
self.collectionView.reloadData()
}
}
What is the best and easiest way to implement this flowchart in a function?
Right now I'm using two dispatch groups but I need to check if they're both done, and not only when they finish.
If they are done then:
friends array will have elements
nicknames array will have elements
note: FB is Facebook and FIR is Firebase database
You could do this using DispatchGroup. Try the following playground;
import UIKit
import XCPlayground
let dispatchGroup = DispatchGroup.init()
for index in 0...4 {
dispatchGroup.enter()
let random = drand48()
let deadline = DispatchTime.now() + random/1000
print("entered \(index)")
DispatchQueue.global(qos: .background).asyncAfter(deadline: deadline, execute: {
print("leaving \(index)")
dispatchGroup.leave()
})
}
dispatchGroup.notify(queue: .global()) {
print("finished all")
}
which should output something similar to
entered 0
leaving 0
entered 1
entered 2
leaving 1
leaving 2
entered 3
leaving 3
entered 4
leaving 4
finished all
Swift 5 + Async await
Let's imagine that you want to load 3 images at the same time and wait for them to be downloaded to present on the screen.
Task {
do {
// Call first function and proceed to next step
async let image_1 = try firstAsyncMethod()
// Call second function and proceed to next step
async let image_2 = try secondAsyncMethod()
// Call function and proceed to next step
async let image_3 = try thirdAsyncMethod()
let images = try await [image_1, image_2, image_3]
// Display images
} catch {
// Handle Error
}
}
You can implement this flow chart in Swift 3 like that.
let fbFriendsArray : [String] = []
let firNickNames :: [String] = []
func flowChart() {
let myGroupOuter = DispatchGroup()
myGroupOuter.enter()
fetchFBfriends(completionHandler: {(isSuccess : Bool) in
myGroupOuter.leave()
})
myGroupOuter.enter()
fetchFIRNickNames(completionHandler: {(isSuccess : Bool) in
myGroupOuter.leave()
})
myGroupOuter.notify(queue: DispatchQueue.main) {
if (fbFriendsArray.isEmpty || firNickNames.isEmpty) {
/// Present Your Error Logic
} else {
/// Fetch Games Logic here
}
}
}
fetchFBfriends and fetchFIRNickNames are functions that are responsible to get the data from Facebook and Firebase.
So my goal here is to basically perform a query in a while loop and append results of the query to my array. When I run the code my "level" variable starts from zero and increments infinitely. I'm highly convinced that my problem is caused by fact that my code is running on 2 async queues but just can't figure out the exact cause.
func displayPathOf(argument: Argument, threadTableView: UITableView) {
array.removeAll()
threadTableView.reloadData()
var level = argument.level!-1
array.insert(argument, at: 0)
var stop = false
DispatchQueue.global(qos: .userInteractive).async {
repeat {
level += 1
print(level)
let query = Argument.query()?.whereKey("level", equalTo: level).addDescendingOrder("reach")
query?.getFirstObjectInBackground(block: { (object, error) in
if object != nil {
DispatchQueue.main.async {
array.append(object as! Argument)
print(array)
threadTableView.reloadData()}
} else {
stop = true
print(error)
}
})
} while stop == false
}
}
Your code boils down to:
do-in-background {
repeat {
level += 1
do-in-background { ... }
} while stop == false
}
do-in-background (i.e. both async and getFirstObjectInBackground) returns immediately, so from the point of view of this loop, it doesn't matter what's in the block. This is equivalent to a tight loop incrementing level as fast as it can.
It looks like you're trying to serialize your calls to getFirstObjectInBackground. You can do that one of two ways:
Have your completion block kick off the next search itself and remove the repeat loop.
Use a DispatchGroup to wait until the completion block is done.
In your case, I'd probably recommend the first. Get rid of stop and make a function something (vaguely) like:
func fetchObject(at level: Int) {
let query = Argument.query()?.whereKey("level", equalTo: level).addDescendingOrder("reach")
query?.getFirstObjectInBackground(block: { (object, error) in
if let object = object {
DispatchQueue.main.async {
array.append(object as! Argument)
print(array)
threadTableView.reloadData()}
// Schedule the next loop
DispatchQueue.global(qos: .userInteractive).async { fetchObject(level + 1) }
} else {
print(error)
}
})
}