Setting an NSManagedObject relationship in Swift - ios

How does one add an object to a relationship property in an NSManagedObject subclass in Swift?
In Objective-C, when you generate an NSManagedObject subclass in Xcode from the data model, there's an automatically generated class extension which contains declarations like:
#interface MyManagedObject (CoreDataGeneratedAccessors)
- (void)addMySubObject: (MyRelationshipObject *)value;
- (void)addMySubObjects: (NSSet *)values;
#end
However Xcode currently lacks this class generation capability for Swift classes.
If I try and call equivalent methods directly on the Swift object:
myObject.addSubObject(subObject)
...I get a compiler error on the method call, because these generated accessors are not visible.
I've declared the relationship property as #NSManaged, as described in the documentation.
Or do I have to revert to Objective-C objects for data models with relationships?

As of Xcode 7 and Swift 2.0 (see release note #17583057), you are able to just add the following definitions to the generated extension file:
extension PersonModel {
// This is what got generated by core data
#NSManaged var name: String?
#NSManaged var hairColor: NSNumber?
#NSManaged var parents: NSSet?
// This is what I manually added
#NSManaged func addParentsObject(value: ParentModel)
#NSManaged func removeParentsObject(value: ParentModel)
#NSManaged func addParents(value: Set<ParentModel>)
#NSManaged func removeParents(value: Set<ParentModel>)
}
This works because
The NSManaged attribute can be used with methods as well as
properties, for access to Core Data’s automatically generated
Key-Value-Coding-compliant to-many accessors.
Adding this definition will allow you to add items to your collections. Not sure why these aren't just generated automatically...

Yeah that's not going to work anymore, Swift cannot generate accessors at runtime in this way, it would break the type system.
What you have to do is use the key paths:
var manyRelation = myObject.valueForKeyPath("subObjects") as NSMutableSet
manyRelation.addObject(subObject)
/* (Not tested) */

Core Data in Objective C automatically creates setter methods (1):
By default, Core Data dynamically creates efficient public and primitive get and set accessor methods for modeled properties (attributes and relationships) of managed object classes. This includes the key-value coding mutable proxy methods such as addObject: and removes:, as detailed in the documentation for mutableSetValueForKey:—managed objects are effectively mutable proxies for all their to-many relationships.
As things currently stand with Swift in Xcode6-Beta2, you'd have to implement those accessors yourself. For example if you have an unordered to-many relationship, from Way to Node, you'd implement addNodesObject like this:
class Way : NSManagedObject {
#NSManaged var nodes : NSSet
func addNodesObject(value: Node) {
self.mutableSetValueForKey("nodes").addObject(value)
}
}
Key here is that you'd have to use mutableSetValueForKey / mutableOrderedSetValueForKey / mutableArrayValueForKey. On these sets / arrays, you can call addObject and they'll be stored on the next flush.

You can just use a typed Set instead which is far easier. Following the example provided by #Nycen and #lehn0058 in the previous answer, you can just write:
extension PersonModel {
#NSManaged var parents: Set<ParentModel>?
}
And then use the insert and remove methods of the Set.

Expanding on the solution above one to many relationships are NSMutableSet so this allows you to directly add or remove the Person NSManagedObject to the Roles in this case a Person has one Role and Roles have many Person(s)
I have tested this solution under Xcode Beta-3 and this works!
This code takes out the Department to simplify showing the one to one and one to many code required to access Roles from a Person and Persons from a Role.
import CoreData
#objc(Person) class Person: NSManagedObject {
#NSManaged var name: String
//One to One relationship in your Model
#NSManaged var roles: Roles
}
#objc(Roles) class Roles: NSManagedObject {
#NSManaged var role: String
//One to Many relationship in your Model
#NSManaged var persons: NSMutableSet
}
extension Roles {
func addPersonsObject(value: Person) {
self.persons.addObject(value)
}
func removePersonsObject(value: Person) {
self.persons.removeObject(value)
}
func addPersons(values: [Person]) {
self.persons.addObjectsFromArray(values)
}
func removePersons(values: [Person]) {
for person in values as [Person] {
self.removePersonsObject(person)
}
}
}

