Using DispatchGroup() in Swift 3 to perform a task? - ios

With Swift 3, using GCD has changed to DispatchGroup(), and I'm trying to learn to use it in my code.
Currently I have a function in another class that attempts to download a file and output its speed. I like to have that function finish first because I assign its speed to a var that I will be using in the first class to perform other tasks that is dependent upon that var.
It goes something like this:
Second class:
func checkSpeed()
{
// call other functions and perform task to download file from link
// print out speed of download
nMbps = speedOfDownload
}
First Class:
let myGroup = DispatchGroup()
let check: SecondClass = SecondClass()
myGroup.enter()
check.checkSpeed()
myGroup.leave()
myGroup.notify(queue: DispatchQueue.main, execute: {
print("Finished all requests.")
print("speed = \(check.nMbps)")
})
The problem is Finish all requests gets output first, thus returning nil for speed, then afterwards checkSpeed finishes and outputs the correct download speed.
I believe I'm doing this wrong, but I'm not sure?
How can I ensure that speed obtains the correct value after the completion of checkSpeed in my first class?
The details of checkSpeed is exactly the same as connectedToNetwork from GitHub: connectedness.swift

You need to call DispatchGroup.leave() when the entered task has completed. So, in your code, myGroup.leave() needs be placed at the end of the completion handler inside your checkSpeed() method.
You may need to modify your code like this:
func checkSpeed(in myGroup: DispatchGroup) {
//...
...downLoadTask... {...its completion handler... in
//...
// print out speed of download
nMbps = speedOfDownload
myGroup.leave() //<- This needs to be placed at the end of the completion handler
}
//You should not place any code after invoking asynchronous task.
}
And use it as:
myGroup.enter()
check.checkSpeed(in: myGroup)
myGroup.notify(queue: DispatchQueue.main, execute: {
print("Finished all requests.")
print("speed = \(check.nMbps)")
})
But, as noted in vadian's comment or Pangu's answer, you usually do not use DispatchGroup for a single asynchronous task.
ADDITION
I need to say, I strongly recommend completion handler pattern shown in Pangu's answer. It's a more general way to handle asynchronous tasks.
If you have modified your checkSpeed() to checkSpeed(completion:) as suggested, you can easily experiment DispatchGroup like this:
let myGroup = DispatchGroup()
let check: SecondClass = SecondClass()
let anotherTask: ThirdClass = ThirdClass()
myGroup.enter() //for `checkSpeed`
myGroup.enter() //for `doAnotherAsync`
check.checkSpeed {
myGroup.leave()
}
anotherTask.doAnotherAsync {
myGroup.leave()
}
myGroup.notify(queue: DispatchQueue.main) {
print("Finished all requests.")
print("speed = \(check.nMbps)")
}

