best way to rename a class in Realm swift - ios

I use a "common" library in my iOS project. This library creates a Realm database. So far, I've been using this library on only iOS projects. I want to now use that same library with a macOS project. It's Foundation based, and doesn't use UIKit, so why not?
Here's the problem: I have a Realm class named Collection
Collection is also the name of a standard Swift protocol.
While I've been able to get away with this name collision on my iOS project, for some reason, I can't do the same on my MacOS project -- it creates a name-collection.
I read about this notation that can be used like this:
#objc(SpecialCollection)
class Collection: Realm.Object {
let items: List<ItemObject>
let name: String
let url: String
....
}
So, this solves the name-collision problem. In ObjC, the name will be something different, but in Swift, I don't need to change anything.
This is all well and good except for my local Realm database. I have a lot of Collection objects that should be renamed to SpecialCollection (since Realm uses ObjC underneath Swift). I'd like to perform a migration to do this, but apparently there isn't a supported way to do this yet? I noticed tickets on github about this issue being "watched", but unfortunately, there still exists no published solution to fix this problem.
All of my Collection objects contain List objects (hence the name). So, I tried to run an enumeration on all of the Collection objects in a migration... I would just take the older object, and create a new object with the new name, like this:
migration.enumerateObjects(ofType: "Collection", { (oldObject, _) in
migration.create("SpecialCollection", value: oldObject)
}
But since oldObject has a list of other objects, Realm's migration will try and create all the items in any List objects... which can't be done, because it creates objects with the same primaryKey value (causing a crash).
So, I can't keep the old name (Collection), and I can't convert to the new name, and I can't just trash the user's data. So, I'm truly at an impasse.
Blockquote
I tried to modify oldObject before creating the new object, but you can't change oldObject in a migration.
The only rule is that the old data has to be preserved, I can't just destroy the user's realm here.
Thanks for any help in this. It is greatly appreciated.

I had a very similar problem last night. I had a couple Realm classes I wanted to rename, and where one of them had a List property referring to the second class. So the only difference compared to your problem is I was renaming ItemObject class as well.
So here's how I did it:
Migrate your Collection class first, creating SpecialCollection.
While migrating, walk the Collection's list and create new
SpecialItemObject for each ItemObject and append it to the new list.
Delete each ItemObject.
Now enumerate all ItemObject remaining
in the realm and create a new SpecialItemObject and map its values
over. The reason is there may be other ItemObject floating around
in your realm, not tied to the list.
Delete all remaining ItemObject.
migration.enumerateObjects(ofType: "Collection")
{ (oldObject, newObject) in
let specialCollection = migration.create(SpecialCollection.className())
specialCollection["name"] = oldObject!["name"]
specialCollection["url"] = oldObject!["url"]
if let oldItems = oldObject!["items"] as? List<MigrationObject>,
let newItems = specialCollection["items"] as? List<MigrationObject>
{
for oldItem in oldItems
{
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"] // You didn't specify what was in your ItemObject so this example just assumes a name property.
newItems.append(newItem)
migration.delete(oldItem)
}
}
}
migration.deleteData(forType: "Collection")
// Now migrate any remaining ItemObject objects that were not part of a Collection.
migration.enumerateObjects(ofType: "ItemObject")
{ (oldObject, newObject) in
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"]
}
// Now let's be sure we'll have no further ItemObject in our entire Realm.
migration.deleteData(forType: "ItemObject")
So this is how I solved it for myself last night, after finding next to nothing about most of this in cocoa-realm in GitHub or on SO or elsewhere. The above example only differs from what you asked in that you weren't asking to rename your ItemObject class. You could try just creating new ItemObject objects and mapping the properties across in the same way I show in my example. I don't see why it wouldn't work. I've provided my example exactly how I solved my own problem, since I tested some migrations last night to prove it was solid.
Since your question is almost 5 months old, I'm really just posting this answer for posterity. Hope this helps someone!
Tested with Realm 3.3.2 on iOS 11.3 sim / Xcode 9.3 / Swift 4.1

Related

Can I make realm.writes directly in the Object data model?

I'm writing an app using Realm data persistence of certain objects.
In an attempt to clean up/remodel my code (getting realm.writes out of the Views and Controllers), I tried to put them directly in the persisted object class.
The logic is basically this:
class PersistedObject: Object {
public var data: String {
get { _saved_data }
set {
do { try realm?.write { _saved_data = newValue }
} catch { print(error) }
}
}
#objc dynamic private var _saved_data = "hello there"
}
This way, I'd be able to access and rewrite realm object properties from view controllers, without needing realm.writes directly in there. That's the idea, anyway.
This works sometimes. Other times, the app crashes with the error...
"Realm accessed from incorrect thread"
...which is what I'm currently trying to solve.
This is my first iOS app and my first time using Realm.
Does it make sense to organize the code like this (I've found little in terms of support in this approach, but also generally little at all, in terms of MVC best-practices when working with Realm)
If it does make sense, how can I solve the problem with accessing Realm from the incorrect thread, while still doing the realm.writes directly in the object class?
Thanks in advance! :)
Simon
There is no sense to organize code like this. You will be able to write only from same thread it was created
to modify objects from different thread you can use ThreadSafeReference for example
You're not going to want to do that.
There's no reason not to realm.write whenever you want to write to realm - that's what it's there for. This pattern works:
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Obviously there should be better error catching in the above code.
Another downside is if you want to write 10 objects, they are written as soon as the data property is set - what if there are three vars you want to set and heep in memory before writing it? e.g. your user is creating a list of items in your app - if the user decides not to do that and hit's Cancel, you would then have to hit the database again to delete the object(s).
Consider a case where you want to write 10 objects 'at the same time'?
realm.add([obj0, obj1, obj2...])
is a lot cleaner.
Another issue comes up if you want to guarantee objects are written within a transaction - either it all succeeds or all fails. That can't be done with your current object.
The last issue is that often you'll want to instantiate an object and add some data to it, populating the object before writing to realm. With the code in the question, you're writing it as soon as data is populated. You would have to add that same code to every property.

Copying all objects (preserving their relationships) from one Realm to another

tl;dr: I'm trying to copy every single object from one Realm to another, but I get twice as many objects or 4 times as many objects as I should (because of their relationships, I presume). (Edit: I actually get many many more! Check my last edit at the bottom.)
I'm trying to allow my users to backup and restore their Realm databases.
I have a Book class and a ReadingSession class. A Book can have many ReadingSessions:
class Book: Object {
// (…)
var readingSessions: [ReadingSession] {
return linkingObjects(ReadingSession.self, forProperty: "book")
}
}
class ReadingSession: Object {
// (…)
var book: Book?
}
To restore from the backup I tried doing this:
func restoreBackupFile(backupFileToRestore: String) {
// (…) I omitted the NSFileManager related part.
let config = Realm.Configuration(path: "\(tmp)/ReadingLog.realm", readOnly: true)
let backupRealm = try! Realm(configuration: config)
let defaultRealm = try! Realm()
let results = backupRealm.objects(Book)
try! defaultRealm.write {
for result in results {
defaultRealm.create(Book.self, value: result)
}
}
That copied all the Book objects alright, but not the ReadingSessions related to those Books.
So I added some code to copy every ReadingSession too:
// (…)
let bookResults = backupRealm.objects(Book)
let sessionResults = backupRealm.objects(ReadingSession)
try! defaultRealm.write {
for result in bookResults {
defaultRealm.create(Book.self, value: result)
}
for result in sessionResults {
defaultRealm.create(ReadingSession.self, value: result)
}
}
And that gave my defaultRealm 4 times as much books as it should! I used a database with 10 books to test it out, and after running that code my default Realm had 40 books, 20 with the right ReadingSessions associated to them and 20 without any ReadingSessions at all.
I tried copying just the ReadingSessions to see if the related Books would be created too, and then I got twice as many Books as I should, half of them with the right ReadingSessions and half of them without any.
So how can I do what I want? Copy every single object from a Realm to another keeping their relationships intact and without getting duplicates like I am now?
(I know I can just replace the database files, but I want my users to be able to restore data from one database without losing the data from the other.)
Thanks in advance,
Daniel
Edit: I've been experimenting some more and it seems that if I copy just the ReadingSessions it creates a book for each reading session copied, even if they're related to the same book. If I have a book with 60 reading sessions, for instance, that book will be created 60 times. So my problem is even worse than I thought. When I copy the Books they don't come with their related ReadingSessions. When I copy the ReadingSessions they create many repeated books.
When you create new objects on base of objects from another Realm, then linked objects are copied as well and recursed into. But backreferences are just convenience getters on your object and neither known to the schema nor accessible (for Swift), so they are not traversed at all. Even though linked objects are recursed into, they are not de-duplicated at all automatically. So this would loop for circular relations.
You might want to configure a primary key on Book and use create(… update: true) to copy your ReadingSessions. When you copy both, you ensure, that unread books are copied as well, if that's a valid case for your schema.

NSManagedObject Subclass can't be Loaded [duplicate]

I'm working on developing an app with Core Data. When I created an instance using:
let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: appDelegate.managedObjectContext)
let user = User(entity: entity, insertIntoManagedObjectContext: appDelegate.managedObjectContext)
I got a warning in log:
CoreData: warning: Unable to load class named 'User' for entity 'User'. Class not found, using default NSManagedObject instead.
How could I fix it?
And another question, how can I define an instance method in NSManagedObject subclass?
Edit:
I have specified class of the entity as in the following screenshot:
Update for Xcode 7 (final):
Prepending the module name to the class (as in Xcode 6 and early beta releases of Xcode 7) is no longer necessary.
The Apple documentation Implementing Core Data Managed Object Subclasses has been
updated accordingly.
The Data Model inspector
has now two fields "Class" and "Module" for an entity:
When you create a Swift managed object subclass for the entity, the
"Module" field is set to "Current Product Module", and with this setting
creating instances works both in the main application and in unit tests.
The managed object subclass must not be marked with #objc(classname) (this was observed in https://stackoverflow.com/a/31288029/1187415).
Alternatively, you can empty the "Module" field (it will show "None") and mark the
managed object subclasses with #objc(classname) (this was observed
in https://stackoverflow.com/a/31287260/1187415).
Remark: This answer was originally written for Xcode 6.
There were some changes in the various Xcode 7 beta releases with
respect to this problem. Since it is an accepted answer with many
upvotes and links to it, I have tried to summarize the situation
for the current Xcode 7 final version.
I did both my own "research" and read all the answers to both this question and the similar question
CoreData: warning: Unable to load class named. So attribution goes to all of them, even if I don't
list them specifically!
Previous answer for Xcode 6:
As documented in Implementing Core Data Managed Object Subclasses, you have to
prefix the entities class name in the Class field in the model entity inspector with the name of your module, for example "MyFirstSwiftApp.User".
Just as a side-note. i had the same issue. And all i had to do was add #objc(ClassName) in my class file.
Example:
#objc(Person)
class Person { }
And that solved my issue.
The accepted answer to this question helped me resolve the same issue but I had a caveat that I thought would be helpful to others. If your project (module) name has a space in it you must replace the space with an underscore. For example:
Entity: MyEntity
Class: My_App_Name.MyClass
Remember to remove your module:
Depending if you are running as App vs Tests the issue can be that the app is looking for <appName>.<entityName> and when it's running as test it's looking as <appName>Tests.<entityName>. The solution I use at this time (Xcode 6.1) is to NOT fill the Class field in the CoreData UI, and to do it in code instead.
This code will detect if you are running as App vs Tests and use the right module name and update the managedObjectClassName.
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional...
let modelURL = NSBundle.mainBundle().URLForResource("Streak", withExtension: "momd")!
let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL)!
// Check if we are running as test or not
let environment = NSProcessInfo.processInfo().environment as [String : AnyObject]
let isTest = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"
// Create the module name
let moduleName = (isTest) ? "StreakTests" : "Streak"
// Create a new managed object model with updated entity class names
var newEntities = [] as [NSEntityDescription]
for (_, entity) in enumerate(managedObjectModel.entities) {
let newEntity = entity.copy() as NSEntityDescription
newEntity.managedObjectClassName = "\(moduleName).\(entity.name)"
newEntities.append(newEntity)
}
let newManagedObjectModel = NSManagedObjectModel()
newManagedObjectModel.entities = newEntities
return newManagedObjectModel
}()
If you are using a hyphen in your project name like "My-App" then use an underscore instead of the hyphen like "My_App.MyManagedObject".
In general, look at the name of the xcdatamodeld file and use the same prefix as in that name.
I.e. "My_App_1.xcdatamodeld" requires the prefix "My_App_1"
This may help those experiencing the same problem. I was, with Swift 2 and Xcode 7 beta 2.
The solution in my case was to comment out #objc(EntityName) in EntityName.swift.
The above answers were helpful. This quick sanity check may save you some time. Go into Project > Build Phases > Compile Sources and remove your xcdatamodeld and your model files with the "-" button, and then add them right back with the "+" button. Rebuild -- that may take care of it.
I had the same warning, though my app appeared to run fine.
The problem was that when running Editor > Create NSManagedObject Subclass on the last screen I used the default Group location, with no Targets displayed or checked, which saved the subclass in the top MyApp directory where MyApp.xcodeproj was located.
The warning went away when I instead changed the Group to be in the MyApp subfolder and checked the MyApp target.
By the way be careful what you add as a prefix: My App is called "ABC-def" and Xcode has converted the "-" into a "_".
To be safe look into the finder, find your project files and see what it says for your data model (for example "ABC_def.xcdatamodeld") and use what is written there EXACTLY!!!
The above answers helped me to solve different issue connected with Objective-C (maybe it will help someone):
If you refactored entity names, don't forget to change "Class" in "Utilities Panel" as well.
The above answers helped me but this may help somebody. If like me you did them and are still having a problem, remember to simply 'clean your project'. For XCode8, Product > Clean. Then run again.
In Xcode 7 Entity and Class names can be the same but Codegen should be Class Definition. In that case there will be no warning etc.

