MagicalRecord does not save new objects - ios

I setup my MagicalRecord stack like so:
MagicalRecord.setupAutoMigratingCoreDataStack()
let moc = NSManagedObjectContext.MR_defaultContext()
moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Now when I create and want to save my object, I get success of false but the returned error is nil too. Also the object still has a temporaryID set to true.
let product = Product.MR_createEntity()!
MagicalRecord.saveWithBlock({ (ctx) -> Void in
product.timeStamp = NSDate()
product.title = "Some title"
}) { (success, error) -> Void in
if !success{
// No success but error is nil
}
}

I've had this in the past with playing with MagicalRecord at first.
Once you've toyed with it, you need to delete the DerivedData folder corresponding to the App you're making (or in turn the whole derived data folder).. It'll run fine the first time, then after that it doesn't or runs like it did the first time.
The general DerivedData folder is at: ~/Library/Developer/Xcode/DerivedData/
delete that and it should be fine. Or another option is delete the folder which corresponds to the folder which your app was built in:
`~/Library/Developer/Xcode/DerivedData/AppName-someRandomString`
i.e.
`~/Library/Developer/Xcode/DerivedData/AppName-ajhtkvwcttbnsulsdfdsfqr`
Finally if this is STILL playing havoc, then another place to look is the App Container (which is the sandbox container) and this lives in:
`~/Library/Containers/com.youridentifier.whatever.AppName`
When you delete this though ALL saved CoreData data will be lost, as along with preferences etc.. pertaining to that App.
Hope this helps...

Related

Core Data model version with new entity

I am unable to migrate my project to a new model version, and I have created the smallest project possible to highlight my issue, and hope that someone can show me what's wrong with my approach.
I have the project in GitHub: MigrateApp
I create a new project in Xcode, call it MigrateApp, choose SwiftUI and hook on the checkbox for Core Data. This should also work with UIKit, because I am not doing anything with the UI.
I immediately run the app, and click the plus button in the upper right a couple of times, to populate the Core Data with some items.
Then I stop the app from Xcode, and click the Core Data MigrateApp in Xcode. I click the Editor menu and choose Add Model Version. I choose the name MigrateApp v2. And I set the current model version to the new version, so that the green checkmark shows in MigrateApp v2.
The entities in MigrateApp v2 shows only one entity, called Item. I now want to add a new entity called Subitem, that have a one-to-one relation to Item. I add the Subitem entity, and adds two attributes, date with type Date and text with type String, both non-optional.
Then I add relationship sub to Item, with destination Subitem, and relationship item to Subitem with destination Item, and sets the inverse of both relationships.
Add a new file, choose Mapping Model type, set the source to MigrateApp.xcdatamodel and target data model to MigrateApp v2.xcdatamodel. Give it the name Modelv1Tov2.
I think that I have done everything correct up to now, so the next steps I am not quite sure I do correctly.
I open the Modelv1Tov2 mapping model file in Xcode, and sets the source of the Subitem entity mapping to Item in the Entity Mapping panel. This renames the Subitem to ItemToSubitem.
Clicking the ItemToItem, I see that the relationship mappings show sub. I click on sub, and sets the Key Path in Relationship Mapping to $source.sub, and mapping name to ItemToSubitem.
Likewise with ItemToSubitem, I click item relationship mappings, and set Key Path to $source.item and mapping name to ItemToItem.
The last thing I need to do, is ensure that Xcode migrate the database automatically, and don't try to infer the migration but use the migration mapping. Add the two lines to Persistence.swift in the init, after setting the container property:
container = NSPersistentContainer(name: "MigrateApp")
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "MigrateApp")
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = false
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
When I now runs the app again from Xcode, it crashes with the message: Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSManagedObject 0x600001d1fcf0> valueForUndefinedKey:]: the entity Item is not key value coding-compliant for the key "item".'
What have I done wrong here?
I have searched for answers to this problem, and I found that I do not need to set the shouldMigrateStoreAutomatically = true and shouldInferMappingModelAutomatically = false. If I let Core Data infer the migration, the migration will be successful. So I remade the steps above for a fresh project, and skipped the step where I added the two lines to Persistence.swift, and also skipped the step adding a mapping model, and the migration was successful. I would still want to know why I had the crash when using the mapping model, and will investigate that issue further.

firestore collection path giving bugs with constants value and String value

