How to return values from Haneke's async fetch method - swift

I'm trying to parse some data that I cached using Haneke Swift. I've cached the data and have written the parser to accomplish this. This parser is in a separate class called AssembleCalendar().
Using Haneke's example code for fetching, I've tried with complete and utter failure to actually return a value from the closure.
My attempt
func getScheduledItems() -> [ScheduledItem] {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
}
print(scheduledItem) // array returns nil
return scheduledItem // returns nil
}
What I know
I understand that this is an asynchronous issue. My code isn't waiting for my AssembleCalendar() parser to finish. It's just running each line and returns nil long before my scheduledItem receives a value. I've tried many, many solutions and read quite a few examples online but I cannot figure out how to retrieve a value from this closure in this scenario.
My question
How can I get .fetch() to return a value before my function hits nil?
update:
Here's my code in context:
class Schedule {
var items : [ScheduledItem]
init() {
items = getScheduledItems() // Schedule.getScheduledItems()
}
func getScheduledItems(completion: (items: [ScheduledItem]) -> ()) {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
completion(items: scheduledItem)
}
}
}

Fetch() is using a completion handler, this means that the block of code called in there is actually executing AFTER your return statement has been executed. This is why it is returning nil. To get around this, you can use your own completion handler to return the information:
class Schedule {
var items = [ScheduledItem]()
init(items: [ScheduledItem]) {
self.items = items
}
class func getScheduledItems(completion: (items: [ScheduledItem]) -> ()) {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
completion(items: scheduledItem)
}
}
}
Then initialise the class using this:
Schedule.getScheduledItems { (items) -> () in
var schedule = Schedule(items: items)
}

Related

Can't assign a value to variable inside of closure Swift [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 2 years ago.
I'm trying to get current document count belongs to spesific user from Firebase.
Whenever i try to assign count value to my variable in closure, value is always shown as nill.
So that i did couple of research, as a result i figured out networking sometimes takes so long and it happens asynchronously. So if i'm not wrong due to asynchronous returning a value inside a fucntion might happen before assign value .
I tried to add dispatchqueue.main.async but it didn't work for me...
Here is my code
func getEventCount () -> Int? {
var count: Int?
db.collection("Events").whereField("owner", isEqualTo: currentUser.email).getDocuments { (snapshot, error) in
if error != nil {
print(error)
}else {
DispatchQueue.main.async {
if let snapshot = snapshot {
count = snapshot.count
}
}
}
}
return count
}
My main goal is getting count data from database and assign it to 'count' variable.
So why do i need that count variable? Because i'm going to pass that count value to tableView datasource method numberOfRowsInSection which is expecting a int value. With that value i'm going to represent some data in Events document from firestore in table views.
note: When i try to print count value in closure it shows desired value, but when function return value it's shown nill...
Once it is a Async call - you cannot synchronously return the value from the function. You should accept a callback to the function that will accept the count. That callback function or closure will be passed the value asynchronously.
func getEventCount (callback: #escaping(Result<Int, Error>) -> Void) {
db.collection("Events").whereField("owner", isEqualTo: currentUser.email).getDocuments { (snapshot, error) in
if error != nil {
let result = Result.failure(error)
callback(result)
}else if let snapshot = snapshot {
let result = Result.success(snapshot.count)
callback(result)
} else {
let result = Result.failure(SomeCustomAppError)
callback(result)
}
}
}
Then you can call this function passing in a callback
self.getCount() { result in
switch result {
case .success(let count): /// use count
print(count)
// only here u can assign the count value to ur variable
case .error(let error): /// handle error
print(error.localizedDescription)
}
}
Note: In the above I've used the Result datatype from Swift standard library - https://developer.apple.com/documentation/swift/result so that both error
or result can be passed back

getting a fully populated array outside the trailing closure after asynchronous function finishes executing in Swift

