This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
let userDefault = UserDefaults.standard
let tokenPinCode = userDefault.string(forKey: "tokenPinCode")
let mailData = self.emailField.text
let dataStruct = mailData!+"|"+tokenPinCode!
print("1")
self.checkToken(code: dataStruct) { (response) in
if(response[0] == "OK"){
print("2")
self.alertPasswordChange(text: "Podaj nowe hasło", code: dataStruct)
}else{
self.standardAlert(title: "Znaleziono błędy", message: "Podany kod jest błedny", ok: "Rozumiem")
self.werifyButton.isEnabled = true
}
}
print("3")
return false
}
Function returns: Print -> 1 -> 3 -> 2
How to get the effect to work out: Print -> 1 -> 2 -> 3
Make your function void and pass completion handler which can handle bool value.
func passcodeViewController(_ controller: Controller, code: String, #escaping handler: (Bool) -> ()) {
// Your logic
asyncRequest(...) {
response in
let result = ... // find whether code ok
handler(result)
}
}
You can call this like:
passcodeViewController(controller, code: "$&36_$") {
(isOk: Bool) in
print(3)
print("code is ok: \(isOk)")
}
You can implement this using semaphore. Semaphore provide the functionality to wait until your function completed. Look at following code.
let serialQueue = DispatchQueue(label: "co.random.queue")
func funcA() {
print("Print A")
let semaphore = DispatchSemaphore(value: 1)
serialQueue.async {
print("Print B")
semaphore.signal()
}
semaphore.wait()
print("Print C")
}
funcA()
The above code is execute with async function, I have implemented in playground and it print following output;
Print A
Print B
Print C
I remove semaphore then print as:
Print A
Print C
Print B
I hope this will work for you.
Make your function void and pass completion handler with signature
Related
I am trying to create a class that executes data loading once and returns the data to all callers of the method while the data was loading to not perform the data loading for the same item (identifier) more than once. The issue I am having is that it seems to crash on the first initialization of CurrentValueSubject for an identifier. This only happens if the downloadStuff returns an Error I have no idea what's wrong. Here is a reproduction of the issue.
Class that does the synchronization:
class FetchSynchronizer<T, ItemIdentifier: Hashable> {
typealias CustomParams = (isFirstLoad: Bool, result: Result<T, Error>)
enum FetchCondition {
// executes data fetching only once
case executeFetchOnlyOnce
// re-executes fetch if request failed
case retryOnlyIfFailure
// always executes fetch even if response is cached
case noDataCache
// custom condition
case custom((CustomParams) -> Bool)
}
struct LoadingState<T> {
let result: Result<T, Error>
let isLoading: Bool
init(result: Result<T, Error>? = nil, isLoading: Bool = false) {
self.result = result ?? .failure(NoResultsError())
self.isLoading = isLoading
}
}
private var cancellables = Set<AnyCancellable>()
private var isLoading: [ItemIdentifier: CurrentValueSubject<LoadingState<T>, Never>] = [:]
func startLoading(identifier: ItemIdentifier,
fetchCondition: FetchCondition = .executeFetchOnlyOnce,
loaderMethod: #escaping () async -> Result<T, Error>) async -> Result<T, Error> {
// initialize loading tracker for identifier on first execution
var isFirstExecution = false
if isLoading[identifier] == nil {
print("----0")
isLoading[identifier] = CurrentValueSubject<LoadingState<T>, Never>(LoadingState<T>())
isFirstExecution = true
}
guard let currentIsLoading = isLoading[identifier] else {
assertionFailure("Should never be nil because it's set above")
return .failure(NoResultsError())
}
if currentIsLoading.value.isLoading {
// loading in progress, wait for finish and call pending callbacks
return await withCheckedContinuation { continuation in
currentIsLoading.filter { !$0.isLoading }.sink { currentIsLoading in
continuation.resume(returning: currentIsLoading.result)
}.store(in: &cancellables)
}
} else {
// no fetching in progress, check if it can be executed
let shouldFetchData: Bool
switch fetchCondition {
case .executeFetchOnlyOnce:
// first execution -> fetch data
shouldFetchData = isFirstExecution
case .retryOnlyIfFailure:
// no cached data -> fetch data
switch currentIsLoading.value.result {
case .success:
shouldFetchData = false
case .failure:
shouldFetchData = true
}
case .noDataCache:
// always fetch
shouldFetchData = true
case .custom(let completion):
shouldFetchData = completion((isFirstLoad: isFirstExecution,
result: currentIsLoading.value.result))
}
if shouldFetchData {
currentIsLoading.send(LoadingState(isLoading: true))
// fetch data
return await withCheckedContinuation { continuation in
Task {
// execute loader method
let result = await loaderMethod()
let state = LoadingState(result: result,
isLoading: false)
currentIsLoading.send(state)
continuation.resume(returning: result)
}
}
} else {
// use existing data
return currentIsLoading.value.result
}
}
}
}
Example usage:
class Executer {
let fetchSynchronizer = FetchSynchronizer<Data?, String>()
func downloadStuff() async -> Result<Data?, Error> {
await fetchSynchronizer.startLoading(identifier: "1") {
return await withCheckedContinuation { continuation in
sleep(UInt32.random(in: 1...3))
print("-------request")
continuation.resume(returning: .failure(NSError() as Error))
}
}
}
init() {
start()
}
func start() {
Task {
await downloadStuff()
print("-----3")
}
DispatchQueue.global(qos: .utility).async {
Task {
await self.downloadStuff()
print("-----2")
}
}
DispatchQueue.global(qos: .background).async {
Task {
await self.downloadStuff()
print("-----1")
}
}
}
}
Start the execution:
Executer()
Crashes at
isLoading[identifier] = CurrentValueSubject<LoadingState<T>, Never>(LoadingState<T>())
Any guidance would be appreciated.
Swift Dictionary is not thread-safe.
You need to make sure it is being accessed from only one thread (i.e queue) or using locks.
EDIT - another solution suggested by #Bogdan the question writer is to make the class an actor class which the concurrency safety is taken care of by the compiler!
By dispatching to a global queue, you increase the chance that two threads will try and write into the dictionary “at the same time” which probably causes the crash
Take a look at these examples.
How to implement a Thread Safe HashTable (PhoneBook) Data Structure in Swift?
https://github.com/iThink32/Thread-Safe-Dictionary/blob/main/ThreadSafeDictionary.swift
I hope this doesn't sound dumb but I'm trying to put:
let lowercasedQuery = query.lowercased()
let usersNew = users.filter({ $0.fullname.lowercased().contains(lowercasedQuery) || $0.username.contains(lowercasedQuery) })
into the DispatchQueue function but obviously, since they are constants declared in the function, the function is out of scope for the return line.
func filteredUsers(_ query: String) -> [User] {
let delay = 3.3
DispatchQueue.main.asyncAfter(deadline: .now() + delay)
{
}
let lowercasedQuery = query.lowercased()
let usersNew = users.filter({ $0.fullname.lowercased().contains(lowercasedQuery) || $0.username.contains(lowercasedQuery) })
return usersNew
}
Does anyone know how to solve this?
Thanks!
You need a closure... more info here. Instead of return, call the completion handler closure.
func filteredUsers(_ query: String, completion: #escaping (([User]) -> Void)) {
let delay = 3.3
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
let lowercasedQuery = query.lowercased()
let usersNew = self.users.filter({ $0.fullname.lowercased().contains(lowercasedQuery) || $0.username.contains(lowercasedQuery) })
completion(usersNew)
}
}
Usage:
viewModel.filteredUsers(searchText) { users in
print(users) /// get your users here!
}
If you are trying to return users inside another function, it won't work. You also need to a add a closure to that function:
/// another closure here
func mainFunction(_ query: String, completion: #escaping (([User]) -> Void)) {
viewModel.filteredUsers(query) { users in
completion(users) /// equivalent to `return users`
}
}
mainFunction("searchText") { users in
print(users) /// get your users here!
}
/// NOT `let users = mainFunction("searchText")`
How could I make my code wait until the task in DispatchQueue finishes? Does it need any CompletionHandler or something?
func myFunction() {
var a: Int?
DispatchQueue.main.async {
var b: Int = 3
a = b
}
// wait until the task finishes, then print
print(a) // - this will contain nil, of course, because it
// will execute before the code above
}
I'm using Xcode 8.2 and writing in Swift 3.
If you need to hide the asynchronous nature of myFunction from the caller, use DispatchGroups to achieve this. Otherwise, use a completion block. Find samples for both below.
DispatchGroup Sample
You can either get notified when the group's enter() and leave() calls are balanced:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
a = 1
group.leave()
}
// does not wait. But the code in notify() is executed
// after enter() and leave() calls are balanced
group.notify(queue: .main) {
print(a)
}
}
or you can wait:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
Note: group.wait() blocks the current queue (probably the main queue in your case), so you have to dispatch.async on another queue (like in the above sample code) to avoid a deadlock.
Completion Block Sample
func myFunction(completion: #escaping (Int)->()) {
var a = 0
DispatchQueue.main.async {
let b: Int = 1
a = b
completion(a) // call completion after you have the result
}
}
// on caller side:
myFunction { result in
print("result: \(result)")
}
In Swift 3, there is no need for completion handler when DispatchQueue finishes one task.
Furthermore you can achieve your goal in different ways
One way is this:
var a: Int?
let queue = DispatchQueue(label: "com.app.queue")
queue.sync {
for i in 0..<10 {
print("Ⓜ️" , i)
a = i
}
}
print("After Queue \(a)")
It will wait until the loop finishes but in this case your main thread will block.
You can also do the same thing like this:
let myGroup = DispatchGroup()
myGroup.enter()
//// Do your task
myGroup.leave() //// When your task completes
myGroup.notify(queue: DispatchQueue.main) {
////// do your remaining work
}
One last thing: If you want to use completionHandler when your task completes using DispatchQueue, you can use DispatchWorkItem.
Here is an example how to use DispatchWorkItem:
let workItem = DispatchWorkItem {
// Do something
}
let queue = DispatchQueue.global()
queue.async {
workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
// Here you can notify you Main thread
}
Swift 5 version of the solution
func myCriticalFunction() {
var value1: String?
var value2: String?
let group = DispatchGroup()
group.enter()
//async operation 1
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value1 = //out of async task
group.leave()
}
group.enter()
//async operation 2
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value2 = //out of async task
group.leave()
}
group.wait()
print("Value1 \(value1) , Value2 \(value2)")
}
Use dispatch group
dispatchGroup.enter()
FirstOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.enter()
SecondOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.wait() // Waits here on this thread until the two operations complete executing.
In Swift 5.5+ you can take advantage of Swift Concurrency which allows to return a value from a closure dispatched to the main thread
func myFunction() async {
var a : Int?
a = await MainActor.run {
let b = 3
return b
}
print(a)
}
Task {
await myFunction()
}
Swift 4
You can use Async Function for these situations. When you use DispatchGroup(),Sometimes deadlock may be occures.
var a: Int?
#objc func myFunction(completion:#escaping (Bool) -> () ) {
DispatchQueue.main.async {
let b: Int = 3
a = b
completion(true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
myFunction { (status) in
if status {
print(self.a!)
}
}
}
Somehow the dispatchGroup enter() and leave() commands above didn't work for my case.
Using sleep(5) in a while loop on the background thread worked for me though. Leaving here in case it helps someone else and it didn't interfere with my other threads.
How to wait to return a value after a closure completion.
Example:
func testmethod() -> String {
var abc = ""
/* some asynchronous service call block that sets abc to some other value */ {
abc = "xyz"
}
return abc
}
Now I want the method to return only after xyz value has been set for variable and not empty string.
How to achieve this?
It is possible (However make sure this is what you really want.).
You have to use something that will block a thread until a resource is available, like semaphores.
var foo: String {
let semaphore = DispatchSemaphore(value: 0)
var string = ""
getSomethingAsynchronously { something in
string = something
semaphore.signal()
}
semaphore.wait()
return string
}
Bare in mind that the thread you are working on will be blocked until the getSomethingAsynchronously is done.
Yes, it is possible to wait for the closure function to populate the data and then return the variable. Although, it is suggested to avoid using semaphore to do that, I think that is the only way.
func foo() -> String {
var str = ""
let semaphore = DispathSemaphore(value: 1) //1 if you are waiting for one closure function to signal the semaphore to continue the main thread execution
getAsync() {
//populate the variable
str = "bar"
semaphore.signal()
}
semaphore.wait()
return str
}
Since Swift 5.5 the recommended way is async/await. If the API doesn't provide an async version you can create your own with a Continuation
func testMethod() async -> String {
return await withCheckedContinuation({ continuation in
someAsynchronousServiceCallBlock() { result in
continuation.resume(returning: result)
}
})
}
You have to call the mathod in an asynchronous context for example in a Task
Task {
let result = await testMethod()
}
this is absolutely not possible, because it is just not how asynchronous tasks are working.
what you could do is something like this:
func testmethod(callback: (abc: String) -> Void) {
asyncTask() {
callback(abc: "xyz")
}
}
Have a nice day.
EDIT (for newer Swift Versions):
func testMethod(callback: #escaping (_ parameter: String) -> Void) {
DispatchQueue.global().async { // representative for any async task
callback("Test")
}
}
Actually I'm facing a similar situation in this link: CLGeocoder in Swift - unable to return string when using reverseGeocodeLocation
Sorry to duplicate it, but how to pass the string out of the completion block?
Thank you very much for your help, Sergey Birukov
Question Updated: To be more specifically, How can I store the "result" into the variable: "data"?
func asyncFunctionToBeCalled(arg1: AnyObject, arg2: AnyObject, aBlock: (result: String) -> Void) -> Void {
//As you can see, this function receives aBlock variable which should point to the block you want to execute once this function is done
var res: String!
aBlock(result)
}
var data = ""
asyncFunctionToBeCalled("a", "b", { (result: String) in
//This block will be called when asyncFunctionToBeCalled is executed
//variable "result" is your requested string here
data = result
})
println(data)
console output nothing
Basically async is you asking someone else to ex. go shop groceries for you so you can continue with your programming and when he gets back from the store you receive the groceries and puts it in the refrigerator.
Practical example:
// Correct way of implementing
asyncShopGrocieries(["Milk", "Cookies", "Potatoes"]) { groceries in
fridge.insert(groceries)
}
// Incorrect way of implementing
var groceries:[String]! // You create a variable
asyncShopGrocieries(["Milk", "Cookies", "Potatoes"]) // You make an async request
{ groceries in // Block that handles the data when done
groceries = groceries_
} // Block will execute after you reach the end of this function
fridge.insert(groceries) // When you come here you will try to put the groceries inside the fridge while he is still on his way TO the store.
UPDATE
class SomeClass {
var groceries:[String]?
var groceries2:[String]?
var fridge = Fridge()
func gotAllGroceries(groc: [String], groc2: [String]) {
fridge.insert(self.groc, groc2)
}
func getGroceries() {
asyncShopGrocieries(["Milk", "Cookies", "Potatoes"]) { groceries in
self.groceries = groceries
if groceries2 != nil {
self.gotAllGroceries(self.groceries!, groc2: self.groceries2!)
}
}
asyncShopGrocieries(["Candy", "Steak", "Apples"]) { groceries in
self.groceries2 = groceries
if groceries != nil {
self.gotAllGroceries(self.groceries!, groc2: self.groceries2!)
}
}
}
}
UPDATE 2
func async(time: UInt32, action: ()->()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
sleep(time)
action()
}
}
func testDispatchGroup() {
let group = dispatch_group_create()
let startDate = NSDate()
dispatch_group_enter(group)
async(2) {
println("1 done")
dispatch_group_leave(group)
}
dispatch_group_enter(group)
async(1) {
println("2 done")
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
println("Everything done!")
println("Total time: \(NSDate().timeIntervalSinceDate(startDate))")
}
}
Prints out:
2 done
1 done
Everything done!
Total time: 2.00842797756195
You are calling asynchronous function which receives block as an argument. You should call this function with your own block which receives a string as an argument. So when the asynchronous function is done it calls your block with resulted string.
Example:
func asyncFunctionToBeCalled(arg1: AnyObject, arg2: AnyObject, aBlock: (result: String) -> Void) -> Void {
//As you can see, this function receives aBlock variable which should point to the block you want to execute once this function is done
var res: String!
//Some asynchronous code here which will call aBlock(result)
}
//Your calling code:
asyncFunctionToBeCalled("a", "b", { (result: String) in
//This block will be called when asyncFunctionToBeCalled is executed
//variable "result" is your requested string here
})