So my goal is to get rid of these bugs completely. I am in a dilemma where each decision leads to a bug.
The first thing I can do that eventually becomes an issue is use a String-interpolated collection path in all my query functions like so:
func getEventName() {
listener = db.collection("school_users/\(user?.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
if let error = error {
print("There was an error fetching the data: \(error)")
} else {
self.events = querySnapshot!.documents.map { document in
return EventName(eventName: (document.get("event_name") as! String))
}
self.tableView.reloadData()
}
}
}
The thing with this is, when I run the app on the simulator, I am restricted from pressing buttons and then sometimes I can press them and then sometimes they get restricted again. This bug is so confusing because it makes no sense where it springs from.
The other issue is I can use a Constants value in all the query functions in my collections path.
static let schoolCollectionName = "school_users/\(user?.uid)/events"
This is nested in a Firebase struct within the Constants struct. In order to keep Xcode from giving errors I create a let users = Auth.auth().currentUser variable outside the Constants struct. The issue with this value is that when I put that in all of my query functions collection paths, all the buttons are accessible and selectable all the time, but when a user logs out and I log in as a new user, the previous user's data shows up in the new user's tableview.
It would obviously make more sense to use the Constants value because you prevent typos in the future, but I can't figure out how to get rid of the bug where the old user's data shows up in the new user's tableview. Thanks in advance.
The user id should definitely not be a constant. What it sounds like is that right now, you have no reliable way to change users -- your setup probably depends on which user is logged in at app startup, since that's where your variable gets set.
I would do something more like this:
func getEventName() {
guard let user = Auth.auth().currentUser else {
//handle the fact that you don't have a user here -- don't go on to the next query
return
}
listener = db.collection("school_users/\(user.uid)/events").order(by: "time_created", descending: true).addSnapshotListener(includeMetadataChanges: true) { (querySnapshot, error) in
Note that now, user.uid in the interpolated path doesn't have the ? for optionally unwrapping it (which Xcode is giving you a warning for right now). It will also guarantee that the correct query is always made with the currently-logged-in user.
Regarding being able to press the buttons, that sounds like an unrelated issue. You could run your app in Instruments and check the Time Profiler to see if you have long-running tasks that are gumming up the main/UI thread.

An error occurring during Core Data persistent store migration in iOS 13

After updating XCode to version 11 I added a new model version to Core Data and in new version I added a new attribute to an Entity. Made the new version active and added the new property to managed object file.
After releasing this version to the users it started to crash with the following message: "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store." and "duplicate column name ZNEWCOLUMN". Until now I made a lot of changes to the Core Data model and migration always worked.
This crash appears only on iOS 13!
This is how I load Core Data:
lazy var managedObjectContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "MyModel")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.append(description)
return container
}()
Any help would be appreciated.
The same thing is happening to me, lightweight migration at iOS 12 was right at real device and Simulator but at iOS 13 fail with the next log result:
SQLite error code:1, 'duplicate column name: ZNAME_OF_THE_COLUMN .... Error Domain
= NSCocoaErrorDomain Code = 134110 "An error occurred during persistent storage migration."
I load data like #iOS Dev post.
I check the xxxx.sqlite database file in the emulator path before and after the migration and there were no columns with those new same names.
To know the route of the *.sqlite in emulator you have to put a breakpoint and when it is stopped put in the console po NSHomeDirectory().
Then go to Finder window, tap the keys Control + Command + G and paste the route. Yo can handle it (for example) with DB Browser for SQLite program, it´s free.
After a long search I have seen what has happened to some people but I have not seen any solution.
Mine was:
Select the actual *.xcdatamodel.
Select Editor > Add Model Version.
Provide a version name based on the previous model (like XxxxxxV2.xcdatamodel).
Click on this new version model NewV2.xcdatamodel.
Select this new version as Current on Properties at right hand of IDE.
Make your changes at DDBB.
Run app and will work fine.
I did tests overriding app (with new values) and it was fine.
I hope this may help.
If you want to edit the descriptions, you need to do so before you load the stores (and I have no idea what appending a new description would do):
container.persistentStoreDescriptions.forEach { storeDesc in
storeDesc.shouldMigrateStoreAutomatically = true
storeDesc.shouldInferMappingModelAutomatically = true
}
container.loadPersistentStores { [unowned self] (storeDesc, error) in
if let error = error {
// handle your error, do not fatalError! even a message that something is wrong can be helpful
return
}
// do any additional work on your view context, etc.
}
If your problem is reproduceable, you should look at the error that's being returned and look for something called ZNEWCOLUMN (though this sounds like a temporary default name?) This nomenclature is the raw column name in the SQL database though, so it's likely the migrator is attempting to add this new column and failing.
Try turning on SQL debugging in your scheme's Arguments:
-com.apple.CoreData.SQLDebug 1
Try logging into the raw SQL database (the above will give you the raw path if you're on the simulator). Try rolling back to the previous data model on a previous OS and then just upgrading to 13.
Sounds like you have some duplicate column somewhere so these are just some ideas to find out where it is.

How to delete files in iOS Notification Service Extension?

I have a UNNotificationServiceExtension that downloads videos and images to the Documents directory for use by classes that adopt UNNotificationContentExtension. I want to delete the media files that are no longer being used by any notifications. I am not sure how to go about doing this.
I tried to delete the files in my AppDelegate, but I believe the UNNotificationServiceExtension has its own Documents directory per the "Sharing Data With Your Containing App" section of this document: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html, so I cannot access these files from my main app. They are in a different container.
I don't want to create an App Group to share the data between the app and the extension just so that I can delete the unused files.
I don't want to delete the unused files in the UNNotificationServiceExtension, because the extension has a limited amount of time in which to complete its work, and if I try to download files and delete other files, it may time out.
I think the best option is to check to see which files are needed by any delivered notifications and to delete the unneeded files in the Notification Service Extension's Documents directory. My concern with this is that the UNNotificationServiceExtension is only given a short period of time during which it must complete all of its work, after which it will time out.
So, my question is, "Is this the right way to clean up unused files from a Notification Service Extension, or is there a better way?"
Thanks to manishsharma93, I was able to implement a good solution. I am now storing the files in a directory shared by the main app and the notification service extension. I first had to set up a shared App Group using the information found here: https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW19
Then in my AppDelegate, I added this private function, which I call at the end of the applicationDidFinishLaunching(_:) method:
// I call this at the end of the AppDelegate.applicationDidFinishLaunching(_:) method
private func clearNotificationMedia() {
// Check to see if there are any delivered notifications. If there are, don't delete the media yet,
// because the notifications may be using them. If you wanted to be more fine-grained here,
// you could individually check to see which files the notifications are using, and delete everything else.
UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
guard notifications.isEmpty else { return }
let fileManager = FileManager.default
guard let mediaCacheUrl = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourGroupHere")?.appendingPathComponent("media_cache", isDirectory: true) else { return }
// Check to see if the directory exists. If it doesn't, we have nothing to do here.
var isDirectory: ObjCBool = false
let directoryExists = FileManager.default.fileExists(atPath: mediaCacheUrl.path, isDirectory: &isDirectory)
guard directoryExists && isDirectory.boolValue else {
print("No media_cache directory to delete.", terminator: "\n")
return
}
// The directory exists and there aren't any notifications using media stored there,
// so go ahead and delete it. Use a lock to make sure that there isn't data corruption,
// since the directory is shared.
let lock = NSLock()
lock.lock()
do {
try FileManager.default.removeItem(at: mediaCacheUrl)
DebugLog("Successfully deleted media_cache directory.")
} catch let error as NSError {
DebugLog("Error: \(error.localizedDescription). Failed to delete media_cache directory.")
}
lock.unlock()
}
}
It works like a charm. Thanks again for pointing me in the right direction manishsharma93.

