Firebase Observe Closure Not Called [duplicate] - swift

This question already has answers here:
Checking if Firebase snapshot is equal to nil in Swift
(2 answers)
How do I check if a firebase database value exists?
(6 answers)
Closed 2 years ago.
Running a Firebase closure such as:
databaseRef.observe(.childAdded) { (snapshot) in
...
}
When first run it will go and get the data and will also execute its closure whenever data is added. This is as it should be. However, when you first fire off this method, and there is no data to get, the closure never returns. In this way there is no way to tell if there is no data or if all of the data just hasn't been gotten yet. Without the closure firing, we cannot look at the snapshot to see if it exists.
Given the documentation indicates that you can interrogate the snapshot to see if it exists or if the values are nil, there must be a way to get called back initially. Again subsequent data added will call back this closure and the closure will come back if there is data there initially.
What am I missing about getting an initial callback if there is no data?
Update:
To provide a working solution which was not provided in the links given indicating this was a duplicate. The reason for the issue - which Frank answered perfectly here and in the thread links he provided - was due to the the way .childAdded works. Frank indicated that one can call both a .value and a .childAdded on the same query and the initial data will not be loaded twice. Based on this key information, I went about handling this in the following way:
databaseRef.observeSingleEvent(.value, with: { (valueSnapshot) in
if !valueSnapshot.exists() {
[do stuff based on the absence of data]
}
databaseRef.observe(.childAdded) { (childSnapshot) in
[do stuff based on children present and when children added]
}
}
The outer event will fire once only. The inner event will be set as a listener so future children added will be executed within its closure only.

A .childAdded event is fired for each child node that exist initially, or that is added later. If there are child nodes, .childAdded is not fired.
If you want to know whether data exists, you should observe the .value event.
If you observe both .child... and .value on the same reference/query, Firebase will only retrieve the data once. So there is no extra data transfer to observe both events.
Also see:
How do I check if a firebase database value exists?
Checking if Firebase snapshot is equal to nil in Swift

Related

Deleting Core Data elements with unwrapped optionals

I have a Core Data Entity with a Date attribute (e.g. current_date. Technically, in the Class which Core Data generates for that Entity, this attribute is optional (#NSManaged public var current_date: Date?). However, in my app this Date is always provided, which is why in the View I am displaying this fetched Entity via a list, I am force unwrapping it with current_date!. This all works fine so far. However, I have added an onDelete to that list to be able to delete single elements and now I am getting a bug Fatal error: Unexpectedly found nil while unwrapping an Optional value. Seems to be some problem related to the deletion process - as said, that value in the data is actually never empty. Does anyone know what is the problem here?
Your code may not set current_date to nil. But if you
Delete a managed object object
Save changes
Try to use that same managed object reference
...then current_date is nil, because that's something Core Data does. This error message suggests that you are attempting to use the object after deleting it and saving changes. Ideally you should fix that, because you won't have valid data anyway. You should also avoid force-unwrapping anyway, because using it is a way of specifically requesting app crashes any time you use it.

Do you need to use, and can you use, perform() and performAndWait() inside performBackgroundTask()?

If I am performing CoreData operations (delete local persistent data, fetch new data from online, save to persistent store) inside a storeContainer.performBackgroundTask() { context in ... } block,
1) Do I NEED to use context.perform() { } inside this to ensure it is thread safe?
2) CAN I use context.performAndWait() { } for part or all of the function inside the curly brackets if I wish to ensure, for example, deletion occurs before downloading and re-saving?
I'm having user crashes associated with CoreData saving which don't appear on testing. I suspect I am failing to understand something about CoreData. I haven't managed to find the answer to this question elsewhere in tutorials or StackOverflow despite searching for ages!
The main job of performBackgroundTask is to create an appropriate background context and call that context on respective queue. You don't need to use "perform" again to switch to private queue.
performAndWait is useful when ever you are on main queue but context is private and you want to finish the database update to move forward(similar cases). You don't need to call performAndWait inside perform because code inside perform executes serially. There is no harm in using though.

Swift - Application crash when using two different OperationQueues with KVO

I'm getting two type of information with JSON and I'm adding "operations" to 2 different Operation Queues Classes with addObserver(forKeyPath:"operations"...).
In the function observeValue I'm checking if operationQueue1.operations.isEmpty and then I refresh my information in UI. I'm doing the same thing with if else with operationQueue2, but when the 2 operations are started in sometime the application crash with error message: *** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <AppName.ViewController 0x102977800> for the key path "operations" from <AppName.OperationQueue1 0x1c4a233c0> because it is not registered as an observer.'
I don't have problem when only 1 operation is started. Any suggestions?
func getInfo1(){//runned in viewDidLoad
operationQueue1.addObserver(forKeyPath:"operations"...)
operationQueue1.dataTask(URL:"..."....){
DispatchQueue.main.async{
NotificationCenter.default.postNotification(NSNotification.Name(rawValue: "NewDataReceived1", userInfo:infoFromTheWebsite)
}
}
}
func NewDataReceived1(){
here I add the information to arrays to be loaded in tableView1
}
HERE IS THE CODE FOR 2ND INFO WHICH IS THE SAME
override func observeValue(forKeyPath keyPath: String?, ....){
if(object as? operationQueue1 == operationQueue1Class && keyPath == "operations" && context == context1){
if(operationQueue1.operations.isEmpty){
DispatchQueue.main.async{
operationQueue1..removeObserver(self, forKeyPath:"operations")
Timer.scheduled("refreshingTableInformation1")
}
}
}else if(operationQueue2....){
SAME AS OPERATION 1, BUT USING DIFFERENT FUNC TO REFRESH TABLE INFORMATION AND THE TABLES ARE DIFFERENT
}else{
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
func refreshingTableInformation1(){
tableView1.reloadData()
Timer.scheduled("getInfo1", repeat:false)
}
func refreshingTableInformation2(){
tableView2.reloadData()
Timer.scheduled("getInfo2", repeat:false)
}
Sometimes it works 10 secs and crash and sometimes works for more than 60 seconds and then crash...
Your code here is vulnerable to race conditions. Consider the following scenario:
getInfo1() is called, which adds an operation to operationQueue1.
The operation completes, which means your KVO observation is called. The queue is now empty, so your observation schedules removal of your observer on the main dispatch queue.
Now, before the operation you've submitted to the main queue is able to run, something else calls getInfo1(), which adds a new operation to operationQueue1, which completes before the operation you queued in step 2 has had the chance to run (hey, maybe the main queue was busy with something; it's easy for this to happen since it's a serial queue).
Your observation for the first call of getInfo1() is called again while the queue is empty, causing another deregister block to be submitted to the main queue.
The two deregister blocks finally get to execute on the main queue. The second one crashes the program, since you've already deregistered your observer.
You could probably fix this problem (assuming the code doesn't have more problems of this nature) by using Swift 4's block-based observers instead, and setting the observer to nil instead of explicitly deregistering it. However, I propose that KVO is the wrong tool for what you're trying to do. As the instructions for the old "Crystal Quest" game used to say, it's a bit like using an anti-aircraft gun to kill a mosquito.
From what I can see from the code above, it looks like you're using KVO just to schedule a notification for when an operation or a group of operations you submit to the queue finishes. Depending on what your dataTask method actually does, here's what I'd do instead:
If you submit only one operation: set the operation's completionBlock property to a closure that refreshes your table information.
If you submit more than one operation: Make a new BlockOperation which refreshes your table information, and call addDependency on that operation with every other operation you submit to the queue. Then, submit that operation.
This will provide you with a much cleaner and more trouble-free way of monitoring completion for your tasks. And since you don't need the queue to completely empty anymore, you might not even have to use two separate queues anymore, depending on what else you're doing with them.

How can I avoid this "variable used within its own initial value" error?

Update:
This is an attempt to improve this question. I have a solution, which I will post as an answer shortly. Its based on the comments I received below. Hopefully this improvement, along with the comments and subsequent answer, can help someone else who might fall into the same trap I did.
I have a collection of objects that need to download some images before I consider them "ready".
I attempted to handle this by passing in a "completion handler" into the init function, which I in turn would pass it to the completion handler of the function that would actually download the images. The intended net result being that after the object was initialized and loaded with it's images, then the closure I passed in during initialization would be called, in this case a notification that the object was "ready"
To accomplish this I tried to capture the newly created object in the closure passed in as the completion handler. (shown below in trailing closure format)
for marker in markerList {
var ourLatitudeMapMarker = OurLatitudeMapMarker(size: size) {
NSNotificationCenter.defaultCenter().postNotificationName("OurLatitudeMapMarkerReady", object: self, userInfo: ["ourLatitudeMapMarker":ourLatitudeMapMarker])
}
}
This results in the error "variable used within its own initial value".
My original, poorly phrased question, was essentially:
How can I get the newly created object into the completion handler passed to the init function so that it can be used in the completion handler when the object finally, is "ready"
In hindsight I think my question should have been:
How can I call a closure when my objects are "ready"? "ready" being initialized and loaded with their images.
As jtbandes pointed out in his comment, one solution to this could have been to simply pass self in as an argument to the the completion handler, but in the end I agreed with both nhgrif's and zaph's comments.
The solution was obvious, once I realized that being initialized and being ready are not the same thing. So I just have init, initialize the object and another method loadImages, takes the completion handler, which is passes to the method that actually down loads the images, and calls the completion handler when the objects are ready.
So now instead of one statement that attempted to do everything (initialize and load the images), I have two.
let ourLatitudeMapMarker = OurLatitudeMapMarker(size: size)
and then later do something like this
ourLatitudeMapMarker.loadImages() {
NSNotificationCenter.defaultCenter().postNotificationName("OurLatitudeMapMarkerReady", object: self, userInfo: ["ourLatitudeMapMarker":ourLatitudeMapMarker])
}

How to validate the values in the context before saving to core data

How can we validate the context before saving it to core data?. My idea is i should have some validation before saving it into core data, if the values doesn't satisfy the validation the the coredata should not save the values. Say for example i have attributes like name, class, age, etc for entity candidate. I should have a validation that the values shouldn't be nil. If it is nil then the other values should not be saved.
Can anybody help me in this regard
EDITED:
I need to check them only at the time of saving and that should be done with core data
I like to do catchall validation in the save routine. Before you actually do the call to save the context, loop through its insertedObjects array and make sure they are as you require. If they aren't, you can either delete them or alert the user that they need to complete something (if the latter, return out of the method; give the user a chance to fix the problem).
Other validation should be at the point of entry, when you are getting values from, say, a textfield or checkbox to assign to your managed objects. Let the user know right away if there's a problem.
Also check out NSNumberFormatter, which can be applied to fields, preventing user from making incorrect entries to begin with.
Coredata validate itself when inserting its values. In managedObject class we can write our custom validation so that coredata will check that validation before saving the values. If the value is not valid then those values in the context will not be saved to coredata.
Here i added
#interface
-(BOOL) validateForInsert:(NSError **)error;
#implementation
-(BOOL) validateForInsert:(NSError **)error {
// check the value of the field with validation
if(condition == true) {
return Yes;
}
return NO;
}
(NSError **) is a special parameter that makes the coredata to call this method as if like a delegate method
Sorry, I hadn’t read your question carefully enough when I made that first answer. You’re not validating that individual entries for individual attrs are correct, rather, that no changes should be saved unless all attrs are filled for that object.
Looking at Apple doc “Model Object Validation”, you are concerned with inter-property validation, not property validation, and you are on the right track to be thinking of using validateForInsert: for this purpose.
That doc also supplies examples. Here’s a possible implementation for the particular entity you describe:
- (BOOL)validateForInsert:(NSError **)error {
if (self.name && self.class && self.age)
return [super validateForInsert:error];
else
return NO;
}
However, this method happens at the insertion stage, not at the save stage.
If you are gathering entries for a new entity all at once, validating at the insertion stage would make sense — don’t add a new object to the context if that object is doomed to be discarded as incomplete.
If you are gathering entries for changes to an existing object, and you want to make sure that all those changes work together before accepting any of them, validateForUpdate: would make sense — but there would be no way to restore the object to its original state other than by reopening the context without saving, unless you had cached its original values elsewhere.
If you want to gather attrs individually and wait to check that they are all complete before saving the object, I think you would do as I first suggested: Loop through the context’s insertedObjects and take care of validation before actually saving the context. There’s no existing validateForSave: method to override, but you could add one.
You could also combine these techniques: Gather entries and make new objects without inserting them, but cache all these objects in an array. When it comes time to save, loop through the cache and insert the objects into the context only if they pass validateForInsert:; then save the context.
Obviously I’m learning along with you, so the above might not be quite the cookie. Hopefully that Apple doc will be enough to get you started.