Is there a way to change the Data Type in Realm Database? - swift

As you can see the image, I totally mess up the Data Type (The red circle). Is there a way to change the Data Type into Integer?
EDIT
I want to change the data type from a String to an Int and I have existing data so I can't start with a fresh realm and just change the var type.
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid: String?
#objc dynamic var cateid: String?
}

I may have misunderstand the question but you appear to have existing data stored as as a String and you want to 'convert' all of those to an Int.
You cannot directly change the type to another type and have the stored data changed as well. If you do, it will be flagged with an error.
Error!
Migration is required due to the following errors:
- Property 'Item.itemid' has been changed from 'string' to 'int'.
You need to incorporate a migration block to 'convert' the string value to an Int. Assuming we add a new Int property to our object `item_id', something along these lines will migrate your strings to int's and in the case where the string is not a valid it, it will be assigned a value of 0
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerateObjects(ofType: Item.className()) { oldObject, newObject in
let stringValue = oldObject!["itemid"] as! String
newObject!["item_id"] = Int(stringValue) ?? 0
}
}
})
Also, as soon as Realm is accessed, the object models are written to the Realm file. So a simple matter of
let items = realm.object(Item.self)
Will store that model even if no data was ever written. If, after that line, the var type is changed from a String to an Int, it will throw the migration error.
Deleting the Realm and starting from scratch is one option if that's the case, and as mentioned above, a migration block.
If this is brand new model that has never been used, then as the comments and other answer suggest, just change the String to an Int.

Simply change the String to Int in your Object model. Please note that the Realm documentation says:
String, NSDate, and NSData properties can be declared as optional or non-optional using the standard Swift syntax.
So unlike the String in your previous model, you will not be able to declare your Int as optional. You have two options:
Declare a default value:
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid: Int = 0
#objc dynamic var cateid: Int = 0
}
Declare it as a RealmOptional:
class Item: Object {
#objc dynamic var name: String?
#objc dynamic var itemid = RealmOptional<Int>()
#objc dynamic var cateid = RealmOptional<Int>()
}
For more information on each solution please see this SO answer and the Realm documentation.

Related

Is there a way to reuse model within itself?

I have an app that stores User (UserModel) Friend list. if a friend clicks one user, its type is the same type (UserModel). In Swift it wouldnt allow using the model recursively, giving me this error:
"Value type 'OwnerModel' cannot have a stored property that recursively contains it"
import Foundation
struct OwnerModel: Codable {
var ownerId: Int
var ownerEmail: String
var ownerUserName: String
var ownerCommonName: String
var ownerBirthDate: String
var ownerCountry: String
var ownerBdayReminderId: Int
var ownerIsVerified: Bool
var ownerIsOnline: Bool
var ownerIsEventGreeted: Bool
var ownerIsBirthdayGreeted: Bool
var ownerAllowGreeting: Bool
var ownerFriends: OwnerModel
}
Is there a way I can reuse the OwnerModel under ownerFriends?
This can't work because structs are value types. So each OwnerModel would have to have a OwnerModel inside it, which would have to have an OwnerModel inside it, which would have to have an OwnerModel inside it.... This can never resolve. Since you've marked this Codable, try to write the JSON you expect to encode this to.
That said, ownerFriends seems plural, which would suggest [OwnerModel], and that's not a problem, since you could have zero of them:
struct OwnerModel: Codable {
...
var ownerFriends: [OwnerModel]
}
Remember again, however, that structs are value types. So each OwnerModel is just a value. It's not a reference to any other object. If you want to refer to other owners, you may want to store IDs rather than the actual object (or use classes in order to create references).

Swift, Core Data, Optional Integer16 and keyPath

I have an entity in CoreData which has an optional property of type Integer 16. It can genuinely be nil and in my application I want to refer to it as an Int? type. As Int? (or for that matter Int16?) isn't a recognised Objective-C type, the compiler throws a bit of a wobbly. I want to avoid using code like NSNumber?.intValue throughout so I've actually set up my ManagedObject type with custom accessors for this property. My question relates to identifying the property through #keyPath rather than a static string. In Core Data the field is named 'pin' on entity 'User'. Here's the code I have:
class User: NSManagedObject {
// MARK: - Properties
static let pinKey = "pin"
#NSManaged internal(set) var name: String
#NSManaged fileprivate var primitivePin: NSNumber?
internal(set) var pin: Int? {
get {
willAccessValue(forKey: #keyPath(pin)) // THIS LINE ERRORS
let value: Int? = primitivePin.map { $0.intValue }
didAccessValue(forKey: User.pinKey)
return value
}
set {
willChangeValue(forKey: User.pinKey)
primitivePin = newValue.map { NSNumber(value: Int16($0)) }
didChangeValue(forKey: User.pinKey)
}
}
}
The line in error is what I 'want' to achieve but of course the var pin isn't an obj-c type and the compiler complains, so I have defined the static constant pinKey as you can see. #keyPath feels like the right way to go about it, and the entity does have a field called pin, but in this scenario is the only option open to me to use a static value?
In #keyPath you have to specify property name. If you don't have defined property called pin, you will receive an error. In your case you have to use #keyPath(User.primitivePin). I believe this should work.
Also, i guess, calling map is redundant here. You can write directly let value = primitivePin?.intValue and so on.
The answer is....with custom properties/accessors #keyPath can't used as there is no defined #NSManaged property for it - as Maksym points out. However, you can't use the defined primitive for it either, instead using the property name as a String as also shown in the code above (User.pinKey)

Making Realm & Unbox play nice

I am learning to parse JSON in Swift, coming from Android/Java, and I am using Unbox by John Sundell to help me with this, which reminds me of GSON.
Reference: Unbox pod
I use Realm as a database to store data locally.
Reference: Realm.io
It would be great to find a workflow to parse a class with JSON and save it to Realm. I don't want to have a struct that implements Unboxable AND a class that implements Object (Realm), because then I have to reflect the two. That isn't too much work for my current project, but it is kinda ugly...
Did any of you try a similar workflow?
I don't think you need two separate types. My suggestion is to create your objects as Swift classes that inherit from Realm's Object class, and then also conform them to the Unboxable protocol that Unbox offers. (Although the examples on Unbox's page use struct models, there's nothing in the code or documentation that indicates that classes wouldn't work.)
Realm model objects work just like any other classes: in addition to defining whatever properties on the objects you'd like stored in the database, you can also define methods and initializers, and even specify properties that you want Realm to ignore. This allows you to create an object that both serves as a Realm model and also a JSON model compatible with Unbox.
A more concise approach that doesn't require to override required initialisers (based on a tweet by Marin Todorov):
class Car: Object, Unboxable {
dynamic var vendor: String = ""
dynamic var modelName: String = ""
dynamic var electric: Bool = false
required convenience init(unboxer: Unboxer) throws {
self.init()
self.vendor = try unboxer.unbox(key: "vendor")
self.modelName = try unboxer.unbox(key: "modelName")
self.electric = try unboxer.unbox(key: "electric")
}
}
Here is an example that works perfectly for me:
class ProviderRealm: Object, Unboxable {
dynamic var identifier: String = "demo"
dynamic var name: String?
dynamic var logo: String?
/// Initializer used for unboxing of JSON string
required init(unboxer: Unboxer) throws {
self.identifier = (try? unboxer.unbox(key: "identifier")) ?? "demo"
self.name = try? unboxer.unbox(key: "name")
self.logo = try? unboxer.unbox(key: "logo")
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required init() {
super.init()
}
required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}
override static func primaryKey() -> String? {
return "identifier"
}
}

Confusion about type casting in swift

I was toying in the playground in xcode 7.3.1 with swift. I am a bit confused about the type casting in swift.
So, here is a bit of code that I tried.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
var movieItem = Movie(name: "GOT", director: "RRMartin")
movieItem.dynamicType //Movie.Type
(movieItem as? MediaItem).dynamicType //Optional<MediaItem>.Type
var someItm = movieItem as! MediaItem //Movie
someItm.dynamicType //Movie.Type
I've shown the output from the playground in the comment. Here you can see the type in each line.
Now according the docs of apple, The conditional form, as?, returns an optional value of the type you are trying to downcast to. As per the docs, I am trying to downcast to MediaItem, and I am getting the MediaItem as optional type.
But when I use force unwrap(that is as!) the returned type is Movie. But I wanted it to be MediaItem.
Also, another thing to notice is that, the type is actually changed. Some data are actually truncated. Because when I tried to access the director property which is present in the Movie, I cannot access it. As I've downcast it.
So, if the type is downcast, why the returned type is Movie? Shouldn't it be MediaType?
So, my question is this, when I type cast some derived class(Movie) to base class(MediaType), shouldn't the converted type be base class(MediaType)?
dynamicType tells you what the underlying type of the object is. It doesn't tell you what the type of var currently referencing that object is.
For instance:
let a: Any = 3
a.dynamicType // Int.Type
Swift, of course, keeps track of these underlying types which is what allows you to later downcast a MediaItem to a Movie (if that is what it really is).
The confusion for you came when you did:
(movieItem as? MediaItem).dynamicType //Optional<MediaItem>.Type
An Optional is it's own type. It is an enumeration with two values: .None and .Some(T). The .Some value has an associated value that has its own dynamic type. In your example, when you asked for the dynamicType, it returned the underlying type of the Optional which is Optional<MediaItem>.Type. It didn't tell you what the dynamic type of the value associated with that Optional is.
Consider this:
let x = (movieItem as? MediaItem)
x.dynamicType // Optional<MediaItem>.Type
x!.dynamicType // Movie.Type

How to store optionals

As Realm doesn't support optionals, which are not Object subclasses, I'm trying to wrap a string into StringObject:
final class StringObject: Object {
dynamic var value: String = ""
convenience init?(_ value: String?) {
self.init()
if let value = value {
self.value = value
} else {
return nil
}
}
}
And use it like this:
final class Person: Object {
dynamic var firstName: String = ""
dynamic var lastName: StringObject? // can be optional
}
But this solution has a nasty side effect: as StringOptional values will be stored in their own table within the database, there will be countless duplicate values every time a StringObject is created. I tried making StringObject's value a primary key, but upon Person object's creation I receive an error:
Can't set primary key property 'lastName' to existing value 'Doe'
Which means that internally Realm does not upsert relationships.
Is there a better way to store optionals?
We actually released a beta of Realm that had support for optional strings and data properties, and it will hopefully be released more widely soon! In the meantime, you can try out the beta at https://github.com/realm/realm-cocoa/issues/628#issuecomment-106952727.