Core Data Delete only working intermittently - ios

I've getting intermittent results on deletion. Sometimes the objects will delete, but most the time when I restart the project or even directly after the delete code, the store still pulls up instances of the objects. I'm deleting through a reference to managedObjectContext from the AppDelegate and making sure I save post delete.
if let object = getById(id, context: context){
context.deleteObject(object)
do{
print("Deleteing object by id")
try context.save()
}catch{
print("Unable to delete object for some reason")
}
}
If I run a getById() with the same id again right after I've successfully saved my deletion, it finds the object again. The error block never triggers, so I figure there is something else going wrong here. Any ideas where to look?

I think you are deleting the found object before entering the do loop and the context.save() is saving it back. That is probably the reason the Error block is not triggering when you look for the object.
try -
if let object = getById(id, context: context) {
do {
try context.deleteObject(object)
try context.save() (I am still not sure if this statement should be there!!!)
print()
}catch{
print()
}
}
Hope that helps.

Figured it out. Deleting was always working fine, the problem was that the identifiers to which I was fetching in my getById() method were not always unique. This caused intermittent deleting to occur because if there were 7 objects with id 1, than there was a 1/7 chance the first object was in fact the one I wished to be deleted.
Long story short, examine the whole problem, and don't make assumptions unless your sure in my case here, that the getById() was actually returning the desired object.

Related

iOS: Firebase removeValue seems to be corrupting array data?

So I've been running into a bizarre problem. Essentially, I have an array which is the data source for a tableView:
data : [(String: SomeDataStruct)] = [(username1, data1), (username2, data2), (username3, data3)...]
In my firebase database, my data is:
If I delete a value from the tableview, this is done by deleting the corresponding item from the array. I then delete the item from firebase to keep the data in sync. For example if I want to delete username2, I remove the tuple with username2 from the array - it's at index 1. Then I use:
data : [(String: SomeDataStruct)] = [(username1, data1), (username2, data2), (username3, data3)...]
referenceToOrders.child("username2").removeValue(completionBlock: { (err, ref) in
print(data)
// data is [] when it should be [(username1, data1), (username3, data3), (username4 ... ]
})
I've isolated it down to this one line - for some reason invoking the firebase removeValue function results in the data array getting wiped out. As a workaround, before invoking the call, I create a copy of the data array. However, I'm not happy with this solution.
Help?
Answering for future reference and others.
So after adding a lot of print statements, I found out that the issue was that I had a observe Firebase handler that ran right after the removeValue but before the completion handler. Makes sense because the database has changed at this point, so the handler fires.

iOS: CloudKit perform(query: ) does nothing - closure not executed

I am in process in adding CloudKit to my app to enable iCloud sync. But I ran into problem with my method, that executes query with perform method on private database.
My method worked fine, I then changed a few related methods (just with check if iCloud is available) and suddenly my perform method does nothing. By nothing I mean that nothing in perform(query: ) closure gets executed. I have breakpoint on the first line and others on the next lines but never manage to hit them.
private static func getAppDetailsFromCloud(completion: #escaping (_ appDetails: [CloudAppDetails]?) -> Void) {
var cloudAppDetails = [CloudAppDetails]()
let privateDatabase = CKContainer.default().privateCloudDatabase
let query = CKQuery(recordType: APPID_Type, predicate: NSPredicate(format: "TRUEPREDICATE"))
privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
if let error = error {
print(error)
completion(nil)
} else {
if let records = records {
for record in records {
let appId = record.object(forKey: APPID_ID_Property) as? Int
let isDeleted = record.object(forKey: APPID_ISDELETED_Property) as? Int
if let appId = appId, let isDeleted = isDeleted {
cloudAppDetails.append(CloudAppDetails(id: appId, isDeleted: isDeleted == 1))
}
}
completion(cloudAppDetails)
return
}
}
completion(nil)
}
}
My problem starts at privateDatabase.perform line, after that no breakpoints are hit and my execution moves to function which called this one getAppDetailsFromCloud. There is no error...
This is my first time implementing CloudKit and I have no idea why nothing happens in the closure above.
Thanks for help.
EDIT: Forgot to mention that this metod used to work fine and I was able to get records from iCloud. I have not made any edits to it and now it does not work as described :/
EDIT 2: When I run the app without debugger attached then everything works flawlessly. I can sync all data between devices as expected. When I try to debug the code, then I once again get no records from iCloud.
In the completion handler shown here, if there's no error and no results are found, execution will fall through and quietly exit. So, there are two possible conditions happening here: the query isn't running or the query isn't finding any results. I'd perform the following investigative steps, in order:
Check your .entitlements file for the key com.apple.dev.icloud-container-environment. If this key isn't present, then builds from xcode will utilize the development environment. If this key is set, then builds from xcode will access the environment pointed to by this key. (Users that installed this app from Testflight or the app store will always use the production environment).
Open the cloudkit dashboard in the web browser and validate that the records you expect are indeed present in the environment indicated by step 1 and the container you expect. If the records aren't there, then you've found your problem.
If the records appear as expected in the dashboard, then place the breakpoint on the .perform line. If the query is not being called when you expected, then you need to look earlier in the call stack... who was expected to call this function?
If the .perform is being called as expected, then add an else to the if let record statement. Put a breakpoint in the else block. If that fires, then the query ran but found no records.
If, after the above steps, you find that the completion handler absolutely isn't executed, this suggests a malformed query. Try running the query by hand using the cloudkit dashboard and observing the results.
The closure executes asynchronously and usually you need to wait few seconds.
Take into account you can't debug many threads in same way as single. Bcs debugger will not hit breakpoint in closure while you staying in your main thread.
2019, I encountered this issue while working on my CloudKit tasks. Thunk's selected answer didn't help me, so I guess I'm gonna share here my magic. I got the idea of removing the breakpoints and print the results instead. And it worked. But I still need to use breakpoints inside the closure. Well, what I had to do is restart the Xcode. You know the drill in iOS development, if something's not right, restart the Xcode, reconnect the device, and whatnot.

