How to synchronously retrieve PFUser objectForKey in Swift - ios

I am trying to synchronously retrieve a PFUser object information. However, I've tried using fetch, but I keep on getting implementation errors. I would like to find for example: PFUser.currentUser()?.objectForKey("scoreNumber"), but I am not getting the updated value. I was told I would find it using fetch, but I am unable to implement objectForKey in my fetch function.
Some of the attempts
var score: String!
func retrieveScore(){
guard let myUser = PFUser.currentUser() else { return }
do{ let data = try myUser.objectForKey("scoreNumber")?.fetch()
try scoreNumber = data! as! String
}catch{}
}
Another one
let userScore = PFUser.currentUser()?.objectForKey("scoreNumber")
let userScore2 = userScore.fetch()

You shouldn't save the user for NSUserDefaults. Parse SDK does a good job of managing your sessions for you. Just have a specific log out button that calls the [PFUser logout]` function.
So, fetch is an asynchronous operation. This means that it runs on a separate thread, and then the main thread will continue executing the next commands. So, you're calling fetch, which fetches an object in the background, but then you are trying to access a value from the object that hasn't been fetched yet. You need to access the object from within the block handling the results of the fetch call.
When you call fetch the way you did, just with .fetch(), it runs in the background but doesn't alert you when you have the data, nor if the fetch failed. This is not a good way to get data you're going to need immediately.
You need to use fetchInBackgroundWithBlock()
scoreNumber.fetchInBackgroundWithBlock() {
(scoreNumber: PFObject?, error: NSError?) -> Void in
if error == nil && scoreNumber != nil {
print(scoreNumber)
} else {
print(error)
}
}

Related

Apple Core Data saving duplicates of entities when initializing entities

I am currently building an iOS app that utilizes Core Data to cache Firebase queries, so that my app won't be hitting the Firebase server multiple times for the same query.
My main issue right now is with fetching what will be labeled as IdeaEntity, where the initial fetch will result in the published array of IdeaEntity storing duplicates of the IdeaEntity elements. I will post images below, along with the code snippet for how I'm fetching IdeaEntity arrays from Core Data. However, when re-opening the app (when you close it down via app preview), the duplicates seem to disappear.
iOS Simulator 16.2; Showing proof of duplicate IdeaEntity
// Fetch either from Firebase or Core Data
func fetchIdeas() {
let request = NSFetchRequest<IdeaEntity>(entityName: "IdeaEntity")
do {
self.savedIdeas = try appContainer.viewContext.fetch(request)
if self.savedIdeas.isEmpty {
self.initialFetchIdeas()
}
} catch {
print("ERROR - \(error)")
}
}
// Initial Function to fetch IdeaEntity
private func initialFetchIdeas() {
do {
let uid = self.firebaseService.fetchUserID()
guard !uid.isEmpty else { throw AltyrErrors.cannotFetchUid }
self.firebaseService.fetchUser(withUid: uid) { [weak self] user in
guard let self = self else { return }
self.firebaseService.fetchIdeas(user: user) { ideas in
guard !(ideas.isEmpty) else { return }
ideas.forEach { idea in
let newIdea = IdeaEntity(context: self.appContainer.viewContext)
// ... Insert code for inserting parameters for `newIdea`
if let feedback = idea.feedback {
let newFeedback = FeedbackEntity(context: self.appContainer.viewContext)
// ... Insert code for inserting parameters for `newIdea` Feedback object
newIdea.feedback = newFeedback
}
}
self.save()
}
}
} catch {
// TODO: Error handling...
print("ERROR - \(error)")
}
}
Couple of assumptions:
FirebaseService functions are assumed to be 100% functional, as in they output the needed items in a proper manner.
The above functions are in a class named CoreDataService, which is a class that inherits from ObservableObject
savedProjects is a Published array of IdeaEntity
If there is a better manner of fetching with another library, a different method for fetching IdeaEntity, need any other code snippets / signatures, or anything else, please do let me know.
I had tried switching around fetchIdea with initialFetchIdeas within the private init (since CoreDataService is a singleton) to see if fetchIdea was missing anything that initialFetchIdeas had, though the result is the same (e.g., duplicate entities).
Edit: TL;DR of the solution, just use NSCache if you have really complex objects. It's not worth the effort to utilize Core Data.