I am trying to populate finalArray with the result of asynchronous call from my extractProperties() function.
class ViewController: UIViewController {
var finalArray: [SingleRepository] = []
let extractor = Extractor()
override func viewDidLoad() {
super.viewDidLoad()
print("Starting the program... ")
extractor.extractProperties { object, error in
guard let object = object else {
print("Extractor did not reutrn data")
return
}
self.finalArray.append(object)
print("Appended successfully --- \(self.finalArray.count) --- inside the trailing closure")
}
print("Size of the array --- \(self.finalArray) --- outside the trailing closure")
}
The issue is that I can't get the fully populated finalArray to work with outside the scope of trailing closure!
Output log:
Starting the program...
Size of the array --- [] --- outside the trailing closure
Appended successfully --- 1 --- inside the trailing closure
Appended successfully --- 2 --- inside the trailing closure
Appended successfully --- 3 --- inside the trailing closure
.
.
.
Appended successfully --- 300 --- inside the trailing closure
I know why the print statement from outside get executed first, but I never can get my fully populated array with all 300 objects in it.
Please note that the following post did NOT solve my problem:Run code only after asynchronous function finishes executing
I even tried solution in that post by writing a the following function:
func constructingFinalArray(completionBlock: #escaping ([SingleRepository]) -> Void) {
var fArrray: [SingleRepository] = []
extractor.extractProperties { data, error in
guard let data = data else {
print("Extractor did not reutrn data")
return
}
fArrray.append(data)
completionBlock(fArrray)
}
}
and called it inside viewDidLoad() as following, but confusingly I got the same result and array gets populated element by element, therefore never be able to access a fully populated array from out of trailing closures!
constructingFinalArray { array in
print("array size from constructingFinalArray function: \(array.count) ")
}
output:
Starting the program...
array size from constructingFinalArray function: 1
array size from constructingFinalArray function: 2
array size from constructingFinalArray function: 3
.
.
.
extractProperties get called 300 times exactly and sometimes it returns no date(Error).
// Class to extract the required properties and
// provide ready to use object for ViewController
class Extractor {
private let client = RepoViewerAPIClient()
private var allURLs: [RepositoryURL] = []
var allRepositories: [SingleRepository] = []
// Method to extract all the required properties
// compromising of 2 asynchrounous call, (nested asynch call)
// one to get the urls and another call within the first call to extract all the propreties
func extractProperties(completionHandler: #escaping (SingleRepository?, RepoViewerErrors?) -> Void) {
// implementation of nested asynchronous calls are deleted to shorten the question length
}
}
It seems that after you call once
extractor.extractProperties {
...
}
The closure gets called exactly 300 times but sometimes it can return no data.
In this case you can follow this approach.
extractor.extractProperties { object, error in
serialQueue.async { [weak self] in
count += 1
guard count < 300 else {
self?.didCompletePopulation()
return
}
guard let object = object else {
print("Extractor did not reutrn data")
return
}
self?.finalArray.append(object)
}
}
func didCompletePopulation() {
// be aware, this is not called on the main thread
// if you need to update the UI from here then use the main thread
print("Final array is populated \(self.finalArray)")
}
How does it work?
The body of the closure is wrapped is wrapped into another closure executed through a Serial Queue. This way we are sure the share resources (finalArray and count) are accessed safely.
serialQueue.async { [weak self] in
...
}
Next every execution of the closure increments count by 1.
Then we make sure count is minor than 300, otherwise we stop the execution of the closure an call didCompletePopulation().
guard count < 300 else {
self?.didCompletePopulation()
return
}
We check if the result contains a proper value otherwise we stop the execution of the current closure
guard let object = object else {
print("Extractor did not reutrn data")
return
}
And finally we add the new element to the array
self?.finalArray.append(object)

object content different from within method

I am encountering a strange behavior with an App I am building.
I have a struct Record and I create an instance of it from a NavigationViewController's child ViewController.
We can see it like this:
NavigationController (called TaskTabsController)
|
ViewController (called TaskFromController)
If I put a breakpoint, I can inspect and check the content of the object is what it should (conform to interface input data).
But when I call a method on that instance (the very next line), the different members have different or missing values.
The object instance is created from within TaskTabsController with something like so:
if let formVC = (viewControllers?[1] as? TaskFormViewController) {
let rec: TaskRecord? = formVC.makeTaskRecord()
// here the rec data are correct when I inspect the instance
rec?.prepareData()
// from the prepareData function, the properties are different of can't be accessed...
}
Ex: From controller, I can see my instance rec having a member task instance (of Task type) with a name property.
But from within the prepareData method, that task member can't display the name attached to it. (debugger says value unreadable or something like that)
Also, I can see a list of other objects, but in the method, their count is different...
Here is how makeTaskRecord method works: (from my TaskFormViewController)
In TaskFormFiewController I have a private property like so:
private var stepsSelected: [StepRecord] = []
That property is updated whit user actions.
The StepRecord is a struct. (so should be passed by value I think)
Next is the makeTaskRecord method in the same controller (gathering details from form elements)
func makeTaskRecord() -> TaskRecord? {
guard let current_task = task
else {
print("TaskForm.makeTaskRecord(): Can't create a record without a Task")
return nil
}
// check data validity
var errors:[String] = []
// generate StepRecords >>>
if stepsSelected.count == 0 {
errors.append("Error, no step selected")
}
// <<<
// DATA OK
if errors.isEmpty {
let taskrecord = TaskRecord(
id: record?.id,
taskId: task!.id,
userCode: Config.getUser()!.code, // global object containing login state
pictures: [],
memo: memo.text,
task: current_task,
stepRecords: stepsSelected // data here is correctly set
)
return taskrecord // but here, I can't retreive the stepRecords! (1)
}
else {
// display errors in an alert box
let errorVC = MyHelpers.makeAlert("Error with data", errors.joined(separator: "\n"))
self.present(errorVC, animated: true)
return nil
}
}
Here is how the prepareData() method looks like: (method of Record class)
func prepareData() -> [String: Any]? {
// no steps in record, data is invalid
guard
let stepRecordsJson = try? JSONEncoder().encode(self.stepRecords)
// This is where I constated that stepRecords is different (set but empty)
else {
return nil
}
// other data
var params = [
"task_id": taskId,
"user_code": userCode,
"step_records": stepRecordsJson, // this is what doesn't work well, but it's certainly related to (1) above
"memo": memo ?? ""
] as [String : Any]
// when editing, we set again the record ID
if let edit_id = self.id {
params["id"] = edit_id
}
return params
}
Note: All the data structures and Codable struct
In the sample above, at the (1), I put a breakpoint on the return line.
When checking data, I see that the "sub-struct" do not have good values:
(lldb) print current_task.name
(String) $R0 = "屋根工事"
(lldb) print taskrecord.task.name
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x6f).
The process has been returned to the state before expression evaluation.
The task is a struct so there should not be problems about it, but the debugger can't read the task I assigned to my taskrecord.
Is there something I am missing? Like a pointer reference of some sort?

