Capturing a value from closure in Swift with Parse - ios

I am creating an app for test taking in Swift, and I'm using Parse to handle the backend. There are two main "object" types that I am working with in Parse: Test and Question. Each Test object contains an array of Question objects called "questions". I need to capture the questions array of the test object with the getObjectInBackgroundWithId method, as I have the test's objectId value, and save it to an array I have declared earlier in the method. When I assign the array to my questions array from the beginning of the method inside of the closure, I print it, and it appears to have been correctly copied, but when I print it outside of the closure, it hasn't been copied. Here is the method:
#IBAction func endTestPressed(sender: UIButton)
{
let lab = self.pinLabel.text!
var questions = [PFObject]()
let query = PFQuery(className:"Test")
query.getObjectInBackgroundWithId(lab.substringFromIndex(advance(lab.startIndex,5)))
{
(test: PFObject?, error: NSError?) -> Void in
if error == nil && test != nil
{
questions = test?["questions"] as! [PFObject]
print("Inside of closure: \(questions)")
}
else
{
print(error)
}
}
print("Outside of closure: \(questions)")
}
How can I save the array from Parse as an array declared in the method before the closure?

It is not that the array is empty in the outside the closure, what happens is that getObjectInBackgroundWithId happens in the background, the rest of your app still running, so you first print the outside println command, than when the results come back from the background thread it just run the completion block
#IBAction func endTestPressed(sender: UIButton)
{
let lab = self.pinLabel.text!
var questions = [PFObject]()
let query = PFQuery(className:"Test")
query.getObjectInBackgroundWithId(lab.substringFromIndex(advance(lab.startIndex,5)))
{
//Run when the getObjectInBackgroundWithId return with the results
(test: PFObject?, error: NSError?) -> Void in
if error == nil && test != nil
{
questions = test?["questions"] as! [PFObject]
print("Inside of closure: \(questions)") //this happen after the print outside the closure as the request happens in the backgroun
}
else
{
print(error)
}
}
//Application continue run while the getObjectInBackgroundWithId retrives data from Parse.com
print("Outside of closure: \(questions)") //This will happen first and the array is not populate yet
}

Related

Swift - Problem While Downloading Data From Firebase Firestore Asynchronously with DispatchGroup()

