I am using RealmSwift. What is the best / canonical way to generate ids for objects?
Here is what I came up with:
class MyObject: Object {
dynamic var id = ""
dynamic var createdAt = NSDate()
override class func primaryKey() -> String {
return "id"
}
func save() {
let realm = try! Realm()
if(self.id == "") {
while(true) {
let newId = NSUUID().UUIDString
let saying = realm.objectForPrimaryKey(MyObject.self, key: newId)
if(saying == nil) {
self.id = newId
break
}
}
}
try! realm.write {
realm.add(self)
}
}
}
I wanted a function that persists the object to Realm and either overwrites or creates a new one based on the id. This seems to work ok, but I wasn't sure if there was something built into realm to do this. Or is there a better way?
Thanks.
I know this is a few months old, but this is how I implement auto incrementing Primary Keys.
This code is untested, but you'll get the general idea
class MyObject: Object {
/**
Primary Key
Since you can't change the primary key after the object is saved,
we'll use 0 to signify an unsaved object. When we set the primary key,
we'll never use 0.
*/
dynamic var id: Int = 0
/**
Some persisted value
*/
dynamic var someString: String?
var nextID: Int {
do {
let realm = try Realm()
/// I would use .max() but Realm only supports String and signed Int for primary keys
/// so using overflow protection, the `next` primary key would be Int.min if the
/// current value is Int.max
var id = realm.objects(MyObject.self).sorted("id", ascending: true).last?.id ?? 0
/// add 1 to id until we find one that's not in use... skip 0
repeat {
id = Int.addWithOverflow(id, 1).0 /// Add with overflow in case we hit Int.max
} while id == 0 || realm.objectForPrimaryKey(MyObject.self, key: id) != nil
return id
} catch let error as NSError {
/// Oops
fatal(error: error.localizedDescription)
}
}
convenience init(someString: String?) {
self.init()
id = nextID
self.someString = someString
save()
}
override class func primaryKey() -> String? {
return "id"
}
func save() {
/// Gotta check this in case the object was created without using the convenience init
if id == 0 { id = nextID }
do {
let realm = try Realm()
try realm.write {
/// Add... or update if already exists
realm.add(self, update: true)
}
} catch let error as NSError {
fatalError(error.localizedDescription)
}
}
}
The process for creating a unique string ID (IE: a UUID) is very similar:
class MyObject: Object {
/**
Primary Key
*/
dynamic var id: String = ""
/**
Some persisted value
*/
dynamic var someString: String?
var nextID: String {
do {
let realm = try Realm()
var id: String = NSUUID().UUIDString
/// NSUUID().UUIDString almost always makes a unique ID on the first try
/// but we'll check after we generate the first one just to be sure
while realm.objectForPrimaryKey(MyObject.self, key: id) != nil {
id = NSUUID().UUIDString
}
return id
} catch let error as NSError {
/// Oops
fatal(error: error.localizedDescription)
}
}
convenience init(someString: String?) {
self.init()
id = nextID
self.someString = someString
save()
}
override class func primaryKey() -> String? {
return "id"
}
func save() {
/// Gotta check this in case the object was created without using the convenience init
if id == "" { id = nextID }
do {
let realm = try Realm()
try realm.write {
/// Add... or update if already exists
realm.add(self, update: true)
}
} catch let error as NSError {
fatalError(error.localizedDescription)
}
}
}
Realm(Swift) does not currently support auto-incrementing primary keys. You can set a primary like you are above, but for auto-incrementing and unique keys, there are a couple routes that you can go:
UUID (like you have above)
Query the max "id" (primary key) of your object and set the object to be inserted as id + 1. Something like...
let id = realm.objects(MyObject).max("id") + 1
Create your own hash signature (one potential example: SHA256(epoch timestamp + SHA256(object.values))
Related
We have a Realm object model that looks like this:
class RealmGroup: Object {
#Persisted var name: String?
#Persisted var groupId: Int? // this *should* have been the primary key
#Persisted var groupPath: String?
}
We also have multiple object models that define a "group" property of type RealmGroup. For example:
class A: Object {
// other properties removed for brevity
#Persisted var group: RealmGroup?
}
class B: Object {
#Persisted var group: RealmGroup?
}
class C: Object {
#Persisted var group: RealmGroup?
}
On one of my development phones, there are approximately 1000 A, B and C objects in the realm. And across all 1000 of those objects, there may only be 10 - 15 unique group IDs. The "groupId" property on the RealmGroup object should have been declared as the primary key so we'd only ever have one instance of RealmGroup with "groupId" = 12345, for example. And then, of course, every A, B and C object belonging to that group would reference the same RealmGroup object. But unfortunately, that wasn't caught until after we put this code into production. So now I'm looking into the possibility of "fixing" this retroactively through a migration.
It seems understandable that I can't simply make the "groupId" property the primary key on the existing RealmGroup model since we already have multiple instances of RealmGroup objects with the same "groupId" value in the realm. But beyond that, I'm not sure how I'd be able to implement this inside of a migration block. The Realm Swift SDK docs cover a few examples of migration but not exactly something like what I'm trying to accomplish.
Is it possible to do this without needing to create a new model (e.g., RealmGroupV2)?
I'm not sure if there's a better way to do this but I did finally get it working. First, I created a new RealmGroupV2 model with the group ID defined as the primary key. Then I added a new .group_v2 property to my A, B and C objects. Finally, I performed the migration like this:
// enumerate all of the existing RealmGroup objects and create one new RealmGroupV2
// object for each unique group ID
var groupIds: [Int] = [] // keep track of the group IDs we've processed
migration.enumerateObjects(ofType: RealmGroup.className(), { oldGroupObject, _ in
guard let oldGroupObject = oldGroupObject else { return }
guard let groupId = oldGroupObject.value(forKey: "groupId") as? Int else { return }
guard let name = oldGroupObject.value(forKey: "name") as? String else { return }
guard let groupPath = oldGroupObject.value(forKey: "groupPath") as? String else { return }
if groupIds.contains(groupId) == false {
// create a new RealmGroupV2 object for this group
let groupV2 = migration.create(RealmGroupV2.className())
groupV2["groupId"] = groupId
groupV2["name"] = name
groupV2["groupPath"] = groupPath
// add groupId to the groupIds array so we don't try to create another
// RealmGroupV2 for this groupId
groupIds.append(groupId)
}
})
// enumerate all of the existing A objects, assign a RealmGroupV2 object to the
// new .group_v2 property and set the now-deprecated .group property to nil
migration.enumerateObjects(ofType: A.className(), { oldObject, newObject in
guard let oldObject = oldObject else { return }
guard let newObject = newObject else { return }
guard let groupObject = oldObject["group"] as? Object else { return }
guard let groupId = groupObject.value(forKey: "groupId") as? Int else { return }
migration.enumerateObjects(ofType: RealmGroupV2.className(), { oldGroupV2Object, newGroupV2Object in
guard let newGroupV2Object = newGroupV2Object else { return }
if let groupIdFromV2Object = newGroupV2Object["groupId"] as? Int {
if groupIdFromV2Object == groupId {
newObject["group_v2"] = newGroupV2Object
newObject["group"] = nil
}
}
})
})
// *** repeat above migration for B and C objects ***
// finally, delete all of the old RealmGroup objects
migration.deleteData(forType: RealmGroup.className())
I'm sure there are ways to improve upon this but hopefully it will help someone else who might be struggling with a similar situation.
I am currently struggling with doing an upsert with vapor/fluent. I have a model something like this:
struct DeviceToken: PostgreSQLModel {
var id: Int?
var token: String
var updatedAt: Date = Date()
init(id: Int? = nil, token: String, updatedAt: Date = Date()) {
self.id = id
self.token = token
self.updatedAt = updatedAt
}
}
struct Account: PostgreSQLModel {
var id: Int?
let username: String
let service: String
...
let deviceTokenId: DeviceToken.ID
init(id: Int? = nil, service: String, username: String, ..., deviceTokenId: DeviceToken.ID) {
self.id = id
self.username = username
....
self.deviceTokenId = deviceTokenId
}
}
From the client something like
{
"deviceToken": {
"token": "ab123",
"updatedAt": "01-01-2019 10:10:10"
},
"account": {
"username": "user1",
"service": "some service"
}
}
is send.
What I'd like to do is to insert the new models if they do not exist else update them. I saw the create(orUpdate:) method however this will only update if the id is the same (in my understanding). Since the client does not send the id i am not quite sure how to handle this.
Also I can't decode the model since the account is send without the deviceTokenId and therefore the decoding will fail. I guess I can address the latter problem by overriding NodeCovertible or by using two different models (one for decoding the json without the id and the actual model from above). However the first problem still remains.
What I exactly want to do is:
Update a DeviceToken if an entry with token already exists else create it
If an account with the combination of username and service already exists update its username, service and deviceTokenId else create it. DeviceTokenId is the id returned from 1.
Any chance you can help me out here ?
For everyone who is interested:
I solved it by writing an extension on PostgreSQLModel to supply an upsert method. I added a gist for you to have a look at: here.
Since these kind of links sometimes are broken when you need the information here a quick overview:
Actual upsert implementation:
extension QueryBuilder
where Result: PostgreSQLModel, Result.Database == Database {
/// Creates the model or updates it depending on whether a model
/// with the same ID already exists.
internal func upsert(_ model: Result,
columns: [PostgreSQLColumnIdentifier]) -> Future<Result> {
let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model)
/// remove id from row if not available
/// otherwise the not-null constraint will break
row = row.filter { (key, value) -> Bool in
if key == "id" && value.isNull { return false }
return true
}
let values = row
.map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in
return (.identifier(row.key), row.value)
}
self.query.upsert = .upsert(columns, values)
return create(model)
}
}
Convenience methods
extension PostgreSQLModel {
/// Creates the model or updates it depending on whether a model
/// with the same ID already exists.
internal func upsert(on connection: DatabaseConnectable) -> Future<Self> {
return Self
.query(on: connection)
.upsert(self, columns: [.keyPath(Self.idKey)])
}
internal func upsert<U>(on connection: DatabaseConnectable,
onConflict keyPath: KeyPath<Self, U>) -> Future<Self> {
return Self
.query(on: connection)
.upsert(self, columns: [.keyPath(keyPath)])
}
....
}
I solved the other problem I had that my database model could not be decoded since the id was not send from the client, by using a inner struct which would hold only the properties the client would send. The id and other database generated properties are in the outer struct. Something like:
struct DatabaseModel: PostgreSQLModel {
var id: Int?
var someProperty: String
init(id: Int? = nil, form: DatabaseModelForm) {
self.id = id
self.someProperty = form.someProperty
}
struct DatabaseModelForm: Content {
let someProperty: String
}
}
I am using RealmSwift for my new app. My Realm class has two primary keys.
Just an example I have a Realm Model(Product) like this:-
class Product: Object, Mappable {
dynamic var id: String? = nil
dynamic var tempId: String? = nil
dynamic var name: String? = nil
dynamic var price: Float = 0.0
dynamic var purchaseDate: Date? = nil
required convenience init?(map: Map) {
self.init()
}
//I want to do something like this
override static func primaryKey() -> String? {
return "id" or "tempId"
}
func mapping(map: Map) {
id <- map["_id"]
tempId <- map["tempId"]
name <- map["name"]
price <- map["price"]
purchaseDate <- (map["purchaseDate"], DateFormatTransform())
}
So I am creating an realm object in my device and storing into realm db with primary key tempId, as the actual primary key is the id, which is a server generated primary key is coming only after the report sync. So when I am sending multiple reports to the server with those tempId server response me back with actual id mapped with each tempId. As the report is not only created from my side so I can't keep tempId as the primary key. I thought of Compound primary key but it won't solve the problem.
So I want to create a primary key such as If id is there then that is the primary key else tempId is the primary key.
How to do this?
What you need essentially is a computed property as a primary key. However, this isn't supported at the moment, only stored and managed realm properties can be used as primary keys. A workaround could be to define both id and tempId to have explicit setter functions and inside the setter function you also need to set another stored property, which will be your primary key.
If you want to change id or tempId don't do it in the usual way, but do it through their setter function.
Idea taken from this GitHub issue.
class Product: Object {
dynamic var id:String? = nil
dynamic var tempId: String? = nil
func setId(id: String?) {
self.id = id
compoundKey = compoundKeyValue()
}
func setTempId(tempId: String?) {
self.tempId = tempId
compoundKey = compoundKeyValue()
}
dynamic var compoundKey: String = ""
override static func primaryKey() -> String? {
return "compoundKey"
}
func compoundKeyValue() -> String {
if let id = id {
return id
} else if let tempId = tempId {
return tempId
}
return ""
}
}
dynamic private var compoundKey: String = ""
required convenience init?(map: Map) {
self.init()
if let firstValue = map.JSON["firstValue"] as? String,
let secondValue = map.JSON["secondValue"] as? Int {
compoundKey = firstValue + "|someStringToDistinguish|" + "\(secondValue)"
}
}
I was playing around with a Singleton to generate unique ID's to conform to Hashable. Then I realised it wasn't really thread safe. So I switched to OSAtomicIncrement32 because it does not use a closure, so it can be as simple as this:
class HashID {
static let sharedID = HashID()
private var storedGlobalID : Int32 = 0
var newID : Int {
get {
return Int(OSAtomicIncrement32(&storedGlobalID))
}
}
private init() {}
}
Then I wanted to try a HashID for each Type. So I use _stdlib_getDemangledTypeName reflecting to feed a Dictionary. But then again it wasn't thread safe. Different threads can now manipulate the Dictionary at the same time.
class HashID {
static let sharedID = HashTypeID()
func idForType(type: Any.Type) -> Int {
let typeName = _stdlib_getDemangledTypeName(type)
guard let currentID = storedGlobalIDForType[typeName] else {
let currentID : Int32 = 0
storedGlobalIDForType[typeName] = currentID
return Int(currentID)
}
let newID = atomicIncrement(currentID)
storedGlobalIDForType[typeName] = newID
return Int(newID)
}
private var storedGlobalIDForType : [String:Int32] = [String:Int32]()
private func atomicIncrement(var value: Int32) -> Int32 {
return OSAtomicIncrement32(&value)
}
private init() {}
}
I can obviously use GCD, but that means using closures and then I can't use a simple function with a return value. Using a completionHandler is just less "clean" and it also makes it more complicated to set this in the init of a class / struct.
This means default ID's and this would make them unhashable until the ID is fetched, or I would have to have an init with a completionHandler of it's own.
-> forgot how dispatch_sync works.
As I said, I was just playing around with this code. The first function is perfectly fine. The second however also gives a count of all instances of a type that were created. Which is not the most useful thing ever...
Is there some way I am forgetting to make the access to the Dictionary thread safe?
Updated Code:
credits to Martin R and Nikolai Ruhe
class HashTypeID {
// shared instance
static let sharedID = HashTypeID()
// dict for each type
private var storedGlobalIDForType : [String:Int] = [String:Int]()
// serial queue
private let _serialQueue = dispatch_queue_create("HashQueue", DISPATCH_QUEUE_SERIAL)
func idForType(type: Any.Type) -> Int {
var id : Int = 0
// make it thread safe
dispatch_sync(_serialQueue) {
let typeName = String(reflecting: type)
// check if there is already an ID
guard let currentID = self.storedGlobalIDForType[typeName] else {
// if there isn't an ID, store one
let currentID : Int = 0
self.storedGlobalIDForType[typeName] = currentID
id = Int(currentID)
return
}
// if there is an ID, increment
id = currentID
id++
// store the incremented ID
self.storedGlobalIDForType[typeName] = id
}
return id
}
private init() {}
}
Is there some way I am forgetting to make the access to the Dictionary thread safe?
Many. Using dispatch_sync on a serial queue is a good and simple way to go:
class HashID {
static func idForType(type: Any.Type) -> Int {
let typeName = _stdlib_getDemangledTypeName(type)
var id : Int = 0
dispatch_sync(queue) {
if let currentID = storedGlobalIDForType[typeName] {
id = currentID + 1
storedGlobalIDForType[typeName] = id
} else {
storedGlobalIDForType[typeName] = 0
}
}
return id
}
private static var queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_SERIAL)
private static var storedGlobalIDForType : [String : Int] = [:]
}
Using a read write lock could have advantages in your case.
I have a class User:
import UIKit
import ObjectMapper
class User: NSObject, CustomStringConvertible, Mappable {
var FirstName: NSString! ;
var LastName: NSString! ;
required init?(_ map: Map){
}
func mapping(map: Map) {
FirstName <- map["FirstName"]
LastName <- map["LastName"]
}
override var description:String {
var s:String=""
//USE REFLECTION TO GET NAME AND VALUE OF DATA MEMBERS
for var index=1; index<reflect(self).count; ++index {
s += (reflect(self)[index].0 + ": "+reflect(self)[index].1.summary+"\t")
}
return s
}
}
In swift 1.2, I was using reflect() method to get array of all the data members with their names and values.
Now, after I have updated to swift 2, I am getting the following error:
'reflect' is unavailable: call the 'Mirror(reflecting:)' initializer
With some trials, I was able to get the count of data members by this: Int(Mirror(reflecting: self).children.count), but still, I am unable to get the member name and its value.
I have looked into the following resources:
https://netguru.co/blog/reflection-swift
http://nshipster.com/mirrortype/
UPDATE
I have found the an answer here: https://stackoverflow.com/a/32846514/4959077. But this doesn't tell how to find out the type of reflected value. If the value is int and we parse it into String then it gives error.
You may access the reflected attribute "label" name, value and type as follows:
let mirror = Mirror(reflecting: SomeObject)
var dictionary = [String: Any]()
for child in mirror.children {
guard let key = child.label else { continue }
let value: Any = child.value
dictionary[key] = value
switch value {
case is Int: print("integer = \(anyValue)")
case is String: print("string = \(anyValue)")
default: print("other type = \(anyValue)")
}
switch value {
case let i as Int: print("• integer = \(i)")
case let s as String: print("• string = \(s)")
default: print("• other type = \(anyValue)")
}
if let i = value as? Int {
print("•• integer = \(i)")
}
}
Note: per the question followup, three approaches to determine the type of the reflected value are shown.
I have a solution that finds the name and type of a property given any class that inherits from NSObject.
I wrote a lengthy explanation on StackOverflow here, and my project is available here on Github,
In short you can do something like this (but really check out the code Github):
public class func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}