Function returns null value

This might be a basic question but I am unable to figure this out. My function always returns a null value and I am not sure why.
My main code ( in a different view controller) calls this function with a "string" to search. But at the end of the function, I can never return the results. If i print the search results where I have the final print statement, then I do see the results. But right after that function is complete, the return value is nil. How would I get the return value from this function ? Thanks in advance,. I am using Swift btw.
func fetchSearchResults (_ searchString: String) -> Array<Any> {
self.globalPost = [Posts]()
self.postKeysArray = []
var searchIndex:AlgoliaSearch.Index
let query = Query()
searchIndex = self.client.index(withName: "Comments")
// you can ignore all of this below until the last couple lines...
query.attributesToRetrieve = ["userComment","postId"]
query.attributesToHighlight = ["postId"]
query.query = searchString
query.optionalWords = [searchString]
let curSearchId = searchId
searchIndex.search(query, completionHandler: { (content, error) in
if (curSearchId <= self.displayedSearchId) || (error != nil) {
return // Newest query already displayed or error
}
self.displayedSearchId = curSearchId
self.loadedPage = 0 // Reset loaded page
guard let hits = content!["hits"] as? [[String: AnyObject]] else { return }
var tmp = [String]()
for hit in hits {
// print ("Comment is \(commentRecord(json:hit).postId)")
tmp.append(commentRecord(json:hit).postId!)
}
self.postKeysArray = tmp
self.getDatafromFirebase(searchString)
// so my search code returns some values here and i see them in the post keysArray.
print ("Search Results -post keys is \(self.postKeysArray)") \\ returns correct values here in the Array
})
self.searchId += 1
// But here i get a NULL value.
return self.postKeysArray \\ returns empty array
}
As Alexander noted, you are printing the search results inside the completion handler, which has not executed yet when fetchSearchResults returns. There are a couple different ways that you could solve this. One would be to refactor fetchSearchResults to take a closure:
func fetchSearchResults (_ searchString: String, completionHandler completion: (Array<Any>) -> ()) {
// ...
searchIndex.search(query, completionHandler: { (content, error) in
// ...
self.postKeysArray = tmp
self.getDatafromFirebase(searchString)
// so my search code returns some values here and i see them in the post keysArray.
print ("Search Results -post keys is \(self.postKeysArray)") \\ returns correct values here in the Array
completion(self.postKeysArray)
})
}
Then, in your 'main code', wherever you were calling:
let results = searchController.fetchSearchResults(searchString)
doSomething(with: results)
You would now have:
searchController.fetchSearchResults(searchString, { results in
doSomething(results)
})
This line could also be written:
searchController.fetchSearchResults(searchString, doSomething)
If you really can't have fetchSearchResults be asynchronous, then you could use DispatchSemaphore to wait until the completion handler has finished:
func fetchSearchResults (_ searchString: String) -> Array<Any> {
// ...
var semaphore = DispatchSemaphore(0)
searchIndex.search(query, completionHandler: { (content, error) in
// ...
self.postKeysArray = tmp
self.getDatafromFirebase(searchString)
// so my search code returns some values here and i see them in the post keysArray.
print ("Search Results -post keys is \(self.postKeysArray)") \\ returns correct values here in the Array
semaphore.signal()
})
semaphore.wait()
return self.postKeysArray
}
Note that unless you know what you're doing, using semaphores is not a good idea. If you don't know what threads various callbacks might be called on, it can lead to a deadlock. The above example will not work if the searchIndex.search completion handler is called on the same DispatchQueue as fetchSearchResults. If you don't understand what I'm talking about, you probably shouldn't do it. It will almost certainly be better to use asynchronous callbacks.