While getting every document's ID in the "events" collection where their EventStatus value is equal to 0 and storing them in a string array (documentIds), I tried to run code asynchronously with DispatchGroup() so when I returned "documentIds", I would return a non-empty and complete value.
But when I run the code as it is below, it froze and in fact it never ran in the getDocuments{} closure.
I tried to run getDocuments{} closure in DispatchQueue.global().async{} but it didn't work also.
func someFunction() -> [String] {
var documentIds : [String]!
var dispatchGroup = DispatchGroup()
dispatchGroup.enter()
Firestore.firestore().collection("events").whereField("EventStatus", isEqualTo: 0).getDocuments { (snap, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let snap = snap else { return }
documentIds = snap.documents.map({ (document) -> String in
return document.documentID
})
dispatchGroup.leave()
}
dispatchGroup.wait()
return documentIds
}
When it froze, firebase gave this error in the debug console:
"Could not reach Cloud Firestore backend. Backend didn't respond within 10 seconds.
This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend."
Other than that, no error or some other feedback. Am I doing something wrong with DispatchGroup() or Firestore?
Thanks for your help in advance!
This is one of the cases where dispatchGroup is useless and causes many errors.
Since retrieving data from Firestore is async call, use completion handler for your method instead of returning value and get rid of dispatchGroup
func someFunction(completion: #escaping ([String]) -> Void) {
Firestore.firestore().collection("events").whereField("EventStatus", isEqualTo: 0).getDocuments { snap, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let snap = snap else { return }
var documentIds = snap.documents { document in
return document.documentID
}
completion(documentIds)
}
}
then call your method with completion handler where you have access to received array of String
someFunction { documentIds in // name completion parameter of type `[String]`
... // assign some global array as `documentIds` and then reload data, etc.
}

Swift 4 accessing completionhandler returned value from class variable

Problem: I’m trying to access the value which is returned from completionHandler, to assign it to a variable which is outside the scope of completionHandler returned method. I can access the variable in the scope of the method, but I can’t from outside. I’ve tried to assign it to the class variable when I access, but didn’t work. Any ideas?
var marketler = [MarketModel]()
var marketAdiArray = [String]()
override func viewDidLoad() {
getMarkets { (marketdizi) in
self.objAryToTableView(markets: marketdizi)
print(self.marketAdiArray) // -> this returns the right array
}
print(self.marketAdiArray) // -> this returns an empty array
}
func getMarkets(completionHandler : #escaping ([MarketModel])->()) {
let uid = "userID(02)"
print("uid : \(uid)")
MobileUserViewModel().getUser(userId: uid, completionHandler: { (user) in
// here returns an user object
self.loginUser = user
MarketViewModel().getMarketFromDb(mobilUser: user, completionHandler: { (marketler) in
print("marketler : \(marketler)")
completionHandler(marketler)
})
})
}
func objAryToTableView(markets : [MarketModel]) {
var ary = [String]()
for m in markets {
ary.append(m.marketName as String!)
}
self.marketAdiArray = ary
}
The code inside the block (completion handler) is getting executed after getMarketFromDb call succeeds. The code which is outside the block is executed just after the previous line where you don't have any data on the array.
If You need to trigger the UI update which updated data from data Db then you need to invoke the UI update from inside the completion block.
getMarkets { (marketdizi) in
self.objAryToTableView(markets: marketdizi)
print(self.marketAdiArray) // -> this returns the right array
self.tableView.reloadData()
}
print(self.marketAdiArray) // -> this returns an empty array

Returning data from function in Firebase observer code block swift

I'm new to firebase and I want to know if is any possible way to return data in observer block. I have class ApiManager:NSObject and in this class I want to create all my firebase function that will return some kind of data from database. This is one of my function in this class
func downloadDailyQuote() -> [String:String] {
let reference = Database.database().reference().child("daily")
reference.observeSingleEvent(of: .value) { (snap) in
return snap.value as! [String:String] //I want to return this
}
return ["":""] //I don't want to return this
}
And if I now do something like let value = ApiManager().downloadDailyQuote(), value contains empty dictionary. Is any solution for that?
Update: When you call .observeSingleEvent, you call the method asynchronously. This means that the method will start working, but the response will come later and will not block the main thread. You invoke this method, but there is no data yet and therefore you return an empty dictionary.
If you use the completion block, then you will get the data as soon as the method action is completed.
func downloadDailyQuote(completion: #escaping ([String:String]) -> Void) {
let reference = Database.database().reference().child("daily")
reference.observeSingleEvent(of: .value) { (snap) in
if let dictionaryWithData = snap.value as? [String:String] {
completion(dictionaryWithData)
} else {
completion(["" : ""])
}
}
}

Swift can't update Parse object

I have a problem and I could really use some help..
I have the method below and everything works fine, until line 907.. When it comes for the object3.saveInBackgroundWithBlock, it does nothing.. Not even errors! It never saves the object and it never goes inside the block..
Any idea why?
func addUserToThoseIFollow(sender: UIButton) {
//self.navigationItem.rightBarButtonItem?.enabled = false
sender.enabled = false
let userQuery = PFQuery(className: "_User")
let userQuery2 = PFQuery(className: "_User")
userQuery.getObjectInBackgroundWithId(PFUser.currentUser().objectId) { (object: PFObject!, error: NSError!) -> Void in
if error == nil {
// If I already follow some users, make
// an array with them, add the user I
// want to follow and save. Else,
// just save an array, with that one user.
if object["following"] != nil {
var thoseIFollow = object["following"] as! [String]
thoseIFollow.append(self.userID!)
object["following"] = thoseIFollow
}
else {
var myUsers = [String]()
myUsers.append(self.userID!)
object["following"] = myUsers
}
object.saveInBackgroundWithBlock({ (ok: Bool, error2: NSError!) -> Void in
if error2 == nil {
self.followButton.setTitle("Unfollow", forState: .Normal)
self.followButton.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.7)
sender.enabled = true
self.doIFollow = true
}
})
}
}
// Add me to his followers
userQuery2.getObjectInBackgroundWithId(self.userID) { (object3: PFObject!, error3: NSError!) -> Void in
if error3 == nil {
// If the user I just followed, has already followers, make
// an array with them and add the current user to
// them. Else, just save an array, with the current user.
if object3["followers"] != nil {
var hisFollowers = object3["followers"] as! [String]
hisFollowers.append(PFUser.currentUser().objectId)
object3["followers"] = hisFollowers
/* Line 907 */ object3.saveInBackgroundWithBlock({ (ok7: Bool, error7: NSError?) -> Void in // Line 907
if error7 == nil {
print("ok")
}
else {
print(error7)
}
})
}
else {
var hisFollowers = [String]()
hisFollowers.append(PFUser.currentUser().objectId)
object3["followers"] = hisFollowers
object3.saveInBackgroundWithBlock( { (ok5: Bool, error7: NSError!) -> Void in
print("otinanai")
if error7 != nil {
print(error7.localizedDescription)
}
})
}
}
}
}
Attempt #1
What PFUser.currentUser().objectId return ? If it returns nil so it doesn't work.
Attempt #2
Available Parse Types
So far we've used values with type NSString, NSNumber, and PFObject. Parse also supports NSDate, and NSNull.
You can nest NSDictionary and NSArray objects to store more structured data within a single PFObject.
Try to use var hisFollowers = [NSString]() instead of var hisFollowers = [String]()
self.userID
Where exactly is this coming from?
Did your check, whether it is an optional?
Comment out the first query and see if it works.
Each Parse object can only have one background thread running for it at a time. Say you save an object, then immediately in the next line (not inside of its call back), edit it and then save it again. The second save will not be called, since the first save is still running. You don't even get an error. You get zero notification whatsoever that this call didn't happen.
My guess is that you have objects being saved inside both the first query and the second query, and because of that, the second query's save is being skipped.
The solution would be to stick the second query inside of the first's callback.
I think that there's a PromiseKit library you can download that adds javascript functionality in iOS, making it more similar to how you'd chain these calls in cloud code, but I haven't used it.

