Swift 4, Xcode 9.2
Here are a few Realm classes I have:
class Dog: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
class Cat: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
class Horse: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
Let's say I have some code like this where I compare two realms and create an object with a specific Dog class:
let remoteDogs = remoteRealm.objects(Dog.self)
for remoteDog in remoteDogs{
if let localDog = realm.objects(Dog.self).filter("id = %#",remoteDog.id).first{
// Update
if localDog.updated < remoteDog.updated{
//Remote is newer; replace local with it
realm.create(Dog.self, value: remoteDog, update:true)
}
}
}
This works great, but I need to do this same stuff on a whole bunch of Realm classes that I have. So I'm trying to make it more generic like this:
let animals = [Dog.self, Cat.self, Horse.self]
for animal in animals{
let remoteAnimals = remoteRealm.objects(animal)
for remoteAnimal in remoteAnimals{
if let localAnimal = realm.objects(animal).filter("id = %#",remoteAnimal.id).first{
// Update
if localAnimal.updated < remoteAnimal.updated{
//Remote is newer; replace local with it
realm.create(animal, value: remoteAnimal, update:true)
}
}
}
}
This sort of works, but anytime I want to reference the property of an object (like with remoteAnimal.id and remoteAnimal.updated) then the compiler complains because it doesn't know what kind of object a remoteAnimal is.
Has anyone done something like this before? Any ideas how I can do this so that I don't have to write this same code over and over for each of my Realm classes? Thanks!
Realm Object does not have id or updated. You can have your Dog,
Cat and Horse classes inherit from an Animal class that is a subclass of Object and that does have id or updated. Since these properties are defined on Animal they will be usable in all of the subclasses (Dog, Cat, Horse).
class Animal: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var updated = Date()
//Other properties...
}
class Dog: Animal {
//Other properties...
}
EDIT you can also abuse Objective C's NSObject setValue:forKey: to set the property by name. This is very sloppy typing and not good object oriented design but it does work. Here is a playground:
import UIKit
class A: NSObject {
#objc var customProperty = 0
}
let a = A()
a.setValue(5, forKey: "customProperty")
print(a.customProperty)
Related
I set up a Realm model (in Swift) with a children relationship:
import Foundation
import RealmSwift
class MyObject1: RealmSwift.Object, RealmSwift.ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var childrenIDs = RealmSwift.List<String>()
}
I added the relationship as a RealmSwift.List of Strings, because I intent to create other model classes, each with an ID, that can be added as children. In other words, the children might not be of a single type/class. For example:
import Foundation
import RealmSwift
class MyObject2: RealmSwift.Object, RealmSwift.ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var title: String
}
Now at some point, I have to fetch all the children by their ID, for example to show them in a list. But I do not know how. I know the realm.objects(Class.self).filter but this expects a single class type. So my question is, how can I fetch objects from a Realm only by their ID (ie without their class/type)?
As mentioned in a comment, ObjectId's are generic and have no correlation to the object class they are tied to. So you can't do exactly what you want.
But... there are options.
TL;DR
create another object with properties to hold the id and the object type, defined by an enum or
Use AnyRealmValue to store the objects themselves
Long answers:
Long answer #1
Create a class to store the id and type. Suppose we have a database that stores wine grape types; some are white and some are red but you want to store them all in the same list. Let me set up an example
enum GrapeTypesEnum: String, PersistableEnum {
case white
case red
}
class Grape: Object {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var grape_name = ""
#Persisted var grape_type: GrapeTypesEnum
}
class GrapeObjects: Object { //contains the grape id string & grape type
#Persisted var child_id = ""
#Persisted var child_type: GrapeTypesEnum
}
so then the model would be
class MyObject1: RealmSwift.Object {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var childGrapes = RealmSwift.List<GrapeObjects>()
}
You could then create a couple of grapes
let whiteGrape = Grape()
whiteGrape.name = "Chenin Blanc"
whiteGrape.grape_type = .white
let redGrape = Grape()
redGrape.name = "Cabernet Franc"
redGrape.grape_type = .red
let grape0 = GrapeObjects()
grape0.grape_id = whiteGrape._id.stringValue()
grape0.grape_type = whiteGrape.grape_type
let grape1 = GrapeObjects()
grape1.grape_id = redGrape._id.stringValue()
grape1.grape_type = redGrape.grape_type
then finally store those objects in your class. Those can then be sorted, filtered and you will know which ID goes with what type of object
let anObject = MyObject1()
anObject.childGrapes.append(objectsIn: [grape0, grape1])
Long answer #2
Another option is to not store the objectID string but store the actual objects by leveraging AnyRealmValue
class MyClass: Object {
#Persisted var myList = List<AnyRealmValue>()
}
let red = WhiteGrape()
let white = RedGrape()
let obj0: AnyRealmValue = .object(red) //both of these are objects,
let obj1: AnyRealmValue = .object(white) // even through they are different objects
let m = MyClass()
m.myGrapeList.append(obj0)
m.myGrapeList.append(obj1)
Then you can take action based on the the objects type
for grape in m.myGrapeList {
if let white = grape.object(WhiteGrape.self) {
print("is a white grape")
} else if let red = grape.object(RedGrape.self) {
print("is a red grape")
}
}
Why not use a struct-based singleton?
I created decodable struct-based singleton.
struct Person: Decodable {
static var shared = Person()
private(set) var name: String?
var age: Int?
private init() {
}
mutating func initData(from data: Data) {
if let person = try? JSONDecoder().decode(Person.self, from: data) {
self = person
}
}
}
init from other class like this:
Person.shared.initData(from: data)
and use parameters:
let name = Person.shared.name
Person.shared.name = "MyName" //error
Person.shared.age = 20
Is this the wrong way?
You can't use a struct fora singleton because struct is a value type so when you assign it to a variable you get a copy. This can be easily shown
struct Singleton {
static var shared = Singleton()
var value: Int
private init() {
value = 0
}
}
Singleton.shared.value = 1
var otherSingleton = Singleton.shared
otherSingleton.value = 2
Now if we print the value of both
print(Singleton.shared.value, otherSingleton.value)
we get
1 2
So otherSingleton is clearly a separate instance so now we have 2 singletons :)
But if we simply change the type of Singleton to class that is a reference type and then run the same code the result of the print is
2 2
since it is the same instance we have changed the value property for.
Goal
To create an "AutoIDable" protocol with the following behaviour.
Every instance of a class conforming to this protocol will get an auto-generated "id" property of String type.
The code should generate id strings in the format <prefix><Instance-count-starting-from-1> (Eg: E-1, E-2, ...E-<n> and so on for 1st , 2nd ... nth Instance of the conforming class.
The protocol & protocol extensions should do ALL of the required work to generate the id strings. The conforming class will only have to subscribe to the protocol and nothing more.
Current status:
I have achieved Goal-1 & Goal-2 with the following implementation:
protocol Identifiable {
var id: String { get }
}
protocol AutoIDable: Identifiable{
static var _instanceCount: Int { get set }
}
class AutoID: AutoIDable {
init(idPrefix: String) {
setAutoID(prefix: idPrefix)
}
internal static var _instanceCount: Int = 0
var id: String = ""
func setAutoID(prefix: String = ""){
Self._instanceCount += 1
self.id = "\(prefix)\(Self._instanceCount)"
}
}
class Employee: AutoID {
init(){
super.init(idPrefix: "E-")
}
}
let e1 = Employee()
let e2 = Employee()
let e3 = Employee()
print(e1.id)
print(e2.id)
print(e3.id)
print(e1.id)
The output from running the above code:
E-1
E-2
E-3
E-1
Todo:
To achieve Goal-3, I need to eliminate the AutoID superclass and implement the same functionality using protocol extensions.
I ran into trouble because:
Protocol extensions do not allow static stored properties. I do know how to work around this limitation without using a superclass.
I do not know how to inject code into all the initialisers the creator of the Employee class might create. Again, I could not think of a workaround without using a superclass.
I would be grateful if you can point me in the right direction.
PS: New to Swift programming. If you’ve suggestions for implementing the code in a more “swifty” way, please do let me know. :-)
Since you want to use protocols, you can't have a stored property in the protocol. So, you'll need some place to store the incrementing ID value, if not the IDs themselves.
Not sure if it violates your requirements of using only protocols, because it would require a type for storage, but at least it won't require conforming classes to have a superclass.
So, let's say we build such a class that holds all the IDs and keeps the incrementing counter:
class AutoIncrementId {
static private var inc: Int = 0
static private var ids: [ObjectIdentifier: String] = [:]
static func getId(_ objectId: ObjectIdentifier, prefix: String) -> String {
if let id = ids[objectId] { return id }
else {
inc += 1
let id = "\(prefix)\(inc)"
ids[objectId] = id
return id
}
}
}
Then the protocol requirement could be:
protocol AutoIdentifiable {
static var prefix: String { get }
var id: String { get }
}
So, a class would need to define its prefix. But we could define a default implementation for id:
extension AutoIdentifiable where Self: AnyObject {
var id: String {
AutoIncrementId.getId(ObjectIdentifier(self), prefix: Self.prefix)
}
}
The usage would be:
class Employee: AutoIdentifiable {
static let prefix = "E-"
}
let e1 = Employee()
let e2 = Employee()
let e3 = Employee()
print(e1.id) // E-1
print(e2.id) // E-2
print(e3.id) // E-3
print(e1.id) // E-1
I would like to create a class with a static property that subclasses can override, which would be used to initialize instances. So far, I've tried to accomplish this like this:
import Cocoa
class A: NSObject {
class var staticProperty: String {
return "A"
}
var property: String = A.staticProperty
}
class B: A {
override class var staticProperty: String {
return "B"
}
}
This does not work, since B().property still returns "A". How could I change this code so that property contains the value specified by the subclass? Any help would be appreciated!
Edit
I would like to initialize property with the value of staticProperty, so this could also look like this:
var property: SomeClass = SomeClass(A.staticProperty)
But then, this initialization should still use "A" for class A, and "B" for class B.
Edit 2 (After #RakeshaShastri's comment)
For my specific use-case, I need property to be stored (so not computed) and non-lazy.
Edit 3
In short, I'm trying to build a Realm model class which has a few to-many relationships to other models. For these models (which are quite similar), I'm trying to create a superclass which contains the shared functionality, amongst which is also the inverse relationship. Therefore, I want to have a static property which contains the key in the first model to either of the other models, and then initialize a LinkingObjects property using this key name. Since Realm does not allow this to be lazy or computed, I cannot use these functionalities here.
If you inherit from NSObject why not using it ?
import Cocoa
class A: NSObject {
var property: String
public override init() {
let str = type(of: self).perform(#selector(getter: type(of: self).staticProperty))?.takeUnretainedValue() as! String
property = str
}
#objc class var staticProperty: String {
return "A"
}
}
class B: A {
override class var staticProperty: String {
return "B"
}
}
You can do this with this aproach
class A {
var prop: String{
return "A"
}
}
class B: A {
override var prop: String{
return "B"
}
}
print(A().prop) // "PRINTS A"
print(B().prop) // "PRINTS B"
A.staticProperty will use static dispatch and will always point to A's class property. You need dynamic dispatch here, aka type(of: self).
However, self needs an instance to work with, thus var property: String = type(of: self.staticProperty won't compile.
However, lazy properties can work around this limitation, so you could declare property as a lazy one:
class A: NSObject {
class var staticProperty: String {
return "A"
}
private(set) lazy var property: String = { type(of: self).staticProperty }()
}
class B: A {
override class var staticProperty: String {
return "B"
}
}
print(B().property) // B
P.S. the private(set) part is just something I usually do, I rarely allow extrinsic factors to change my object.
Update As #MartinR has pointed out, lazy is not a choice for the OP. An alternative solution that doesn't use a lazy var is to use a "shadowing" property:
class A: NSObject {
class var staticProperty: String {
return "A"
}
private var _property: String?
var property: String {
get {
return _property ?? type(of: self).staticProperty
}
set {
_property = newValue
}
}
}
class B: A {
override class var staticProperty: String {
return "B"
}
}
let b = B()
print(b.property) // B
b.property = "B'"
print(b.property) // B'
I have the following classes in Swift:
class A {}; class B {}
class Collection<T> {
var parent: Collection?
}
When I want to build a hierarchy like
var rootCol = Collection<A>()
var childCol = Collection<B>()
childCol.parent = rootCol
The last line produces this error:
Cannot assign value of type 'Collection<A>' to type 'Collection<B>?'
What type does parent have to be, so that one can assign it with different generic types?
The problem is that you are trying to assign an instance of type Collection<A> to a variable of type Collection<B>? which is not possible , You can only assign Collection<A> to Collection<A> or Collection<B> to Collection<B>.
Although your aren't writing <T> at the end of the parent type but swift infers that to be of the type Collection<T> and then also optional.
In This case I would recommend using protocol.
One more thing,it's advisable to NOT use names for classes that conflict with swift protocols.
class A {}
class B {}
protocol Foo{}
class Bar<T>:Foo {
var parent: Foo?
}
var rootCol = Bar<A>()
var childCol = Bar<B>()
childCol.parent = rootCol
Your structure can either be a tree of "containers" using a generic class based on the contained data (which can work on both reference and value types).
For example:
class TreeOf<T>
{
var value:T? = nil
weak var parent:TreeOf<T>? = nil
var children:[TreeOf<T>] = []
init(_ value:T? = nil)
{ self.value = value }
func addChild(_ value:T) -> TreeOf<T>
{
let newNode = TreeOf<T>()
newNode.value = value
children.append(newNode)
return newNode
}
}
var directory = TreeOf<String>("/")
directory.addChild("usr")
directory.addChild("library")
let users = directory.addChild("users")
users.addChild("paul")
users.addChild("John")
users.addChild("Mary")
let userNames = directory.children[2].children.map{$0.value}
Or your tree nodes themselves (i.e. the objects that are linked in the tree structure) can implement the linking variables and use a common protocol to obtain all the tree manipulation functionality for free. This however, only works on reference types and requires a final class.
For example:
protocol TreeNode:class
{
var parent :Self? { get set }
var children :[Self] { get set }
}
extension TreeNode
{
func removeFromParent()
{
parent?.children = parent!.children.filter{$0 !== self}
parent = nil
}
func childOf(_ newParent:Self) -> Self
{
removeFromParent()
parent = newParent
newParent.children.append(self)
return self
}
// ... more generic tree related functions provided by protocol for all classes
// e.g. root, count descendants, searches, traversal, prune and graft, etc.
}
final class FamilyMember:TreeNode
{
var parent:FamilyMember? = nil
var children:[FamilyMember] = []
var name = ""
init(_ newName:String) {name = newName}
}
let paul = FamilyMember("Paul")
let mary = FamilyMember("Mary").childOf(paul)
let john = FamilyMember("John").childOf(paul)
let suzie = FamilyMember("Suzie").childOf(mary)
let luke = FamilyMember("Luke").childOf(mary)
let irene = FamilyMember("Irene").childOf(john)
let miniPaul = paul.children.map{$0.name}
let miniMary = mary.children.map{$0.name}