I have two classes.Class A and Class B. In class A there is table view.When i tap on cell in class A i call first api to save the data /saveData & on response of first api i call another api getData.I call these API in background.Now when i move to class A i call the another API on viewDidLoad().I call this in foreground .Now i want that the API of class A should not effect the class B.
Please tell what is the best way to do that.
i tried DispatchGroup but did not work for me.
func saveInBackground(parameter : [String : AnyObject]?) -> Void {
let group = DispatchGroup()
group.notify(queue: DispatchQueue.global(qos: .background)){
let apiManager = APIHandler(baseURL: Constants.API.baseURL, APIVersion: "")
apiManager.requestOfBgMethod(.post, path: Constants.API.Name.addGeneralField.completePath, parameters: parameter, encoding: .url, headers: nil, apiSuccess: { (result) in
//update user
self.copyUser = User(copyFrom: self.user)
self.saveCVResponse(result: result)
//fetch data in background
Utility.sharedInstance.updateCVdata(cvManager: self.cvManager)
}, apiFailure: { (error) in
})
}
// DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
//
//
// }
}
Use DispatchSemaphore and a Singleton instance to make API calling one by one:
class ApiHelper {
static let shardInstance = ApiHelper()
private let semaphore = DispatchSemaphore(value: 1) //one task allowed at a time
private let apiManager = APIHandler(baseURL: Constants.API.baseURL, APIVersion: "")
func saveInBackground(parameter : [String : AnyObject]?) -> Void {
DispatchQueue.global(qos: .default).async { [unowned self] in
self.semaphore.wait() //lock
apiManager.requestOfBgMethod(.post, path: Constants.API.Name.addGeneralField.completePath, parameters: parameter, encoding: .url, headers: nil, apiSuccess: { (result) in
//.......
self.semaphore.signal() //unlock
}, apiFailure: { (error) in
self.semaphore.signal() //unlock
})
}
}
}
Use it in Class A and Class B, they will not be effected each other:
ApiHelper.shardInstance.saveInBackground(parameter: nil)
Use global DispatchSemaphore without Singleton:
In class A or class B or somewhere, define a global semaphore out of class:
let semaphoreGlobal = DispatchSemaphore(value: 1) //one task at a time
//ignore following, just an example.
class AnyClass {
//......
}
And your method become:
func saveInBackground(parameter : [String : AnyObject]?) -> Void {
DispatchQueue.global(qos: .default).async {
semaphoreGlobal.wait() //wait if semaphore is 0. Else, set semaphore to 0 and continue.
//remember: the variable 'apiManager' should not be a local one.
apiManager.requestOfBgMethod(.post, path: Constants.API.Name.addGeneralField.completePath, parameters: parameter, encoding: .url, headers: nil, apiSuccess: { (result) in
//.......
semaphoreGlobal.signal() //set semaphore to 1, means allowing one of other tasks stops waiting.
}, apiFailure: { (error) in
semaphoreGlobal.signal() //set semaphore to 1
})
}
}
Related
I need to return a value from a function that has a closure in it.
I researched about returning value from closures, and found out that I should use 'completion handler' to get the result I want.
I saw posts here and articles explaining it but could not apply because I didn't find anything that matches with my problem.
class ViewController: UIViewController {
let urls = URLs()
override func viewDidLoad() {
super.viewDidLoad()
var leagueId = getLeagueId(country: "brazil", season: "2019")
print(leagueId) //PRINTING 0
}
func getLeagueId (country: String, season: String) -> Int {
let headers = Headers().getHeaders()
var leagueId = 0
let url = urls.getLeagueUrlByCountryAndSeason(country: country, season: season)
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON {
response in
if response.result.isSuccess {
let leagueJSON: JSON = JSON(response.result.value!)
leagueId = (leagueJSON["api"]["leagues"][0]["league_id"].intValue)
}
else {
print("error")
}
}
return leagueId
}
}
The value returned is always 0 because the closure value is not passing to the function itself.
Thanks a lot
So the reason why you're having this issue is because AlamoFire.request is asynchronous. There's a great explanation of asynchronous vs synchronous here But basically when you execute something asynchronously, the compiler does not wait for the task to complete before continuing to the next task, but instead will execute the next task immediately.
So in your case, the AlamoFire.request is executed, and while it's running, the next line after the block is immediately run which is the line to return leagueId which will obviously still be equal to zero since the AlamoFire.request task (function) has not yet finished.
This is why you need to use a closure. A closure will allow you to return the value, after AlamoFire.request (or any other asynchronous task for that matter) has finished running. Manav's answer above shows you the proper way to do this in Swift. I just thought I'd help you understand why this is necessary.
Hope this helps somewhat!
Edit:
Manav's answer above is actually partially correct. Here's how you make it so you can reuse that value the proper way.
var myLeagueId = 0;
getLeagueId(country: "brazil", season: "2019",success: { (leagueId) in
// leagueId is the value returned from the closure
myLeagueId = leagueId
print(myLeagueId)
})
The code below will not work because it's setting myLeagueId to the return value of getLeagueId and getLeagueId doesn't have a return value so it won't even compile.
myLeagueId = getLeagueId(country: "brazil", season: "2019",success: { (leagueId) in
print(leagueId)
})
You need to either return value from the function.
func getLeagueId (country: String, season: String)->Int
Else you need to use completion handlers.
func getLeagueId (country: String, season: String,success:#escaping (leagueId: Int) -> Void) {
let headers = Headers().getHeaders()
var leagueId = 0
let url = urls.getLeagueUrlByCountryAndSeason(country: country, season: season)
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON {
response in
if response.result.isSuccess {
let leagueJSON: JSON = JSON(response.result.value!)
leagueId = (leagueJSON["api"]["leagues"][0]["league_id"].intValue)
success(leagueId)
}
else {
print("error")
}
}
}
And then use it in your code :
getLeagueId(country: "brazil", season: "2019",success: { (leagueId) in
print(leagueId)
self.leagueId = leagueId
})
This is how you should implement completionBLock
func getLeagueId (country: String, season: String, completionBlock:((_ id: String, _ error: Error?) -> Void)?) {
let headers = Headers().getHeaders()
var leagueId = 0
let url = urls.getLeagueUrlByCountryAndSeason(country: country, season: season)
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default, headers: headers).responseJSON {
response in
if response.result.isSuccess {
let leagueJSON: JSON = JSON(response.result.value!)
if let leagueId = (leagueJSON["api"]["leagues"][0]["league_id"].intValue){
completionBlock?(leagueId,nil)
}else {
completionBlock?(nil,nil) // PASS YOUR ERROR
}
}
else {
completionBlock?(nil,nil) // PASS YOUR ERROR
}
}
}
func getLeagueId isn't return anything so you get 0, if you want to get the result from func getLeagueId you should add completion handler function that will update this value.
I have created a function getFriends that reads a User's friendlist from firestore and puts each friend in a LocalUser object (which is my custom user class) in order to display the friendlist in a tableview. I need the DispatchSemaphore.wait() because I need the for loop to iterate only when the completion handler inside the for loop is called.
When loading the view, the app freezes. I know that the problem is that semaphore.wait() is called in the main thread. However, from reading DispatchQueue-tutorials I still don't understand how to fix this in my case.
Also: do you see any easier ways to implement what I want to do?
This is my call to the function in viewDidLoad():
self.getFriends() { (friends) in
self.foundFriends = friends
self.friendsTable.reloadData()
}
And the function getFriends:
let semaphore = DispatchSemaphore(value: 0)
func getFriends(completion: #escaping ([LocalUser]) -> ()) {
var friendsUID = [String : Any]()
database.collection("friends").document(self.uid).getDocument { (docSnapshot, error) in
if error != nil {
print("Error:", error!)
return
}
friendsUID = (docSnapshot?.data())!
var friends = [LocalUser]()
let friendsIdents = Array(friendsUID.keys)
for (idx,userID) in friendsIdents.enumerated() {
self.getUser(withUID: userID, completion: { (usr) in
var tempUser: LocalUser
tempUser = usr
friends.append(tempUser)
self.semaphore.signal()
})
if idx == friendsIdents.endIndex-1 {
print("friends at for loop completion:", friends.count)
completion(friends)
}
self.semaphore.wait()
}
}
}
friendsUID is a dict with each friend's uid as a key and true as the value. Since I only need the keys, I store them in the array friendsIdents. Function getUser searches the passed uid in firestore and creates the corresponding LocalUser (usr). This finally gets appended in friends array.
You should almost never have a semaphore.wait() on the main thread. Unless you expect to wait for < 10ms.
Instead, consider dispatching your friends list processing to a background thread. The background thread can perform the dispatch to your database/api and wait() without blocking the main thread.
Just make sure to use DispatchQueue.main.async {} from that thread if you need to trigger any UI work.
let semaphore = DispatchSemaphore(value: 0)
func getFriends(completion: #escaping ([LocalUser]) -> ()) {
var friendsUID = [String : Any]()
database.collection("friends").document(self.uid).getDocument { (docSnapshot, error) in
if error != nil {
print("Error:", error!)
return
}
DispatchQueue.global(qos: .userInitiated).async {
friendsUID = (docSnapshot?.data())!
var friends = [LocalUser]()
let friendsIdents = Array(friendsUID.keys)
for (idx,userID) in friendsIdents.enumerated() {
self.getUser(withUID: userID, completion: { (usr) in
var tempUser: LocalUser
tempUser = usr
friends.append(tempUser)
self.semaphore.signal()
})
if idx == friendsIdents.endIndex-1 {
print("friends at for loop completion:", friends.count)
completion(friends)
}
self.semaphore.wait()
}
// Insert here a DispatchQueue.main.async {} if you need something to happen
// on the main queue after you are done processing all entries
}
}
I am making two asynchronous network calls and would like to use a Dispatch Group to wait until the call complete and then resume. My program is freezing.
class CommentRatingViewController: UIViewController, UITextViewDelegate {
let myDispatchGroup = DispatchGroup()
#IBAction func saveRatingComment(_ sender: Any) {
rating = ratingView.rating
if rating != 0.0 {
myDispatchGroup.enter()
saveRating(articleID: post.articleID, userID: post.userID) //Network call
self.updatedRating = true
}
if commentsTextView.text != "" {
myDispatchGroup.enter()
saveComment(articleID: post.articleID, userID: post.userID, comment: commentsTextView.text!) //Network call self.addedComment = true
}
myDispatchGroup.wait()
DispatchQueue.main.async {
self.delegate?.didCommentOrRatePost(updatedRating: self.updatedRating, addedComment: self.addedComment)
}
}
And here is one of the network calls:
func saveRating (articleID: String, userID: String) {
let userPostURLRaw = "http://www.smarttapp.com/DesktopModules/DnnSharp/DnnApiEndpoint/Api.ashx?method=UpdatePostRating"
Alamofire.request(
userPostURLRaw,
method: .post,
parameters: ["articleID": articleID,
"newRating": self.rating,
"UserID": userID]
)
.responseString { response in
guard let myString = response.result.value else { return }
DispatchQueue.main.async {
self.myDispatchGroup.leave()
}
}
}
The network calls worked until I introduced Dispatch Group code.
I've resolved this.
The problem was that myDispatchGroup.enter() and self.myDispatchGroup.leave() where being called on different threads. I moved the call to the beginning and very end of the network requests and it works fine now.
I would like somehow to asynchronously validate the pin in ABPadLockScreen since pins are not saved on the device. I'm using Alamofire for http requests along with PromiseKit to have promises.
I have tried to use AwaitKit but the problem is that i get into a deadlock.
I have also tried to use semaphore as well, but the result is the same. Since i can't change the ABPadLock method to accommodate something like a completion handler i need some solution, it doesn't matter if it blocks the main thread, just that it works.
Alamofire request method:
public func loginAsync(pinCode: String?, apiPath: String?) -> Promise<LoginResult>{
return Promise { fullfil, reject in
let params = [
"Pin": pinCode!
]
Alamofire.request(.POST, "\(baseUrl!)/\(apiPath!)", parameters: params).responseObject{(response: Response<LoginResult, NSError>) in
let serverResponse = response.response
if serverResponse!.statusCode != 200 {
reject(NSError(domain: "http", code: serverResponse!.statusCode, userInfo: nil))
}
if let loginResult = response.result.value {
fullfil(loginResult)
}
}
}
}
ABPadLockScreen pin validation method:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
let pinCode = pin!
let defaults = NSUserDefaults.standardUserDefaults()
let serverUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: serverUrl)
service.loginAsync(pinCode, apiPath: "sw/airpharm/login").then { loginResult -> Void in
if loginResult.code == HTTPStatusCode.OK {
AirpharmService.id = loginResult.result!.id
}
}
return false // how do i get the result of above async method here?
}
With semaphore:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
var loginResult: LoginResult?
let defaults = NSUserDefaults.standardUserDefaults()
let baseUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: baseUrl)
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
service.loginAsync(pin, apiPath: "sw/airpharm/login").then { loginResultRaw -> Void in
loginResult = loginResultRaw
dispatch_semaphore_signal(semaphore)//after a suggestion from Josip B.
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return loginResult != nil // rudimentary check for now
}
EDIT:
After a suggestion from Josip B. i added semaphore signal in then, but it still doesn't work
AirpharmService is a class that contains a static property called id, and the Alamofire request method.
ABPadLockScreen pin validation is done on main thread in a ViewController
SOLVED EDIT:
Thanks to everyone for being so patient with me and my, not so good, knowledge of swift and iOS. There are a lot of good answers here and in the end i just went with, in my opinion, simplest solution. I listened to Losiowaty-s suggestion; implemented a spinner and manually dismissed the lock screen when i get the response from the server. I've used a SwiftSpinner. The final solution looked like this:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
let baseUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: baseUrl)
SwiftSpinner.show("Logging in. Please wait...")
service.loginAsync(pin, apiPath: "sw/airpharm/login").then { loginResult -> Void in
if loginResult.code == HTTPStatusCode.OK {
SwiftSpinner.hide()
AirpharmService.id = loginResult.result!.id
self.unlockWasSuccessfulForPadLockScreenViewController(padLockScreenViewController)
} else if loginResult.code == HTTPStatusCode.Unauthorized {
let toast = JLToast.makeText("Invalid pin, please try again", duration: 5)
toast.show()
SwiftSpinner.hide()
} else {
let toast = JLToast.makeText("\(loginResult.code) sent from server. Please try again.", duration: 5)
toast.show()
SwiftSpinner.hide()
}
}.error { error in
let toast = JLToast.makeText("\((error as NSError).code) sent from server. Please try again.", duration: 5)
toast.show()
SwiftSpinner.hide()
}
return false
}
It's great that a lot of people tried to help you make your asynchronous call synchronous. Personally I agree with #OOPer and his comment that you should redesign your code, especially after looking through ABPadLockScreen code. It seems they don't support asynchronous pin verification, which is a shame. Also it seems from their github repo that the original author has abandoned the project, for the time being at least.
I'd attempt to solve your issue like this :
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
let pinCode = pin!
let defaults = NSUserDefaults.standardUserDefaults()
let serverUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: serverUrl)
service.loginAsync(pinCode, apiPath: "sw/airpharm/login").then { loginResult -> Void in
if loginResult.code == HTTPStatusCode.OK {
AirpharmService.id = loginResult.result!.id
self.handleLoginOk()
} else {
self.handleLoginFailed()
}
}
// disable interaction on padlock screen
// indicate to user that an async operation is going on, show a spinner
return false // always return false here
}
func handleLoginOk() {
// dismiss the ABPadlockScreenViewController manually
}
func handleLoginFailed() {
// dismiss the spinner indicating the async operation
// restore user interaction to padlock screen
}
With this approach your users will know that something is going on (the spinner, you can use for example SVProgressHUD as a drop-in solution) and that the app didn't hang. It is quite important, ux-wise, as users with poor connection could get frustrated thinking the app hanged and close it.
There is a potential problem though - if the padlock screen shows some kind of "wrong pin" message when you return false from the delegate method, it could be visible to the user creating some confusion. Now this can be tackled by making/positioning the spinner so that it obscures the message, though this is a very crude and unelegant solution. On the other hand, maybe it can be customised enough so that no message gets shown, and you'd display your own alert after server side verification.
Let me know what you think about this!
... it doesn't matter if it blocks the main thread... but the problem is that i get into a deadlock.
One problem could be it is blocking the main thread with dispatch_semaphore_wait, so the Alamofire response never get a chance to run on the main thread and you're deadlocking.
The solution to this could be create another queue on which the Alamofire completion handler is dispatched.
For example:
If you making a request like this:
Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts").validate().responseData() { response in
print(response.result.value)
}
You can modify this call to dispatch completion handler in your defined queue like this:
let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT)
let request = Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts", parameters: .None).validate()
request.response(queue: queue, responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments)) { response in
print(response.result.value)
}
A simplified version for test.
//MARK: Lock Screen Delegate
func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
print("Validating Pin \(pin)")
let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT)
let semaphore = dispatch_semaphore_create(0)
let request = Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts", parameters: .None).validate()
request.response(queue: queue, responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments)) { response in
print(response.result.value)
//isPinValid = ???
dispatch_semaphore_signal(semaphore);
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return thePin == pin
//return isPinValid
}
Try this:
add dispatch_group:
static let serviceGroup = dispatch_group_create();
Then after calling the function, wait for this group:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
var loginResult: LoginResult?
let defaults = NSUserDefaults.standardUserDefaults()
let baseUrl = defaults.stringForKey(Util.serverUrlKey)
let service = AirpharmService(baseUrl: baseUrl)
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
service.loginAsync(pin, apiPath: "sw/airpharm/login").then { loginResultRaw -> Void in
loginResult = loginResultRaw
}
dispatch_group_wait(yourClass.serviceGroup, DISPATCH_TIME_FOREVER);
return loginResult != nil // rudimentary check for now
}
And release the group after the function returns an answer:
public func loginAsync(pinCode: String?, apiPath: String?) -> Promise<LoginResult>{
return Promise { fullfil, reject in
let params = [
"Pin": pinCode!
]
Alamofire.request(.POST, "\(baseUrl!)/\(apiPath!)", parameters: params).responseObject{(response: Response<LoginResult, NSError>) in
let serverResponse = response.response
if serverResponse!.statusCode != 200 {
reject(NSError(domain: "http", code: serverResponse!.statusCode, userInfo: nil))
}
if let loginResult = response.result.value {
fullfil(loginResult)
}
dispatch_group_leave(yourClass.serviceGroup)
}
}
}
Based on the comments we exchanged, it sounds like the endless wait when you tried using a semaphore is because the semaphore signal is never being sent. Let's try to simplify this down to the minimum code needed to test:
public func padLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!, validatePin pin: String!) -> Bool {
var success = false
let defaults = NSUserDefaults.standardUserDefaults()
let baseUrl = defaults.stringForKey(Util.serverUrlKey)
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
let params = ["Pin": pin]
Alamofire.request(.POST, "\(baseUrl!)/sw/airpharm/login", parameters: params).responseObject {
(response: Response<LoginResult, NSError>) in
if let loginResult = response.result.value where loginResult.code == HTTPStatusCode.OK {
AirpharmService.id = loginResult.result!.id
success = true
}
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return success
}
This should either:
crash because you are force unwrapping several variables (e.g.baseUrl!, loginResult.result!.id, etc. and one of them is nil
return true if you got a valid LoginResult
return false if you didn't get a valid LoginResult
But theoretically, it shouldn't deadlock.
I've tried to make the ABPadLockScreen support asynchronous pin verification.
I've modified ABPadLockScreenViewController. Added a new ABPadLockScreenViewControllerDelegate protocol method shouldValidatePinManuallyForPadLockScreenViewController:.
/**
Call when pin validation is needed manually
Call processUnlock method to validate manually if return true from this method
*/
- (BOOL)shouldValidatePinManuallyForPadLockScreenViewController:(ABPadLockScreenViewController *)padLockScreenViewController;
Added a new instance method processUnlock
- (void)processUnlock {
if ([self isPinValid:self.currentPin]) {
[self unlockScreen];
} else {
[self processFailure];
}
}
Modified the processPin method
- (void)processPin {
if ([self.lockScreenDelegate respondsToSelector:#selector(shouldValidatePinManuallyForPadLockScreenViewController:)]) {
if ([self.lockScreenDelegate shouldValidatePinManuallyForPadLockScreenViewController:self]) {
return;
}
}
[self processUnlock];
}
Now in your viewController implement shouldValidatePinManuallyForPadLockScreenViewController
func shouldValidatePinManuallyForPadLockScreenViewController(padLockScreenViewController: ABPadLockScreenViewController!) -> Bool {
print("Requesting server...")
Alamofire.request(.GET, "https://jsonplaceholder.typicode.com/posts").validate().responseJSON() { response in
//isPinValid = ???
print("Request complete")
padLockScreenViewController.processUnlock()
}
return true
}
Made a demo project at https://github.com/rishi420/ABPadLockScreen
See the swift demo example.
I think semaphore can help. Here is a usage example:
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(#selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(#selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(#selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(#selector(tasks))]) {
tasks = [#[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:#"#unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
This is a function comes from AFNetworking. The method getTasksWithCompletionHandler is a method of NSURLSession which will
Asynchronously calls a completion callback with all data, upload, and download tasks in a session.
Semaphore_wait will ensure that tasks has be assigned with proper value. This way you can get the asynchronously request result.
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
})