Unable to find specific subclass of NSManagedObject

I'm working on developing an app with Core Data. When I created an instance using:
let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: appDelegate.managedObjectContext)
let user = User(entity: entity, insertIntoManagedObjectContext: appDelegate.managedObjectContext)
I got a warning in log:
CoreData: warning: Unable to load class named 'User' for entity 'User'. Class not found, using default NSManagedObject instead.
How could I fix it?
And another question, how can I define an instance method in NSManagedObject subclass?
Edit:
I have specified class of the entity as in the following screenshot:
Update for Xcode 7 (final):
Prepending the module name to the class (as in Xcode 6 and early beta releases of Xcode 7) is no longer necessary.
The Apple documentation Implementing Core Data Managed Object Subclasses has been
updated accordingly.
The Data Model inspector
has now two fields "Class" and "Module" for an entity:
When you create a Swift managed object subclass for the entity, the
"Module" field is set to "Current Product Module", and with this setting
creating instances works both in the main application and in unit tests.
The managed object subclass must not be marked with #objc(classname) (this was observed in https://stackoverflow.com/a/31288029/1187415).
Alternatively, you can empty the "Module" field (it will show "None") and mark the
managed object subclasses with #objc(classname) (this was observed
in https://stackoverflow.com/a/31287260/1187415).
Remark: This answer was originally written for Xcode 6.
There were some changes in the various Xcode 7 beta releases with
respect to this problem. Since it is an accepted answer with many
upvotes and links to it, I have tried to summarize the situation
for the current Xcode 7 final version.
I did both my own "research" and read all the answers to both this question and the similar question
CoreData: warning: Unable to load class named. So attribution goes to all of them, even if I don't
list them specifically!
Previous answer for Xcode 6:
As documented in Implementing Core Data Managed Object Subclasses, you have to
prefix the entities class name in the Class field in the model entity inspector with the name of your module, for example "MyFirstSwiftApp.User".
Just as a side-note. i had the same issue. And all i had to do was add #objc(ClassName) in my class file.
Example:
#objc(Person)
class Person { }
And that solved my issue.
The accepted answer to this question helped me resolve the same issue but I had a caveat that I thought would be helpful to others. If your project (module) name has a space in it you must replace the space with an underscore. For example:
Entity: MyEntity
Class: My_App_Name.MyClass
Remember to remove your module:
Depending if you are running as App vs Tests the issue can be that the app is looking for <appName>.<entityName> and when it's running as test it's looking as <appName>Tests.<entityName>. The solution I use at this time (Xcode 6.1) is to NOT fill the Class field in the CoreData UI, and to do it in code instead.
This code will detect if you are running as App vs Tests and use the right module name and update the managedObjectClassName.
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional...
let modelURL = NSBundle.mainBundle().URLForResource("Streak", withExtension: "momd")!
let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL)!
// Check if we are running as test or not
let environment = NSProcessInfo.processInfo().environment as [String : AnyObject]
let isTest = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"
// Create the module name
let moduleName = (isTest) ? "StreakTests" : "Streak"
// Create a new managed object model with updated entity class names
var newEntities = [] as [NSEntityDescription]
for (_, entity) in enumerate(managedObjectModel.entities) {
let newEntity = entity.copy() as NSEntityDescription
newEntity.managedObjectClassName = "\(moduleName).\(entity.name)"
newEntities.append(newEntity)
}
let newManagedObjectModel = NSManagedObjectModel()
newManagedObjectModel.entities = newEntities
return newManagedObjectModel
}()
If you are using a hyphen in your project name like "My-App" then use an underscore instead of the hyphen like "My_App.MyManagedObject".
In general, look at the name of the xcdatamodeld file and use the same prefix as in that name.
I.e. "My_App_1.xcdatamodeld" requires the prefix "My_App_1"
This may help those experiencing the same problem. I was, with Swift 2 and Xcode 7 beta 2.
The solution in my case was to comment out #objc(EntityName) in EntityName.swift.
The above answers were helpful. This quick sanity check may save you some time. Go into Project > Build Phases > Compile Sources and remove your xcdatamodeld and your model files with the "-" button, and then add them right back with the "+" button. Rebuild -- that may take care of it.
I had the same warning, though my app appeared to run fine.
The problem was that when running Editor > Create NSManagedObject Subclass on the last screen I used the default Group location, with no Targets displayed or checked, which saved the subclass in the top MyApp directory where MyApp.xcodeproj was located.
The warning went away when I instead changed the Group to be in the MyApp subfolder and checked the MyApp target.
By the way be careful what you add as a prefix: My App is called "ABC-def" and Xcode has converted the "-" into a "_".
To be safe look into the finder, find your project files and see what it says for your data model (for example "ABC_def.xcdatamodeld") and use what is written there EXACTLY!!!
The above answers helped me to solve different issue connected with Objective-C (maybe it will help someone):
If you refactored entity names, don't forget to change "Class" in "Utilities Panel" as well.
The above answers helped me but this may help somebody. If like me you did them and are still having a problem, remember to simply 'clean your project'. For XCode8, Product > Clean. Then run again.
In Xcode 7 Entity and Class names can be the same but Codegen should be Class Definition. In that case there will be no warning etc.

