I have a function in swift to return a string from array, if the array has value return directly, if not I need load it from server and return the value from callback, I don't know how to do it, something like
func getValue(idx: Int)->string {
if arrayValue.count <= 0 {
callbackfunc() { arrayValue in
return arrayValue[idx]
}
} else {
return arrayValue[idx]
}
}
but it is impossible to return from callback, any help? thank you very much
Add a completion handler:
func getValue(idx: Int, completion: #escaping (String) -> Void) {
if arrayValue.count <= 0 {
callbackfunc() { arrayValue in
completion(arrayValue[idx])
}
} else {
completion(arrayValue[idx])
}
}
And call it:
getValue(idx: 2) { result in
print(result)
}
As you have found out you cannot directly return a value from a closure. Therefore you need to handle the returned data within the callback. This can be as complex as needs be, but for a simple example you could just pass the data back to an underlying method.
Depending on what else the app might be doing during the time the callback is running, you may need to be careful how you update this data to make it thread-safe. As a simple precaution it's probably worth performing the update on the main thread:
func getValue(idx: Int)->string {
if arrayValue.count <= 0 {
callbackfunc() { [weak self] arrayValue in
DispatchQueue.main.async {
self.processReturnedData(arrayValue[idx])
}
}
} else {
return arrayValue[idx]
}
}
func processReturnedData(_ idx: IDXType) {
//do something with the data
}
Related
I am having some issues and am just really lost so this is a last resort.
I have this function:
func fetchGoogleData(forLocation: CLLocation) {
//guard let location = currentLocation else { return }
googleClient.getGooglePlacesData(location: forLocation) { (response) in
self.listResults(places: response.results)
}
}
Which calls this functions:
func listResults(places: [Place]) {
dump(places)
}
However, dumping responses.result or places return a null value but looping through places returns all the correct values.
Is there a way to return response.results as the value without calling this function?
You can use completion block (Closures) to return your results. Let say your response.results is of type [Place]. Then your function should be like below
func fetchGoogleData(forLocation: CLLocation, completion: #escaping [Place]->Void) {
//guard let location = currentLocation else { return }
googleClient.getGooglePlacesData(location: forLocation) { (response) in
completion(response.results)
}
}
and call it as below:
self.fetchGoogleData(forLocation: currentPosition) { (places) in
//print(places)
}
I am using a library that allows me to poll for events (blocking), until there are no more events (in which case it returns nil).
I am tempted to implement the observable like so:
private func createObservable() -> Observable<MyEvents> {
return Observable.create { observer in
let myPollingObject = PollingObject()
while let event = try myPollingObject.poll() {
observer.onNext(event)
}
return Disposables.create()
}
}
Where the while loop finishes when there are no more events (and poll() returns nil).
However, the while loop means that I never return Disposables.create(), which is an issue.
Is there a more reactive way to implement that? I don't really feel like putting the while loop in a thread...
You have to wrap your loop in a dispatch queue. You should also handle errors properly and notify the subscriber when it's completed. Also some way to cancel would be nice...
func createObservable() -> Observable<MyEvents> {
return Observable.create { observer in
let myPollingObject = PollingObject()
var canceled = false
DispatchQueue.init(label: "poller").async {
do {
while let event = try myPollingObject.poll(), !canceled {
observer.onNext(event)
}
if !canceled {
observer.onCompleted()
}
}
catch {
observer.onError(error)
}
}
return Disposables.create { canceled = true }
}
}
I have been designing an app that analyzes lines of text, and I want to use SVProgressHUD to show progress of it.
This is my code:
let total = text.count
for line in text{
count = count + 1
DispatchQueue.global(pos: .background).async{
//Analyzing line
DispatchQueue.main.async{
SVProgressHUD.showProgress(count/total)
}
}
}
The analyzing works, and the HUD shows properly, when the count reaches total, the process gets stuck, and SVProgressHUD stops at max status, and the program stops. What is the issue with the program?
Am I using Dispatchqueue wrong?
Do I have to call something other to free the background process or something?
I have tried exchanging //Analyzing line and SVProgressHUD.show..., but it still doesn't work. I initially used SVProgress within the loop without the dispatchqueue, but then the progress hud moves only after the analyzation(full loop) has been completed, which is a problem.
Any help would be appreciated.
Thank you.
Try using this code. It does not use loop but implements recursive calls to a function for processing your string data.
func processLines() {
SVProgressHUD.showProgress(0)
// sample data
var text = Array("abcdefghijklmnop")
var lines : [Line] = []
let count : [Int] = Array(0..<text.count)
count.forEach({ pos in lines.append(Line(char: text[pos])) })
var currentIndex : Int = 0
func processLine(at index: Int) {
DispatchQueue.global(qos: .background).async{
//Analyzing line
let line = lines[index]
print("Processing Line CHAR: \(line.char)")
DispatchQueue.main.async{
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
guard currentIndex < lines.count-1 else {
SVProgressHUD.showProgress(1)
return
}
currentIndex += 1
startLineProces(at: currentIndex)
}
}
}
}
func startLineProces(at index: Int) {
processLine(at: index)
SVProgressHUD.showProgress(Float(index) / Float(lines.count))
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
startLineProces(at: currentIndex)
}
}
struct Line {
var char: Character
init(char: Character) {
self.char = char
}
}
Use the below string extension to analyse your string. It has a completion block which will return progress as well as status of completion.
extension String {
func analyseString(completion: #escaping (Bool, Float) -> ()) {
let totalCountOfString = self.count
for (index, _) in self.enumerated() {
if index == totalCountOfString - 1 {
completion(true, Float(index)/Float(totalCountOfString))
} else {
completion(false, Float(index)/Float(totalCountOfString))
}
}
}
}
You can call the above method to show your progress as below (maybe on a button click). self.yourString is your input string that you need to analyse.
#IBAction func clicked(_ sender: UIButton) {
DispatchQueue.main.async {
self.yourString.analyseString { (isCompleted, progress) in
if isCompleted {
SVProgressHUD.dismiss()
print("Ending")
} else {
SVProgressHUD.showProgress(progress, status: "Analysing (\(progress)%)")
}
}
}
}
I have a function that does processing asynchronously:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
I thought it would be wise to use defer to guarantee that completion gets called every time, so I tried this:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
}
Working well. Then I tried to use a guard statement within the asynchronous dispatch that always failed, to see if defer will activate. It didn't:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
guard let shouldFail = ... else { return }
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
}
defer would not be called. Why?
Because you are using defer after returning. The compiler doesn't know that you specified defer instructions (because it returned already and didn't see any defer instructions in that point, so the next lines are not fired up). If you'd move defer {} before the guard, then it will be called.
guard will return before even getting to the defer. Try doing it the other way around:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
guard let shouldFail = ... else { return }
}
}
I'm having an issue retrieving data from within an closure. I'm calling function called getWallImages which is supposed to return an array. I can print the contents of the array from within the closure, but outside of it the array is empty.
import Foundation
import Parse
class WallPostQuery {
var result = [WallPost]()
func getWallImages() -> [WallPost] {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
}
}
}
//this line prints [] ...empty array?
println(result)
return self.result
}
}
Question
How do I get values out of a closure?
That is because println(result) is executed BEFORE self.results = objects. The closure is executed asynchronously so it executes afterwards. Try making a function that uses results which can be called form the closure:
var result = [WallPost]()
func getWallImages() {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
self.useResults(self.result)
}
}
}
}
func useResults(wallPosts: [WallPost]) {
println(wallPosts)
}
}
Another solution to your problem, so that you can return it from that function is to create your own closure:
var result = [WallPost]()
func getWallImages(completion: (wallPosts: [WallPost]?) -> ()) {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
completion(wallPosts: self.result)
} else {
completion(wallPosts: nil)
}
} else {
completion(wallPosts: nil)
}
}
}
func useResults(wallPosts: [WallPost]) {
println(wallPosts)
}
}
What is happening is that the method is returning before the closure executes.
Fundamentally, you're running into a problem with they way you are managing asynchronous callbacks.
Asynchronous vs synchronous execution, what does it really mean?
You need to create a way of notifying your caller from within your closure. You can achieve this by: requiring your own closure as an input parameters; using a delegate pattern; using a notification.
https://codereview.stackexchange.com/questions/87016/swift-ios-call-back-functions
Each has their benefits/drawbacks, and it depends on your particular situation. The simplest way to get started with async data fetches, is to pass in your own closure. From there, you can jump to another pattern such as the delegate pattern if the need arises.
I think the latter of println(result) is called before because findObjectsInBackgroundWithBlock is executed on background as its name suggests.
So, you can confirm result in the following way,
import Foundation
import Parse
class WallPostQuery {
var result = [WallPost]() {
didSet {
println(result)
}
}
func getWallImages() {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
}
}
}
}
}