Deleting CloudKit Records Swift 4

I am having issues deleting CloudKit records. This is my first time dealing with the API and apparently there are two ways to do this.
Saving records is straight forward and ostensibly so is deleting them, except this doesn't do it:
func deleteRecords() {
let recordID = record.recordID
publicDatabase.delete(withRecordID: recordID) { (recordID, error) in
guard let recordID = recordID else {
print(error!.localizedDescription)
return
}
print("Record \(recordID) was successfully deleted")
}
}
I understand using a ckModifyRecordsOperation is another way to do this but this is a batch operation. I only need to delete one record at a time. Here's my code for that:
func batchDelete() {
let recordIDsToDelete = [CKRecordID]()
let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDsToDelete)
operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
// handle errors here
}
publicDatabase.add(operation)
print("Batch \(recordIDsToDelete) record was successfully deleted")
}
Neither of these separately or together are working for me.
You are correct, there are two ways. The first way you describe is referred to by Apple as a "convenience" function. If you're just deleting a single record, it's probably the quickest option to implement. However, each convenience operation conducts its own trip to the database. If you loop through thousands of records and delete them individually with the convenience function, you're going to use a lot of your cloudKit quota making a series of individual calls.
The second option, the operation, let's you batch the deletes and send them in one operation. Generally, this will be a more efficient use of your cloudkit quotas. But, according to Apple docs, there's no technical difference between the two; the the convenience function is just a wrapper to the operation.
Now, to your specific problem, the operation has two separate completion blocks: perRecordCompletionBlock and modifyRecordsCompletionBlock. As the names imply, the first block is called after each and every record is processed in the operation and that's where errors are surfaced. Make sure you implement perRecordCompletionBlock and check for errors there (and then you'll have to decide if your error handling steps belong in the perRecordCompletionBlock or the modifyRecordsCompletionBlock).
Finally, if the operation (or convenience function) is running and you confirm that the completion blocks fire without errors but the record still doesn't delete, this typically indicates you passed nil rather than a valid record to the deletion.

Properly handle Realm object deletion

I have a Roof object in a Realm DB, and I use it to show some data on the screen. After the user logs out, I delete the Roof object and then update the screen. Inside this update method the app crashes with the message: "Object has been deleted or invalidated."
Should the object become invalidated, or is this not supposed to do? Should I just check in the update method if the roof is invalidated, or is there a better way to handle a non-existent object?
Here is the basic code that I use:
class Roof: Object {
dynamic var info: String?
}
let roof = Roof()
let realm = try! Realm()
try! realm.write {
realm.add(roof)
}
try! realm.write {
realm.delete(roof)
}
Following my understand, this is the basic flow: RealmDB -> Container -> View.
When your container (may be an array) holds references to the objects in the DB, but if one was deleted before, then when you update the View, it can take nothing. Because your container is older than the DB.
My answer is query again (update the container), then update your view.
When you delete object from Realm all its instances become invalid and you can't use it anymore. You can check if the object has been invalidate with isInvalidated property.

Having to call fetch twice from CoreData

Both on simulator and my real device, an array of strings is saved upon app termination. When I restart the app and fetchRequest for my persisted data (either from a viewDidLoad or a manual button action), I get an empty array on the first try. It isn't until the second time I fetchRequest that I finally get my data.
The funny thing is that there doesn't seem to be a time discrepancy involved in this issue. I tried setting various timeouts before trying to fetch the second time. It doesn't matter whether I wait 10 seconds to a minute -- or even immediately after the first fetch; the data is only fetched on the second try.
I'm having to use this code to fetch my data:
var results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
while (results.isEmpty) {
results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
}
return results
For my sanity's sake, here's a checklist:
I'm initializing the Core Data Stack using boilerplate code from Apple: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1
I'm putting my single DataController instance in a static variable at the top of my class private static let context: NSManagedObjectContext = DataController().managedObjectContext
I'm successfully saving my context and can retrieve the items without any issue in a single session; but upon trying to fetch on the first try in a subsequent session, I get back an empty array (and there lies the issue).
Note** I forgot to mention that I'm building a framework. I am using CoreData with the framework's bundle identifier and using the model contained in the framework, so I want to avoid having to use logic outside of the framework (other than initalizing the framework in the appDelegate).
The Core Data stack should be initialized in applicationDidFinishLaunchingWithOptions located in appDelegate.swift because the psc is added after you're trying to fetch your data.
That boilerplate code from Apple includes:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
/* ... */
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
The saved data isn't available until the addPersistentStoreWithType call finishes, and that's happening asynchronously on a different queue. It'll finish at some point but your code above is executing before that happens. What you're seeing isn't surprising-- you're basically looping until the async call finishes.
You need to somehow delay your fetch until the persistent store has been loaded. There are a couple of possibilities:
Do something sort of like what you're already doing. I'd prefer to look at the persistent store coordinator's persistentStores property to see if any stores have been loaded rather than repeatedly trying to fetch.
Post a notification after the persistent store is loaded, and do your fetch when the notification happens.

Resources