With hint provided in the comment and solution found here: from #vadian, since I am only performing one task, I used a async completion handler:
Second Class:
func checkSpeed(completion: #escaping () -> ())
{
// call other functions and perform task to download file from link
// print out speed of download
nMbps = speedOfDownload
completion()
}
First Class:
let check: SecondClass = SecondClass()
check.checkSpeed {
print("speed = \(check.nMbps)")
}
Now checkSpeed will complete first and speed is assigned the appropriate value.

Related

How to change a value outside, when inside a Dispatch Semaphore (Edited)

i have a question, and I will tell you as clear as possible:
I need to get an object to my func, create a variable version of it, change some property values one by one, and use new version of it for saving to the cloud. My problem is, when I declare my variable, if I change property values inside my Dispatch Semaphore, the variable outside not changing somehow, there is some problem that I could not understand. Here is the code :
func savePage(model: PageModel, savingHandler: #escaping (Bool) -> Void) {
// some code .....
var page = model // (1) I created a variable from function arg
let newQueue = DispatchQueue(label: "image upload queue")
let semaphore = DispatchSemaphore(value: 0)
newQueue.async {
if let picURL1 = model.picURL1 {
self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
let url = urlString // (2) urlString value true and exist
page.picURL1 = url // (3) modified the new created "page" object
print(page.picURL1!) // (4) it works, object prints modified
}
}
semaphore.signal()
}
semaphore.wait()
newQueue.async {
if let picURL2 = model.picURL2 {
self.saveImagesToFireBaseStorage(pictureURL: picURL2) { urlString in
let url = urlString
page.picURL2 = url
}
}
semaphore.signal()
}
semaphore.wait()
print(page.picURL1!) //(5) "page" object has the old value?
newQueue.async {
print(page.picURL1!) //(6) "page" object has the old value?
do {
try pageDocumentRef.setData(from: page)
savingHandler(true)
} catch let error {
print("Error writing city to Firestore: \(error)")
}
semaphore.signal()
}
semaphore.wait()
}
I should upload some pictures to the cloud and get their urls, so I can create updated version of the object and save onto the old version on the cloud. But "page" object doesn't change somehow. When inside the semaphore, it prints right value, when outside, or inside another async semaphore block, it prints old value. I am new to the concurrency and could not find a way.
What I tried before :
Using Operation Queue and adding blocks as dependencies.
Creating queue as DispatchQueue.global()
What is the thing I am missing here?
Edit : I added semaphore.wait() after second async call. It actually was in my code but I accidentally deleted it while pasting to the question, thanks to Chip Jarred for pointing it.
UPDATE
This is how I changed the code with async - await:
func savePage(model: PageModel, savingHandler: #escaping () -> Void) {
// Some code
Task {
do {
let page = model
let updatedPage = try await updatePageWithNewImageURLS(page)
// some code
} catch {
// some code
}
}
// Some code
}
private func saveImagesToFireBaseStorage(pictureURL : String?) async -> String? {
//some code
return downloadURL.absoluteString
}
private func updatePageWithNewImageURLS(_ page : PageModel) async throws -> PageModel {
let picUrl1 = await saveImagesToFireBaseStorage(pictureURL: page.picURL1)
let picUrl2 = await saveImagesToFireBaseStorage(pictureURL: page.picURL2)
let picUrl3 = await saveImagesToFireBaseStorage(pictureURL: page.picURL3)
let newPage = page
return try await addNewUrlstoPage(newPage, url1: picUrl1, url2: picUrl2, url3: picUrl3)
}
private func addNewUrlstoPage(_ page : PageModel, url1: String?, url2 : String?, url3 :String?) async throws -> PageModel {
var newPage = page
if let url1 = url1 { newPage.picURL1 = url1 }
if let url2 = url2 { newPage.picURL2 = url2 }
if let url3 = url3 { newPage.picURL3 = url3 }
return newPage
}
So one async function gets a new photo url, for an old url, second async function runs multiple of this first function inside to get new urls for all three urls of the object, then calls a third async function a create and return an updated object with the new values.
This is my first time using async - await.
Let's look at your first async call:
newQueue.async {
if let picURL1 = model.picURL1 {
self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
let url = urlString // (2) urlString value true and exist
page.picURL1 = url // (3) modified the new created "page" object
print(page.picURL1!) // (4) it works, object prints modified
}
}
semaphore.signal()
}
I would guess that the inner closure, that is the one passed to saveImagesToFireBaseStorage, is also called asynchronously. If I'm right about that, then saveImagesToFireBaseStorage returns almost immediately, executes the signal, but the inner closure has not run yet, so the new value isn't yet set. Then after some latency, the inner closure is finally called, but that's after your "outer" code that depends on page.picURL1 has already been run, so page.picURL1 ends up being set afterwards.
So you need to call signal in the inner closure, but you still have to handle the case where the inner closure isn't called. I'm thinking something like this:
newQueue.async {
if let picURL1 = model.picURL1 {
self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
let url = urlString
page.picURL1 = url
print(page.picURL1!)
semaphore.signal() // <--- ADDED THIS
}
/*
If saveImagesToFireBaseStorage might not call the closure,
such as on error, you need to detect that and call signal here
in the case as well, or perhaps in some closure that's called in
the failure case. That depends on your design.
*/
}
else { semaphore.signal() } // <--- MOVED INTO `else` block
}
Your second async would need to be modified similarly.
I notice that you're not calling wait after the second async, the one that sets page.picURL2. So you have 2 calls to wait, but 3 calls to signal. That wouldn't affect whether page.picURL1 is set properly in the first async, but it does mean that semaphore will have unbalanced waits and signals at the end of your code example, and the blocking behavior of wait after the third async may not be as you expect it to be.
If it's an option for your project, refactoring to use async and await keywords would resolve the problem in a way that's easier to maintain, because it would remove the need for the semaphore entirely.
Also, if my premise that saveImagesToFireBaseStorage is called asynchronously is correct, you don't really need the async calls at all, unless there is more code in their closures that isn't shown.
Update
In comments, it was revealed the using the solution above caused the app to "freeze". This suggests that saveImagesToFireBaseStorage calls its completion handler on the same queue that savePage(model:savingHandler) is called on, and it's almost certainly DispatchQueue.main. The problem is that DispatchQueue.main is a serial queue (as is newQueue), which means it won't execute any tasks until the next iteration of its run loop, but it never gets to do that, because it calls semaphore.wait(), which blocks waiting for the completion handler for saveImagesToFireBaseStorage to call semaphore.signal. By waiting it prevents the very thing its waiting for from ever executing.
You say in comments that using async/await solved the problem. That's probably the cleanest way, for lots of reasons, not the least of which is that you get compile time checks for a lot of potential problems.
In the mean time, I came up with this solution using DispatchSemaphore. I'll put it here, in case it helps someone.
First I moved the creation of newQueue outside of savePage. Creating a dispatch queue is kind of heavy-weight operation, so you should create whatever queues you need once, and then reuse them. I assume that it's a global variable or instance property of whatever object owns savePage.
The second thing is that savePage doesn't block anymore, but we still want the sequential behavior, preferably without going to completion handler hell (deeply nested completion handlers).
I refactored the code that calls saveImagesToFireBaseStorage into a local function, and made it behave synchronously by using a DispatchSemaphore to block until it's completion handler is called, but only in that local function. I do create the DispatchSemaphore outside of that function so that I can reuse the same instance for both invocations, but I treat it as though it were a local variable in the nested function.
I also have to use a time-out for the wait, because I don't know if I can assume that the completion handler for saveImagesToFireBaseStorage will always be called. Are there failure conditions in which it wouldn't be? The time-out value is almost certainly wrong, and should be considered a place-holder for the real value. You'd need to determine the maximum latency you want to allow based on your knowledge of your app and its working environment (servers, networks, etc...).
The local function uses a key path to allow setting differently named properties of PageModel (picURL1 vs picURL2), while still consolidating the duplicated code.
Here's the refactored savePage code:
func savePage(model: PageModel, savingHandler: #escaping (Bool) -> Void)
{
// some code .....
var page = model
let saveImageDone = DispatchSemaphore(value: 0)
let waitTimeOut = DispatchTimeInterval.microseconds(500)
func saveModelImageToFireBaseStorage(
from urlPath: WritableKeyPath<PageModel, String?>)
{
if let picURL = model[keyPath: urlPath]
{
saveImagesToFireBaseStorage(pictureURL: picURL)
{
page[keyPath: urlPath] = $0
print("page.\(urlPath) = \(page[keyPath: urlPath]!)")
saveImageDone.signal()
}
if .timedOut == saveImageDone.wait(timeout: .now() + waitTimeOut) {
print("saveImagesToFireBaseStorage timed out!")
}
}
}
newQueue.async
{
saveModelImageToFireBaseStorage(from: \.picURL1)
saveModelImageToFireBaseStorage(from: \.picURL2)
print(page.picURL1!)
do {
try self.pageDocumentRef.setData(from: page)
// Assume handler might do UI stuff, so it needs to execute
// on main
DispatchQueue.main.async { savingHandler(true) }
} catch let error {
print("Error writing city to Firestore: \(error)")
// Should savingHandler(false) be called here?
}
}
}
It's important to note that savePage does not block the thread that's called on, which I believe to be DispatchQueue.main. I assume that any code that is sequentially called after a call to savePage, if any, does not depend on the results of calling savePage. Any that does depend on it should be in its savingHandler.
And speaking of savingHandler, I have to assume that it might update the UI, and since the point where it would be called is in a closure for newQueue.async it has to be explicitly called on DispatchQueue.main, so I do that.

Adding dependency of one BlockOperation on another is not working properly in swift

I have multiple api's in a controller and after successful response I have to reload the UITableView.
For now I started with two api having second api dependency on first one using BlockOperation and DispatchGroup in it.
First in viewDidLoad:
getDataFromAllApis {
self.tableView.reloadData()
}
Then I added the method:
func getDataFromAllApis(completion: #escaping (() -> Void)) {
let queue = OperationQueue()
let getFirstDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getFirstDataFromApi {
group.leave()
}
group.wait()
}
queue.addOperation(getFirstDataOperation)
let getSecondDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getSecondDataFromApi {
group.leave()
}
group.notify(queue: .main) {
completion()
}
}
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation)
}
The problem that I am facing here is getSecondDataOperation executes first and returns to the tableview reload part.
Am I missing something here or there can be a different approach for it? Any help will be appreciated.
I have tried going through this post :
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
You are way overthinking this. Just call the second API from the completion handler of the first API. No operations, no dispatch groups, no nothing.
self.getFirstDataFromApi {
self.getSecondDataFromApi {
// call the completion handler
}
}
As for why your code didn't work, it's because you didn't do what the linked answer said to do!
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
It said to do this:
getSecondDataOperation.addDependency(getFirstDataOperation)
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
That isn't what you did. You did this:
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation) // too late
(However, that post, while ingenious, is not what I would do in this situation. If I wanted to sequentialize download operations, I would use the technique described here: https://fluffy.es/download-files-sequentially/. Or, in iOS 13, I'd use the Combine framework, as I describe here: https://stackoverflow.com/a/59889993/341994.)

How to get cancellation state for multiple DispatchWorkItems

Background
I'm implementing a search. Each search query results in one DispatchWorkItem which is then queued for execution. As the user can trigger a new search faster than the previous one can be completed, I'd like to cancel the previous one as soon as I receive a new one.
This is my current setup:
var currentSearchJob: DispatchWorkItem?
let searchJobQueue = DispatchQueue(label: QUEUE_KEY)
func updateSearchResults(for searchController: UISearchController) {
let queryString = searchController.searchBar.text?.lowercased() ?? ""
// if there is already an (older) search job running, cancel it
currentSearchJob?.cancel()
// create a new search job
currentSearchJob = DispatchWorkItem() {
self.filter(queryString: queryString)
}
// start the new job
searchJobQueue.async(execute: currentSearchJob!)
}
Problem
I understand that dispatchWorkItem.cancel() doesn't kill the running task immediately. Instead, I need to check for dispatchWorkItem.isCancelled manually. But how do I get the right dispatchWorkItemobject in this case?
If I were setting currentSearchJob only once, I could simply access that attribute like done in this case. However, this isn't applicable here, because the attribute will be overriden before the filter() method will be finished. How do I know which instance is actually running the code in which I want to check for dispatchWorkItem.isCancelled?
Ideally, I'd like to provide the newly-created DispatchWorkItem as an additional parameter to the filter() method. But that's not possible, because I'll get a Variable used within its own initial value error.
I'm new to Swift, so I hope I'm just missing something. Any help is appreciated very much!
The trick is how to have a dispatched task check if it has been canceled. I'd actually suggest consider OperationQueue approach, rather than using dispatch queues directly.
There are at least two approaches:
Most elegant, IMHO, is to just subclass Operation, passing whatever you want to it in the init method, and performing the work in the main method:
class SearchOperation: Operation {
private var queryString: String
init(queryString: String) {
self.queryString = queryString
super.init()
}
override func main() {
// do something synchronous, periodically checking `isCancelled`
// e.g., for illustrative purposes
print("starting \(queryString)")
for i in 0 ... 10 {
if isCancelled { print("canceled \(queryString)"); return }
print(" \(queryString): \(i)")
heavyWork()
}
print("finished \(queryString)")
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
}
Because that's in an Operation subclass, isCancelled is implicitly referencing itself rather than some ivar, avoiding any confusion about what it's checking. And your "start a new query" code can just say "cancel anything currently on the the relevant operation queue and add a new operation onto that queue":
private var searchQueue: OperationQueue = {
let queue = OperationQueue()
// queue.maxConcurrentOperationCount = 1 // make it serial if you want
queue.name = Bundle.main.bundleIdentifier! + ".backgroundQueue"
return queue
}()
func performSearch(for queryString: String) {
searchQueue.cancelAllOperations()
let operation = SearchOperation(queryString: queryString)
searchQueue.addOperation(operation)
}
I recommend this approach as you end up with a small cohesive object, the operation, that nicely encapsulates a block of work that you want to do, in the spirit of the Single Responsibility Principle.
While the following is less elegant, technically you can also use BlockOperation, which is block-based, but for which which you can decouple the creation of the operation, and the adding of the closure to the operation. Using this technique, you can actually pass a reference to the operation to its own closure:
private weak var lastOperation: Operation?
func performSearch(for queryString: String) {
lastOperation?.cancel()
let operation = BlockOperation()
operation.addExecutionBlock { [weak operation, weak self] in
print("starting \(identifier)")
for i in 0 ... 10 {
if operation?.isCancelled ?? true { print("canceled \(identifier)"); return }
print(" \(identifier): \(i)")
self?.heavyWork()
}
print("finished \(identifier)")
}
searchQueue.addOperation(operation)
lastOperation = operation
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
I only mention this for the sake of completeness. I think the Operation subclass approach is frequently a better design. I'll use BlockOperation for one-off sort of stuff, but as soon as I want more sophisticated cancelation logic, I think the Operation subclass approach is better.
I should also mention that, in addition to more elegant cancelation capabilities, Operation objects offer all sorts of other sophisticated capabilities (e.g. asynchronously manage queue of tasks that are, themselves, asynchronous; constrain degree of concurrency; etc.). This is all beyond the scope of this question.
you wrote
Ideally, I'd like to provide the newly-created DispatchWorkItem as an
additional parameter
you are wrong, to be able to cancel running task, you need a reference to it, not to the next which is ready to dispatch.
cancel() doesn't cancel running task, it only set internal "isCancel" flag by the thread-safe way, or remove the task from the queue before execution. Once executed, checking isCancel give you a chance to finish the job (early return).
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .background)
let prq = DispatchQueue(label: "print.queue")
var task: DispatchWorkItem?
func work(task: DispatchWorkItem?) {
sleep(1)
var d = Date()
if task?.isCancelled ?? true {
prq.async {
print("cancelled", d)
}
return
}
sleep(3)
d = Date()
prq.async {
print("finished", d)
}
}
for _ in 0..<3 {
task?.cancel()
let item = DispatchWorkItem {
work(task: task)
}
item.notify(queue: prq) {
print("done")
}
queue.asyncAfter(deadline: .now() + 0.5, execute: item)
task = item
sleep(1) // comment this line
}
in this example, only the very last job is really fully executed
cancelled 2018-12-17 23:49:13 +0000
done
cancelled 2018-12-17 23:49:14 +0000
done
finished 2018-12-17 23:49:18 +0000
done
try to comment the last line and it prints
done
done
finished 2018-12-18 00:07:28 +0000
done
the difference is, that first two execution never happened. (were removed from the dispatch queue before execution)

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.

Waiting for Asynchronous function call to complete

I know this question has been asked before, but all solutions do not work for me.
I have a function with sends parameters to an API, and returns the data as a list, I have a UITableView set up to use that list, but it runs before the list is assigned to a variable.
code:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//gradesscrollView.contentSize.height = 3000
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
}
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
}
I would appreciate it if it is not immediately voted as a duplicate, here is what I have tried.
Dispatch groups
Semaphore timing
running variables
including self.tableofItems.delegate = self & self.tableofItems.dataSource = self in the fetchItems{ (str) in part.
EDIT:
Fetch items has been requested,
func fetchItems(completionHandler: #escaping (String?) -> ()) -> () {
let headers = [
"Content-Type": "application/x-www-form-urlencoded"
]
//Switch to keychain
let username = UserDefaults.standard.object(forKey: "username") as! String?
let password = UserDefaults.standard.object(forKey: "password") as! String?
let usernametoSend = username!
let passwordtoSend = password!
print(usernametoSend)
print(passwordtoSend)
let parameters: Parameters = [
"username": usernametoSend,
"password": passwordtoSend
]
Alamofire.request("https://www.mywebsite.com/API/getItems", method: .post, parameters: parameters, headers: headers)
.responseString { response in
completionHandler(String(response.result.value!))
You can't - and shouldn't - wait until an async call to complete. You need to study async programming until you understand it.
An async function accepts a job to do, and returns immediately, before the job is done.
in Swift you usually write an async function to take a completion handler, which is a block of code that you want to be run one the async task is complete.
I have a project called Async_demo (link) on Github that illustrates this. It implements a DownloadManager class that handles async downloads.
The key part is the function downloadFileAtURL(), which should more properly be named downloadDataAtURL, since it returns in-memory data rather than a file.
I created that function to take a completion handler as a parameter:
/**
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!
}
}
It uses an NSURLSession to download a block of data from the specified URL. The data request call I use takes a completion handler that gets executed on a background thread. In the completion handler that I pass to the data task, I invoke the completion handler that's passed in to the downloadFileAtURL() function, but on the main thread.
The code you posted is kind of confusing. It isn't clear which part is the async function, what the flow is, or what data is needed to display your table view.
If you rewrite your function that does async work to take a completion block then you could call tableView.reloadData() in your completion block. (Make sure that call is performed on the main thread.)
EDIT:
As others have said, you need to edit your question to show the code for your fetchItems() function.
I'm guessing that that function is the one that does the Async work, and that the block after it is a completion handler that gets performed asynchronously. If so, you should probably refactor your code like this:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//I moved these lines above the call to fetchItems to make it clear
//that they run before fetchItems' completion closure is executed
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
print("Step 1")
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
print("Step 3")
DispatchQueue.main.async() {
tableview.reloadData() //Do this from the main thread, inside the closure of `fetchItems()`
}
}
print("Step 2")
}
You should load the data of the list in the method of loadView, so that it will be loaded early before UITableView reads.
Sometimes, viewDidLoad performs a little bit slowly. Generally, people will initialize the data of list or sth in the method of loadView to make sure the data is completed before view is created.

Resources