I have been trying to abstract a little bit my Realm models, so I wanted to create a superclass model with a generic type. Then, create subclasses for the specific type.
I know Realm does not support storing a generic type. However, my question is:
Is it possible to store a specific defined subclass?
So the idea is to have a superclass:
class SuperClass<T: RealmCollectionValue> : Object {
#objc dynamic var pk: String = ""
let listProp = List<T>()
convenience init(pk: String){
self.init()
self.pk = pk
}
override static func primaryKey() -> String? {
return "pk"
}
}
Then implement a specific subclass:
class StringSubClass : SuperClass<String> {}
Is then the possibility to save in Realm instances of StringSubClass (as in fact, it is not a generic)?
From what I have found in this already answered question (which stands it is possible with an accepted answer): Store concrete generic subclass in Realm
You can specify the Realm to ignore the SuperClass by defining it to only handle the StringSubClass.
However, when trying it in Realm 3.13.1 with:
let realm = try! Realm(configuration: Realm.Configuration(objectTypes: [StringSubClass.self]))
try! realm.write {
realm.add(StringSubClass(pk: "mypk"))
}
The following exception is thrown when storing instances of StringSubClass
Terminating app due to uncaught exception 'RLMException', reason: 'Object type 'StringSubClass' is not managed by the Realm. If using a custom `objectClasses` / `objectTypes` array in your configuration, add `StringSubClass` to the list of `objectClasses` / `objectTypes`.'
Related
Consider the following:
protocol P {
init(data: Data) throws
}
enum PError: Error {
case invalidData
}
final class O: Object, P {
#Persisted var name: String
init(data: Data) throws {
guard let name = String(data: data, encoding: .utf8) else {
throw PError.invalidData
}
self.name = name
super.init()
}
}
O inherits from Realm Object and conforms to P. To satisfy the requirements of P, O implements a designated initializer.
https://github.com/realm/realm-swift/issues/4889#issuecomment-296301901 quotes some docs that no longer exist (or have moved) which stated:
When creating your model Object subclasses, you may sometimes want to add your own custom initialization methods for added convenience.
Due to some present limitations with Swift introspection, these methods cannot be designated initializers for the class. Instead, they need to be marked as convenience initializers
Did this change at some point, possibly as a result of https://github.com/realm/realm-swift/pull/6294?
While it's possible to satisfy P's requirement with a convenience initializer, I'm interested to learn whether it's strictly necessary or whether a designated initializer is okay as well.
Some context first:
I am building a generic API for my CoreData Database. All Objects in my model live in pairs:
An NSManagedObject class that is stored in CoreData and can be converted into an NSObject with a protocol called ManagedObjectProtocol
An NSObject class that is actually used throughout my app and can be converted into an NSManagedObject with a protocol called DataObject
My ManagedObject Protocol
//MANAGED OBJECT PROTOCOL - Should be adhered to by all NSManagedObject classes
protocol ManagedObjectProtocol where Self: NSManagedObject {
//var managedObjectID: NSManagedObjectID { get set }
func populateRegularObject() -> DataObject
func populateRegularObjectFromRelated<T: TypeErasedDataObject>(relatedObject: T, at key: String) -> DataObject
}
In my API, I load the objects as follows:
let managedObject = API.shared.persistentContainer.newBackgroundContext().object(with: someObjectID) as! ManagedObjectProtocol
let toReturn = managedObject.populateRegulardObject() //<-- This Crashes
The problem:
This successfully loads my object. I should now be able to populate the DataObject that belongs to this ManagedObjectProtocol and use it in my app. But I can't because, apparently, typecasting to a Protocol loads the object differently than when I TypeCast it as a normal NSManagedObject. Immediately when I access a property of the loaded ManagedObject, my app crashes with error EXC_BAD_ACCESS.
Question:
How can I access my NSManagedObject's properties when I need to typecast it to a protocol?
To me, it would make sense to be able to do something like this:
extension NSManagedObject where Self: ManagedObjectProtocol {
func populateDataObject() -> DataObject
}
But this can't be done in swift. Can anyone suggest a solution? Any help would be highly appreciated.
The following post will help you better understand the issue
https://www.lesstroud.com/dynamic-dispatch-with-nsmanaged-in-swift/
Essentially, it seems that core data is unable to handle protocols which are unmanaged. It seems like core data rewrites the class definition to pass #NSManaged through proxy methods, but is unable to do so for protocols.
Adding the dynamic keyword to your property declaration will solve this issue.
I'm building a generic API for my Swift applications. I use CoreData for local storage and CloudKit for cloud synchronization.
in order to be able to work with my data objects in generic functions I have organized them as follows (brief summary):
Objects that go in the CoreData Database are NSManagedObject instances that conform to a protocol called ManagedObjectProtocol, which enables conversion to DataObject instances
NSManagedObjects that need to be cloud synced conform to a protocol called CloudObject which allows populating objects from records and vice-versa
Objects I use in the graphic layer of my apps are NSObject classes that conform to the DataObject protocol which allows for conversion to NSManagedObject instances
an object of a specific class. What I would like this code to look like is this:
for record in records {
let context = self.persistentContainer.newBackgroundContext()
//classForEntityName is a function in a custom extension that returns an NSManagedObject for the entityName provided.
//I assume here that recordType == entityName
if let managed = self.persistentContainer.classForEntityName(record!.recordType) {
if let cloud = managed as? CloudObject {
cloud.populateManagedObject(from: record!, in: context)
}
}
}
However, this gives me several errors:
Protocol 'CloudObject' can only be used as a generic constraint because it has Self or associated type requirements
Member 'populateManagedObject' cannot be used on value of protocol type 'CloudObject'; use a generic constraint instead
The CloudObject protocol looks as follows:
protocol CloudObject {
associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
var recordID: CKRecordID? { get }
var recordType: String { get }
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudManagedObject>
func populateCKRecord() -> CKRecord
}
Somehow I need to find a way that allows me to get the specific class conforming to CloudObject based on the recordType I receive. How would I Best go about this?
Any help would be much appreciated!
As the data formats of CoreData and CloudKit are not related you need a way to efficiently identify CoreData objects from a CloudKit record and vice versa.
My suggestion is to use the same name for CloudKit record type and CoreData entity and to use a custom record name (string) with format <Entity>.<identifer>. Entity is the record type / class name and identifier is a CoreData attribute with unique values. For example if there are two entities named Person and Event the record name is "Person.JohnDoe" or "Event.E71F87E3-E381-409E-9732-7E670D2DC11C". If there are CoreData relationships add more dot separated components to identify those
For convenience you could use a helper enum Entity to create the appropriate entity from a record
enum Entity : String {
case person = "Person"
case event = "Event"
init?(record : CKRecord) {
let components = record.recordID.recordName.components(separatedBy: ".")
self.init(rawValue: components.first!)
}
}
and an extension of CKRecord to create a record for specific record type from a Entity (in my example CloudManager is a singleton to manage the CloudKit stuff e.g. the zones)
extension CKRecord {
convenience init(entity : Entity) {
self.init(recordType: entity.rawValue, zoneID: CloudManager.shared.zoneID)
}
convenience init(entity : Entity, recordID : CKRecordID) {
self.init(recordType: entity.rawValue, recordID: recordID)
}
}
When you receive Cloud records extract the entity and the unique identifier. Then try to fetch the corresponding CoreData object. If the object exists update it, if not create a new one. On the other hand create a new record from a CoreData object with the unique record name. Your CloudObject protocol widely fits this pattern, the associated type is not needed (by the way deleting it gets rid of the error) but add a requirement recordName
var recordName : String { get set }
and an extension to get the recordID from the record name
extension CloudObject where Self : NSManagedObject {
var recordID : CKRecordID {
return CKRecordID(recordName: self.recordName, zoneID: CloudManager.shared.zoneID)
}
}
Swift is not Java, Swift is like C++, associatedType is a way of writing a generic protocol, and generics in Swift means C++ template.
In Java, ArrayList<String> is the same type as ArrayList<Integer>!!
In Swift (and C++) , Array<String> is NOT the same type as Array<Int>
So, you can't take an array of Arrays for example, you MUST make it an array of Array<SpecificType>
What did Apple do to make you able to make a "type-erased" array for example?
They made Array<T> extend Array<Any>.
If you want to immitate this in your code, how?
protocol CloudObject {
// Omitted the associatedtype (like you already done as in the replies)
//associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
var recordID: CKRecordID? { get }
var recordType: String { get }
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<NSManagedObject & ManagedObjectProtocol>
func populateCKRecord() -> CKRecord
}
Then make the "generic protocol", this would be useful in safely and performance programming when the resolving of protocol is known at compile time
protocol CloudObjectGeneric: CloudObject {
// Generify it
associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
// You don't need to redefine those, those are not changed in generic form
//var recordID: CKRecordID? { get }
//var recordType: String { get }
//func populateCKRecord() -> CKRecord
// You need a new function, which is the generic one
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject>
}
Then make the generic protocol conform to the non-generic one, not to need writing 2 populateManagedObject functions in each implementation
extension CloudObjectGeneric {
// Function like this if the generic was a parameter, would be
// straightforward, just pass it with a cast to indicate you
// are NOT CALLING THE SAME FUNCTION, you are calling it from
// the generic one, but here the generic is in the return, so
// you will need a cast in the result.
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject> {
let generic = populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext)
return generic as! Promise<CloudObject> // In Promises I think this
// will NOT work, and you need .map({$0 as! CloudObject})
}
}
I made a protocol called Synchronizable and I have some of my Realm objects conformed to this protocol.
Such as :
class Comment : Object, Synchronizable {
// ...
}
I want to query all of local Realm objects, and retrieve all objects that are conformed to this protocol.
So I did something like :
func getObjectsToSynchronize() -> [Object]{
Array(realm.objects(Object.self)).filter({
if let $0 = $0 as? Synchronizable {
return true
}
return false
})
}
With Object the Realm Object type, but this type object by default doesn't implements my protocol, so I can't detect if the object is conform to the protocol Synchronizable, it says :
Expression pattern of type 'Object' cannot match values of type 'Synchronizable?'
Is there a way to do this ? I think I'm on the wrong way by querying in the Realm object.
Wrong way?
Maybe I have to create another Realm class object called SynchronizableObjectthat inherits from Object realm class.
This class will have a relationship to Realm objects that I want to be Synchronizable, such as :
class SynchronizableObject : Object {
// MARK: - Realm Relationships
dynamic var synchronizables : List<Object>()
}
And then I should query this class with the Realm object
#AnthonyR you can't query/filter directly RealmSwift.Object, you must inherit your model from RealmSwift.Object as you'v done
class Comment : Object, Synchronizable {
// ...
}
Then you can query/filter all objects of inherited type:
func getObjectsToSynchronize() -> Results<Comment> {
return realm.objects(Comment.self)
}
If you explain a little more what do you want to do may be I can help you
I have subclass of NSObject and I can set value of my subclass with:
performSelector(onMainThread: Selector("setNameOfProperty:"), with: "myName", waitUntilDone: true)
Actually, I did not write any method named setMethodProperty of course.
So here is my simple class:
class Post: NSObject {
var name: String?
var statusText: String?
var profileImageName: String?
var statusImageName: String?
var numLikes: NSNumber?
var numComments: NSNumber?
var location: Location?
override func setValue(_ value: Any?, forKey key: String) {
if key == "location" {
location = Location()
location?.setValuesForKeys(value as! [String: AnyObject])
} else {
super.setValue(value, forKey: key)
}
}
}
A then from another class I just invoke method performSelector:
let samplePost = Post()
samplePost.performSelector(onMainThread: Selector("setStatusText:"), with: "myName", waitUntilDone: true)
I am looking for any information about that interesting thing, but I couldn't. Maybe someone has the link about it or just know what is this behavior. If you can write about it to clarify situation.
Read more about key-value coding in About Key-Value Coding, specifically:
Objects typically adopt key-value coding when they inherit from NSObject (directly or indirectly), which both adopts the NSKeyValueCoding protocol and provides a default implementation for the essential methods. Such an object enables other objects, through a compact messaging interface, to do the following:
Access object properties.
The protocol specifies methods, such as the generic getter valueForKey: and the generic setter setValue:forKey:, for accessing object properties by their name, or key, parameterized as a string. The default implementation of these and related methods use the key to locate and interact with the underlying data, as described in Accessing Object Properties.
By subclassing NSObject, Post class implements NSKeyValueCoding.
Basically it means that the properties defined in Post generate corresponding getter and setter methods, which means that they can be accessed using performSelector. This Key-Value coding allows you to perform selectors for getting or setting even properties which names you don't know during compilation - selector can be created from a string variable.
In case you will decide to migrate the project to Swift 4, note that you will have to either mark each property that you want to access this way using #objc, or use #objcMembers annotation on the whole class.