As of Xcode 8 and Swift 3.0, Xcode now generates accessors for relationships. For example, I have an NSManagedObject class Store, that has a one to many relationship with Items; I've called that relationship SellsItems. The generated class for Store now has the following extension to add and remove from SellsItems. Adding or removing items to the relationship is as simple as calling these functions.
// MARK: Generated accessors for sellsItems
extension Store {
#objc(addSellsItemsObject:)
#NSManaged public func addToSellsItems(_ value: Item)
#objc(removeSellsItemsObject:)
#NSManaged public func removeFromSellsItems(_ value: Item)
#objc(addSellsItems:)
#NSManaged public func addToSellsItems(_ values: NSSet)
#objc(removeSellsItems:)
#NSManaged public func removeFromSellsItems(_ values: NSSet)
}

As you only need to set one side of a relationship for both to be set nowadays, it's particularly simple if you have a 1<->many relationship, e.g. a Department object has multiple Person objects, then you can just use:
aPerson.department = aDepartment
If you check you'll find that aDepartment.people (assuming that is the reciprocal relationship you've set up) will now contain the 'aPerson' Person object.
If the relationship is many<->many then one of the more complex solutions above appears necessary.

Let's say you have the following entities:
Person
Role
Department
In your Person entity, they have a to-many relationship with Role and to-one with Department. Your managed object might look something like this:
class Person : NSManagedObject
{
#NSManaged var roles : Array<Role>
#NSManaged var department : Department
}
Relationships with inverses (all should have them) only require one side to be set for the link to be established.
For example, if you set a Person's department property to a Department object, the inverse Department.people property would now also have this Person object contained inside.

Related

App is terminating when trying to get an object value

My homework is to get a JSON array and save it to CoreData and then fetch it to a UITableView.
This is my first time working with CoreData and have already saved objects in CoreData, but when fetching I couldn't get an Object from the class I defined but instead I got [NSManagedObject].
So I added #NSManaged on every field in my defined class, but then the app terminates.
Movie.swift
import Foundation
import CoreData
class Movie :NSObject, Codable{
#NSManaged var title:String
#NSManaged var image:String
#NSManaged var rating:Double
#NSManaged var releaseYear:Int
#NSManaged var genre:[String]
init(title:String, image:String, rating:Double, releaseYear:Int, genres:[String]) {
super.init()
self.title = title
self.image = image
self.rating = rating
self.releaseYear = releaseYear
self.genre = genres
}
var title1:String{
get{
return title
}
}
}
The main problem is supposed to be in Movie.swift, I looked it up online and I couldn't find much that could help.
Could it be that I need to add NSManagedObject instead of NSObject?
Some issues
A class representing a Core Data entity must be a subclass of NSManagedObject. Let Xcode create the subclass(es) on your behalf.
A custom init method cannot be used unless it's a convenience initializer calling init(entity:insertInto:) or other designated initializers.
It's not trivial to implement Codable. Please see How to use swift 4 Codable in Core Data
For further information please read the Core Data Programming Guide
If you have a core data model, Xcode can generate the model class for you. Open the xcdatamodel file and in the menu bar, go to Editor -> Create NSManagedObject Subclass.

ios swift models structure with realm database