How to add unique constraints for some fields in Core Data

I use Xcode for iOS development. I have some entity (for example, User), and I need to set unique constraint for his name, but I can't find how I can do it through visual editor. Is it possible to do it through GUI? Or it's possible through code only? I will be glad to get some screenshot.
There's a new section in the sidebar when selecting an entity in the editor for Core Data. You can set what constraint(s) you want to be unique across all instances of an entity
For automatic conflict resolution during saves, you'll need to make sure you've got a merge policy set for your managed object context or else you'll just get errors when saving (which might actually be what you want)
[managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
The "Swift version" is exactly the same
managedObjectContext.mergePolicy = .mergeByPropertyObjectTrumpMergePolicyType
Keep in mind conflict resolution only happens during saves, and not inserts. So if you're making use of a NSFetchedResultsController you will see entities with non-unique constraints as they're inserted.
If you want to sure you have no entities with non-unique constraints in your managed object context without saving (if you're making use of a FRC), this answer is still probably the best way to go. Although, keep in mind, it's expensive if you're doing a lot of inserts, since NSFetchRequests are expensive operations.
Sample code for this demo can be found here
Swift solution:
As noted in the other answer, you can have unique constraints in Core Data for iOS9 onwards.
To do this, first add constraints to the Entity from the Core Data Editor (explaination in Zachary's answer).
Then add this line in code:
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Note: The uniqueness is checked when you do managedObjectContext.save() not while you're just adding objects to the managed object.
NSMergeByPropertyObjectTrumpMergePolicy is just one of the merge policies, which overwrites the old value with the new one. You might want to check the documentation for other options.
swift Version Is Easy
Disclaimer : If You Have Conflicting information please delete piror to implementation. Else App will not run.
Solution Delete from device and start again
steps are:
Open Core Data File (projectname.xcdatamodeld)
Click on the entity name (needs to be highlighted)
Right Side of screen (in class section) find constraints (hit the plus button)
Right click to edit info rename to Attribute.
// now to add the code in you core data container
open AppDelegate.swift file and scroll into the coredata stack (" // MARK: - Core Data Saving support ")
update the code for the static func saveContext() { let variable = persistentContainer.viewContext "
//now make this simple call that manages update process
variable".mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy"
// clear understanding
static func saveContext () {
let context = persistentContainer.viewContext
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// you need that line
if context.hasChanges {
do {
try context.save()
} catch {

Resources