Swift Array is Empty After Parse Queries - Completion Handler?

I don't understand why the arrays become empty after the query with block. I did some research and it's most likely because I need a completion handler, but I can't figure out how to implement it in this case. Can I just add an activity indicator until the method is done?
var usernamesFollowing = [""]
var useridsFollowing = [""]
func refresh(completion: (Bool)){
//find all users following the current user
var query = PFQuery(className: "Followers")
query.whereKey("following", equalTo: PFUser.currentUser()!.objectId!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil {
//remove all from arrays
self.usernamesFollowing.removeAll(keepCapacity: true)
self.useridsFollowing.removeAll(keepCapacity: true)
//get all userIds of following current user and add to useridsFollowing array
if let objects = objects {
for userId in objects {
var followerId = userId["follower"] as! String
self.useridsFollowing.append(followerId)
//get usernames from followerId and add to usernamesFollowing array
var query = PFUser.query()
query!.whereKey("objectId", equalTo: followerId)
query!.findObjectsInBackgroundWithBlock({ (objects2, error) -> Void in
if let objects2 = objects2 {
for username in objects2 {
var followerUsername = username["username"] as! String
self.usernamesFollowing.append(followerUsername)
}
}
//WORKS. usernamesFollowing array is now full.
println(self.usernamesFollowing)
})
//BROKEN. usernamesFollowing array is now empty outside of block.
println(self.usernamesFollowing)
}
}
}
//WORKS. useridsFollowing is now full.
println(self.useridsFollowing)
})
//BROKEN. usernamesFollowing is now empty outside of block.
println(self.usernamesFollowing)
}
To elaborate on Larme's point - asynchronous methods return immediately, and dispatch the work into another queue. To put this in context, consider your two println statements:
println(self.usernamesFollowing) //1. inside async fetch closure
println(self.usernamesFollowing) //2. outside async fetch closure
The asynchronous method will take your closure and dispatch it on to a different queue. After doing so, it returns immediately, and continues to execute your code, which goes to your 2nd println statement right away. In this situation, your second println statement will actually print before your first.
If possible, do all your data manipulations within the block. It'll save you a lot of work. If you must offload the objects outside of the block, consider using NSOperations, which is perfectly equipped to deal with that type of scenario.