I am developing my iOs App and I am using Realm database. As I am totally new to ios developing (also swift and xcode) I have question about structuring data (I've already read some general project structure guidelines but couldn't find the answer). My thinking is connected with Java structures
For Realm databases (RealmSiwft) I created a model like this:
#objcMembers class Patient: Object {
dynamic var patientId:Int = 0
dynamic var refNumber:String = ""
convenience init(id:Int, refNumber:String){
self.init()
self.patinetID = id
self.refNumber = refNumber
}
}
Now, it looks just like a POJO class in Java. But as I learned, this model structure is made that way so it can be able to use Realm.
So the question is, if I need somewhere else in my project to use Patient objects, is this Realm-POJO-model good to use? I mean, should I use it just like a normal Model even when I dont need to make database operations on it? Or should I make this Realm model alike DAO class for databases operations and make another model class like Patient.swift for whenever I want to play with Patient without using databases (I hope not, cause it's so much code duplicating)
And what if I need variables in that Patient Model that won't be stored in database? Can I make it without dynamic? What about init than? That blows my mind, as far as I learn swift it seems so ugly and unstructured, or I just can't switch to it yet...
if I need somewhere else in my project to use Patient objects, is this
Realm-POJO-model good to use?
even when I dont need to make database operations on it?
You can use your Patient object without savings to the DB, move them to different controllers and so on.
what if I need variables in that Patient Model that won't be stored
in database?
Look to ignoredProperties() method.
Can I make it without dynamic?
No you can't because of Realm based on Objective-C object, so this is necessary type.
What about init than?
You can create different Constructors methods, look to the Initialization doc. In case with Realm you should setup values to noticed variables (if you don't give them Default Property Values)
Your class should look like this:
class Patient: Object {
// MARK: - Properties
#objc dynamic var patientId: Int = 0
#objc dynamic var refNumber: String = ""
// MARK: - Meta
// to set the model’s primary key
override class func primaryKey() -> String? {
return "patientId"
}
//Ignoring properties
override static func ignoredProperties() -> [String] {
return ["tmpID"]
}
//It's ok
convenience init(id:Int, refNumber:String){
self.init()
self.patientId = id
self.refNumber = refNumber
}
}
All other detail information you can find in: realm docs
Also you can extend you base code with swift extension:
extension Patient {
var info: String {
return "\(patientId) " + refNumber
}
func isAvailableRefNumber() -> Bool {
return refNumber.length > 6
}
}

Save EVObjects with CoreData

I need to save some data with CoreData. Generally thats not a problem at all. The problem is, that the data is created with EVReflection an therefore inherits the class EVObject. To save the gathered data to CoreData they have to also inherit NSManagedObject. The problem is that swift does not allow you to inherit multiple classes. Would appreciate any tips.
class Device : EVObject
{
var channel : [Channel] = [Channel]()
var name : String = ""
var ise_id : Int = 0
var unreach : Bool = false
var sticky_unreach : Bool = false
var config_pending : Bool = false
override internal func propertyMapping() -> [(String?, String?)] {
return [("name", "_name"), ("ise_id", "_ise_id"), ("unreach", "_unreach"), ("sticky_unreach", "_sticky_unreach"), ("config_pending", "_config_pending")]
}
}
You don't have to inherit. You can extend them. Example:
class User : NSManagedObject{
#NSManaged .....
}
//Extension
import EVReflection
extension User : EVReflectable { }
https://github.com/evermeer/EVReflection#extending-existing-objects
Note I'm not aware of EVReflection but I think this answer can generally apply.
Don't use multiple inheritance. Have two separate classes and a mechanism for creating/loading/updating one object from the other. Protocols may allow it to be done in a way that minimises translation boilerplate (possibly using valueForKey(_:) and setValue(_:forKey) if you can know the key names in a safe manner.
It may not even be even be necessary to have an NSManagedObject subclass but just have an instance of NSManagedObject in all your classes that is loaded/created/saved as necessary.
It depends on what functionality you want to use from EVReflection. Since NSManagedObject also has NSObject as it's base class you could use most functions by just setting NSManagedObject as your base class instead of EVObject.
The only thing you have to do is instead of calling EVObject functions directly, you have to implement the code snippets that are in that EVObject method. Almost any function there is just a convenience method that will call the corresponding EVReflection function.
If you have any questions in the future, then please also report this as an issue on GitHub.

Core Data simple relationship - NSSet

I am trying to play with relationships in xCode and I'm having some problems:
I want to create a simple User to favoriteThing relationship.
I created a model, so every user looks like this:
extension User {
#NSManaged var name: String?
#NSManaged var favoriteThings: NSSet?
}
extension FavoriteThing {
#NSManaged var thingName: String?
#NSManaged var user: User?
}
And I got a tableview conected (so it displays favoriteThing for each user).
The thing is, when I create a new favoriteThing how to add this thing to favoriteThings NSSet that is created in User class?
And what about when I delete this favoriteThing from my table view? (it is managed by fetchedResultsController) How to delete it also from mentioned favoriteThings NSSet?
Any help appreciated!
In the case of a one-to-many relationship, the easiest way to
add an object to the to-many relationship is to set the property
of the inverse to-one relationship:
theFavoriteThing.user = theUser
This automatically updates theUser.favoriteThings to include
theFavoriteThing.
And to remove it just set the inverse to-one relationship to nil:
theFavoriteThing.user = nil

Core Data managed objects in swift - is nil allowed?

I have created NSManagedObject subclasses for my model objects in swift.
Typically my pattern is to create an instance of an object and then set properties on it, then save.
The new objects have properties that are set to nil. They are not optionals, though. I thought in swift this wasn't allowed?
A lot of times I need to check for values, but if I try something like:
if (managedObject.property == nil) I crash.
It seems Xcode doesn't automatically make managed vars optional when creating NSManagedObject subclasses. If the values are set as optional in the model, they should be optional in the subclass as well. (I set them as optional manually)
class ClassWithOptionalName: NSManagedObject {
#NSManaged var name: String?
}
Is managedObject.property optional value?
class CustomManagedObject: NSManagedObject {
#NSManaged var aProperty: String?
^
}

Resources