CoreData multithreading fetch requires small delay before accessing attributes

I use coreData as persistent store.
To read data, I use (only essential parts are shown):
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (backgroundManagedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
var shoppingItems: Set<ShoppingItem> = []
do {
let cdShoppingItems: [CDShoppingItem] = try backgroundManagedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
nextCdShoppingItem.managedObjectContext!.performAndWait {
Thread.sleep(forTimeInterval: 0.1) // This seems to be required
let nextShoppingItem = ShoppingItem.init(name: nextCdShoppingItem.name!)
shoppingItems.insert(nextShoppingItem)
} // performAndWait
} // for all cdShoppingItems
completion(shoppingItems, nil)
return
} catch let error as NSError {
completion(nil, error)
return
} // fetch error
} // performBackgroundTask
} // fetchShoppingItems
To test the coreData implementation, I wrote a unit test that creates multiple threads that write to and read from coreData concurrently.
This test runs only successfully, if the instruction
Thread.sleep(forTimeInterval: 0.1) // This seems to be required
is inserted in the performAndWait closure.
If it is commented out, nextCdShoppingItem is often read back with nil attributes, and the function crashes due to the forced unwrap.
I am not sure, if nextCdShoppingItem.managedObjectContext!.performAndWait is correct or if I had to use backgroundManagedContext.performAndWait, but with backgroundManagedContext the effect is the same.
I do not understand why inserting a small delay before accessing an attribute of a managed object is necessary to avoid the problem.
Any hint is welcome!
EDIT:
I investigated the issue further, and found the following:
Every time nextCdShoppingItem is read back by the background thread (called read thread below) as nil, there is also another background thread that tries to save its own managedContext after all records in its managedContext have been deleted (called write thread below).
Apparently the read thread tries to fetch a record that has just been deleted by the write thread.
So the problem is definitively a multithreading issue, and I found a solution (see my answer below).
performAndWait will add the block to the queue and schedule it to run, just like perform, but performAndWait will not return until the block is complete. Since you are inside a loop of cdShoppingItems, the loop does not stop and wait for the block to return. By adding the thread sleep, you are essentially slowing down the loop and giving core data enough time to complete its fetch. The forced unwrap crash is probably an indication that it's lost its nextCdShoppingItem reference.
I would consider refactoring where you do not need to query core data inside a loop. If it's possible, add the name attribute to CDShoppingItem so you don't have to fetch it to build a ShoppingItem object.
Edit: took a stab at a refactor although I don't know your exact use case:
func fetchShoppingItems(completion: #escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (backgroundManagedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
var shoppingItems: Set<ShoppingItem> = []
let cdShoppingItems: [CDShoppingItem] = try backgroundManagedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
if let name = nextCdShoppingItem.name {
let nextShoppingItem = ShoppingItem.init(name: name)
shoppingItems.insert(nextShoppingItem)
}
}
completion(shoppingItems, nil)
} catch let error as NSError {
print("Error fetching CDShoppingItem: \(error)")
completion(nil, error)
} // fetch error
return
} // performBackgroundTask
} // fetchShoppingItems
To prevent the multithreading issue, I tried 2 things:
1) Since iOS10, a persistentStore of SQL type maintains a connection pool for concurrent access to the pool, and it is possible to set a maximum pool size, see the WWDC video. I did so using
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// ...
} else {
storeDescription.setOption(NSNumber(1), forKey: NSPersistentStoreConnectionPoolMaxSizeKey)
}
})
return container
}()
to prevent concurrent access to the persistentStore. Unfortunately, this did not solve the problem for an unknown reason.
2) I then tried to serialize read and write operations by setting up a serial queue:
private let coreDataSerialQueue = DispatchQueue(label: "com.xxx.ShopEasy.coreDataManager") // serial by default
It is used for the read and write accesses in the following way:
coreDataSerialQueue.async {
let backgroundManagedContext = self.persistentContainer.newBackgroundContext()
backgroundManagedContext.performAndWait {
// …
} // performAndWait
} // coreDataSerialQueue.async
This did solve the problem.
Please note that it would be wrong to use
coreDataSerialQueue.async {
self.persistentContainer.performBackgroundTask { (backgroundManagedContext) in
// …
} // performBackgroundTask
} // coreDataSerialQueue.async
because performBackgroundTask would fork another asynchronous thread and thus break the serialization.