Array retuning a blank array outside of PFQuery with Parse.

Array returning a blank array when outside of the PFQuery. For some reason, the items are not being passed to the array when compiled.
class DriverViewController: UIViewController {
var placesArr : Array<Place> = []
override func viewDidLoad() {
super.viewDidLoad()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var query = PFQuery(className:"places")
query.whereKey("username", equalTo:"email#email.com")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
println("Successfully retrieved \(objects!.count) scores.")
if let objects = objects as? [PFObject] {
for object in objects {
let x = Place(aIdent: (object["Ident"] as! Int), aName: (object["name"] as! String), aAddress: (object["originAddress"] as! String), aCity: (object["originCity"] as! String), aCategoryName: (object["catName"] as! String), aLat: (object["aLat"] as! String), aLng: (object["aLng"] as! String))
self.placesArr.append(x)
println(placesArr) //****It works here and prints an array****
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
println(placesArr) //****But here it returns a blank array and this is where I need it to return an array****
This a very common misunderstanding relating to threading, the issue is what order events run:
// Runs 1st
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
// Runs 3rd
}
// Runs 2nd
println(placesArr)
The execution of the program doesn't halt when you call findObjectsInBackground, it finds objects: inBackground which means the heavy lifting of a network request is dispatched to a different queue so that the user can still interact with the screen. A simple way to do this would be to do:
var placesArray: [Place] = [] {
didSet {
// Do any execution that needs to wait for places array here.
}
}
You can also trigger subsequent actions within the parse response block, I just personally find executing behavior dependent on a property update being executed in didSet to be a nice way to control flow.
Logan's answer is right in the money.
See my answer in this thread: Storing values in completionHandlers - Swift
I wrote up a detailed description of how async completion handlers work, and in the comments there is a link to a working example project the illustrates it.
query.findObjectsInBackgroundWithBlock is a block operation that performs in the background - it's asynchronous.
The line println(placesArr) actually executes before the block is finished - that's why you see nil there.

Resources