Swift 3 updating core data model causes crash for external testers

I released an app using core data to store some important information.
Recently I decided to re-do my data model to bring it up to date and make it easier to use.
I added some entities to the data model, and it runs fine in the simulator - however when I released it to the beta testers as soon as it tries to do anything with core data it is crashing.
I did not create a new version of the data model.
I have read here and here about how to deal with this error but both answers reference code that I do not have anywhere in my app, but they seem to have built in - they also talk about lightweight data migration? A lot of the answers reference a NSPersistentStoreCoordinator, which I do not have/know how to implement.
The code I have in the app delegate dealing with the persistentContainer is:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "App_Name")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
The other answers also referenced crashes in the simulator which required re-installation - I don't think I got these, but I may not have noticed.
What is the best way for me to update my data-model such that my users won't get crashes?
EDIT:
I have updated the persistentContainer to this:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "App_Name")
let myFileManager = FileManager()
do {
let docsurl = try myFileManager.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let myUrl = docsurl.appendingPathComponent("UserDataTA")
if try myFileManager.contentsOfDirectory(atPath: docsurl.path).contains("UserDataTA") == false {
try myFileManager.createDirectory(at: myUrl, withIntermediateDirectories: false, attributes: nil)
try container.persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: myUrl, options: nil)
}
} catch {
print(error)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalErrorText = error.debugDescription
firstFuncs.errorAlert(error: fatalErrorText)
}
})
return container
}()
however now the error message is "Error Domain=NSCocoaErrorDomainCode=134140 'Persistent store migration failed, missing mapping model.'"
Your crash sounds like it's happening because your data model changed, and any existing data that exists on a device can't be mapped 1:1 to the new model. This is where data mapping and migration comes in. Apple's docs are a good place to start. The very high-level overview is:
Open your xcdatamodel or xcdatamodeld file in Xcode, then select "Add Model Version...".
If necessary, save your model as an xcdatamodeld file. (The d suffix indicates that it's versioned.)
Note that your xcdatamodeld file is really a folder, and it includes multiple xcdatamodel files. All versions after the first one will include a numeric version number in the filename.
Make your model changes in the new model version.
If the changes are relatively simple (such as renaming an entity or adding or removing attributes), then it's considered a lightweight migration, and core data can figure out how to do the migration itself.
If your changes are more involved (such as splitting an entity type into two), then you must create a mapping model.
Add the code to perform the migration at startup time.
Obviously there's a lot more to this process, but this may give you a decent start.

Resources