Cannot signal semaphore with Firebase observeEventType - what gives?

I am using Firebase to read/write data to a remote server.
My objective is to write a routine that also takes a callback function. Heres what the routine should accomplish:
Write some data to Firebase (this works fine)
Observe for changes on above written data
If change occurs call callback with success
If a timeout is triggered, call callback with failure
Im unable to figure out why steps 2-4 are failing for me. Below is a snippet of the code
Here's whats going on in the code:
It sets up a GCD semaphore
It writes some data to the root firebase URL - specifically it adds a user called joe. This works fine. I can see the data written from Vulcan
it sets up a reference to Joe's node and watches for events on childAdded for Joe. Here is the API reference
Upon successfully getting an event it signals the semaphore, which should call the callback with message "Success"
Instead I see the "timeout" message. Strangely though the observer does print out the newly added key:value pair, which is doubly confusing!
Whats going on here? Am I using semaphores incorrectly? Or am i using Firebase's API incorrectly?
typealias completionfoo = (gid:String) -> Void
func setStateReady(somestr: String, callfoo: completionfoo) {
let semaphore = dispatch_semaphore_create(0)
var myRootRef = Firebase(url:"https://xxxxxx.firebaseio.com/")
var joe = ["state" : "READY", "msg" : somestr]
var usersRef = myRootRef.childByAppendingPath("users")
var users = ["joe": joe]
usersRef.setValue(users)
var joeref = Firebase(url: "https://xxxxxx.firebaseio.com/users/joe/")
// Read data and react to changes
joeref.observeEventType(.ChildAdded, withBlock: {
snapshot in
print(snapshot.key)
print(snapshot.value)
dispatch_semaphore_signal(semaphore)
})
let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(30 * Double(NSEC_PER_SEC)))
if dispatch_semaphore_wait(semaphore, timeout) != 0 {
joeref.removeAllObservers()
callfoo(gid: "timeout")
} else {
joeref.removeAllObservers()
callfoo(gid: "success")
}
}
override func viewDidLoad() {
super.viewDidLoad()
setStateReady("hi i am joe") { (gid) -> Void in
print(gid)
}

Best way to use findObjectsInBackgroundWithBlock when moving data around

I have a Tableview that gets data with findObjectsInBackgroundWithBlock in viewDidLoad and passes that data to a Detail View Controller no problem.
Im having trouble managing the flow of findObjectsInBackgroundWithBlock. Here is a example: I have a like button on the detail view and when pressed it increments the UILabel and displays it. It also then gets the object in Parse then increments and saves it... Everything good.
#IBAction func likeButtonPressed(sender: AnyObject) {
print("likeButtonPressed()")
// Adding the like to label
mixLike!++
var stringForCount: String = String(mixLike!)
mixLikeLabel.text = stringForCount
// Saving the like back to Parse
var query = PFQuery(className: "musicMixes")
query.whereKey("info", equalTo: mixNameLabel.text)
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!, error:NSError!) -> Void in
if error == nil {
for object in objects {
//var votes = object["votes"] as! Int
let mixObject:PFObject = object as! PFObject
mixObject.incrementKey("votes", byAmount: 1)
mixObject.saveInBackgroundWithTarget(nil, selector: nil)
print("mixObjectSaved")
}
} else {
print("Error getLikeCount()")
}
print("sending Notification...")
NSNotificationCenter.defaultCenter().postNotificationName("reload", object: nil)
print("sent Notification...")
}
} // likeButtonPressed End
I also then call a NSNotification back to the Table View so the Table View can update the likes to match the users like click on the detail view (See bellow)
The NSNotification calls this function in the Table View, which removes the like array, grabs the new likes again and then reloads the Table View.
# objc func reloadTableData(notification: NSNotification){
print("Notification Recived, Removing Likes and Reloading. reloadTableData()...")
self.mixLikeArray.removeAll()
//self.stringForCountArray.removeAll()
print("Like array Data removed, getting data again...")
var query = PFQuery(className: "musicMixes")
query.orderByAscending("date")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!,error: NSError!) -> Void in
if error == nil {
for object in objects {
let mixLike = object["votes"] as! Int
self.mixLikeArray.append(mixLike)
print("New mixLikeArray data is \(self.mixLikeArray)")
}
} else {
print("error getting like object")
}
}
dispatch_async(dispatch_get_main_queue(),{
self.allTableView.reloadData()
});
}
I see three issues wrong with how this works at the moment. likeButtonPressed() Is sometimes sending the NSNotification before mixObject.saveInBackgroundWithTarget is finished. Meaning that the incremented like won't be displayed on the table view.
Secondly if I was to click like then click back to tableview swiftly the app will crash. This is because I'm guessing both likeButtonPressed() and the NSNotification function still has not been completed.
Also in # objc func reloadTableData(notification: NSNotification) once again the
dispatch_async(dispatch_get_main_queue(),{
self.allTableView.reloadData()
});
Is being called before the findObjectsInBackgroundWithBlock is being completed? Anyway round this?
How would you suggest I can remodel this to work efficiently? Im pretty new to coding and a bit rusty with designing the best ways to do things... I know the concept behind completion handlers could I use these? I know that Parse likes to work in the background though hhhmmmm.....
to fix your reloadTableData problem, you should trigger the reload once the parse block is done executing, which means moving this line
dispatch_async(dispatch_get_main_queue(),{
self.allTableView.reloadData()
});
inside the block
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!,error: NSError!) -> Void in
if error == nil {
for object in objects {
let mixLike = object["votes"] as! Int
self.mixLikeArray.append(mixLike)
print("New mixLikeArray data is \(self.mixLikeArray)")
}
dispatch_async(dispatch_get_main_queue(),{
self.allTableView.reloadData()
});
} else {
print("error getting like object")
}
}
That will ensure that it gets triggered once parse is done updating objects. Currently its triggering before that while the block is executing. It also means that it won't reload if you get an error as you probably need to handle that differently anyway.
As for your problem of the notification happening before the saving is complete, you are calling . saveInBackgroundWithTarget but don't seem to send anything into it. You could use saveInBackgroundWithBlock and then use dispatch_group dispatch_group_enter, dispatch_group_leave, and dispatch_group_notify inside the block to make your program wait till everything is done being saved before sending the notification.
So you would create a dispatch_group
dispatch_group_t group = dispatch_group_create();
And then call it dispatch_group_enter in the for loop through the objects
for object in objects {
dispatch_group_enter(group);
let mixObject:PFObject = object as! PFObject
.....
}
Then call dispatch_group_leave on the mixObject.saveInBackgroundWithBlock
and wrap the notification in dispatch_group_notify
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 4
NSNotificationCenter.defaultCenter().postNotificationName("reload", object: nil)
});
Something like that
It sounds more daunting than it is, here's a Ray Wenderlich tutorial to bring you up to speed on how to use it, if your not familiar

NSMetadataQuery’s update notification interferes with (run loop?)

I emailed an Apple engineer last week about a problem with my NSMetadataQuery.
Here’s the email:
Hi,
I'm writing a document-based app or iOS and my method for renaming (moving the document to a new location) seems to conflict with the running NSMetadataQuery.
The query updates a couple of time after the move method is called, the first time it has the old URL of the item that just moved, and the next it has the new URL. However, because of my updating method (below) if a URL has been removed since the update, my model removes the deleted URL and vice versa for if it finds a URL which doesn't exist yet.
I think my problem is one of two issue, either the NSMetadataQuery's update method is insufficient and doesn't check an item's URL for the 'correct' attributes before deleting it (although looking over documentation I can't see anything that would suggest I'm missing something) or my renaming method isn't doing something it should.
I have tried disabling updates at the start of the renaming method and reenabling once all completion blocks are finished but it doesn't make any difference.
My NSMetadataQuery's update method:
func metadataQueryDidUpdate(notification: NSNotification) {
ubiquitousItemsQuery?.disableUpdates()
var ubiquitousItemURLs = [NSURL]()
if ubiquitousItemsQuery != nil && UbiquityManager.sharedInstance.ubiquityIsAvailable {
for var i = 0; i < ubiquitousItemsQuery?.resultCount; i++ {
if let result = ubiquitousItemsQuery?.resultAtIndex(i) as? NSMetadataItem {
if let itemURLValue = result.valueForAttribute(NSMetadataItemURLKey) as? NSURL {
ubiquitousItemURLs.append(itemURLValue)
}
}
}
// Remove deleted items
//
for (index, fileRepresentation) in enumerate(fileRepresentations) {
if fileRepresentation.fileURL != nil && !contains(ubiquitousItemURLs, fileRepresentation.fileURL!) {
removeFileRepresentations([fileRepresentation], fromDisk: false)
}
}
// Load documents
//
for (index, fileURL) in enumerate(ubiquitousItemURLs) {
loadDocumentAtFileURL(fileURL, completionHandler: nil)
}
ubiquitousItemsQuery?.enableUpdates()
}
}
And my renaming method:
func renameFileRepresentation(fileRepresentation: FileRepresentation, toNewNameWithoutExtension newName: String) {
if fileRepresentation.name == newName || fileRepresentation.fileURL == nil || newName.isEmpty {
return
}
let newNameWithExtension = newName.stringByAppendingPathExtension(NotableDocumentExtension)!
// Update file representation
//
fileRepresentation.nameWithExtension = newNameWithExtension
if let indexPath = self.indexPathForFileRepresentation(fileRepresentation) {
self.reloadFileRepresentationsAtIndexPaths([indexPath])
}
UbiquityManager.automaticDocumentsDirectoryURLWithCompletionHandler { (documentsDirectoryURL) -> Void in
let sourceURL = fileRepresentation.fileURL!
let destinationURL = documentsDirectoryURL.URLByAppendingPathComponent(newNameWithExtension)
// Update file representation
//
fileRepresentation.fileURL = destinationURL
if let indexPath = self.indexPathForFileRepresentation(fileRepresentation) {
self.reloadFileRepresentationsAtIndexPaths([indexPath])
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
let coordinator = NSFileCoordinator(filePresenter: nil)
var coordinatorError: NSError?
coordinator.coordinateWritingItemAtURL(sourceURL, options: .ForMoving, writingItemAtURL: destinationURL, options: .ForReplacing, error: &coordinatorError, byAccessor: { (newSourceURL, newDestinationURL) -> Void in
var moveError: NSError?
let moveSuccess = NSFileManager().moveItemAtURL(newSourceURL, toURL: newDestinationURL, error: &moveError)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
assert(moveError == nil || moveSuccess, "Error renaming (moving) document from \(newSourceURL) to \(newDestinationURL).\nSuccess? \(moveSuccess).\nError message: \(moveError).")
if let query = self.ubiquitousItemsQuery {
query.enableUpdates()
}
if moveError != nil || moveSuccess {
// TODO: Implement resetting file rep
}
})
})
})
}
}
I had a reply almost instantly but since then there’s been no reply.
Here’s the reply
One of the big things that jumps out at me is your usage of disableUpdates() and enableUpdates(). You’re executing them both on the same turn of the run loop, but NSMetadataQuery delivers results asynchronously. Since this code executes within your update notification, it is executing synchronously with respect to the query. So from the query’s point-of-view, it’s going to begin delivering updates by posting the notification. Posting a notification is a synchronous process, so while it’s posting the notification, updates will be disabled and the re-enabled. Thus, by the time the query is done posting the notification, it’s back in the exact same state it was in when it started delivering results. It sounds like that’s not the behavior you’re wanting.
Here’s where I need help
I took this to assume that NSMetadataQuery has some kind of cache which it adds results to while updates are disabled and when enabled, those (perhaps many) cache results are looped through and each are sent via the updates notification.
Anyway, I had a look at run loops on iOS and although I understand them as much as I can on my own, I don’t understand how the reply is helpful, i.e how to actually fix the problem - or what’s even causing the problem.
If anyone has any good idea I’d love your help!
Thanks.
Update
Here’s my log of when functions start and end:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start renameFileRepresentation:toNewNameWithoutExtension
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
end renameFileRepresentation:toNewNameWithoutExtension
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
start metadataQueryDidUpdate:
end metadataQueryDidUpdate:
I was having the same problem. NSMetaDataQuery updates tell you if there is a change, but does not tell you what that change was. If the change is a rename, there is no way to identify the previous name, so I can find the old entry in my tableView. Very frustrating.
But, you can get the information by using NSFileCoordinator and NSFilePresenter.
Use the NSFilePresenter method presentedSubitemAtURL(oldURL: NSURL, didMoveToURL newURL: NSURL)
As you noted, the query changed notification is called once with the old URL, and once with the new URL. The method above is called between those two notifications.

Resources