Closed maciejtrybilo closed 9 years ago
We currently require default initializers for Swift classes due to limitations with Swift introspection. String
properties are reported as being of type id
due to that it's not an Objective-C type, and the only solution for that we've found is to create an object of the class and inspect the runtime type of id
fields. This does unfortunately mean that all properties need a default (and in the case of String
, it can't be nil
) even if it's not a meaningful default.
@maciejtrybilo If all of the properties have default values, you can define an init()
that just calls super.init()
. The framework creates an instance to inspect the types but doesn't use it otherwise. After that, everything works as you would expect, so your code can call your custom init
method. Hope that helps for now!
@mrh-is I tried defining the default constructor as you mention, I'm getting fatal error: use of unimplemented initializer 'init(objectSchema:)' for class Foo
.
class Foo: RLMObject {
dynamic var bar = ""
override init() {
super.init()
}
init(bar: String) {
self.bar = bar
super.init()
}
}
let foo = Foo(bar: "😿")
3 months passed since you commented it, so maybe it's not working anymore since new Xcode version? Is there a workaround ?
@Pintouch Yep, it's a change sometime between 0.87.4 and 0.90.0. Still investigating...
Edit: It's between 0.89.2 and 0.89.0. Still investigating even more.
OK, I'm not totally certain, because I (sadly!) can't spend the whole work day on debugging this so I didn't test with this commit, but I think it's commit 94c2dee. My suspicion (again, untested, sorry) is that there's some issue with isSwift
being applied to the whole chain rather than decided per class. I think someone with more familiarity with the schema loader will need to look into this, I'm at the limits of my abilities.
But definitely a breaking change for the Swift side of things. (Or is there a better way to set up Swift classes with custom initializers?)
This change happened in #1284 and lays the groundwork for a super awesome new Swift API, but makes using Realm in Swift a bit more cumbersome in the meantime :cry:.
Until our super awesome new Swift API is released, you'll unfortunately have to implement the following initializers if you want to use custom initializers:
init() {
super.init()
}
init(object:) {
super.init(object:object)
}
init(object:schema:) {
super.init(object: object, schema: schema)
}
init(objectSchema:) {
super.init(objectSchema: objectSchema)
}
I understand this is suuuuper awkward, but as I said, this lays an important foundation.
Ah, that's ok! As long as there are clear directions. :+1: Thanks @jpsim! (YEAH SWIFT API)
Thanks! Overriding those constructors works! Can't wait for the Swift API with generics Support!! :)
class Sentence: RLMObject {
convenience init(content: String){
self.init(object: content)
}
}
This is working for me.
http://quabr.com/27294702/how-do-i-fix-this-error-use-of-unimplemented-initializer-init-for-class
Just in case anyone else comes here that is beginning Realm for the first time and doesn't know the argument types, here's a drop in solution for RLMObjects with custom initializers. Just add this to your class
// MARK: RLMSupport
/*
Initializers required for RLM Support -- https://github.com/realm/realm-cocoa/issues/1101
*/
override init(object: AnyObject?) {
super.init(object:object)
};
override init(object value: AnyObject!, schema: RLMSchema!) {
super.init(object: value, schema: schema)
}
override init(objectSchema: RLMObjectSchema) {
super.init(objectSchema: objectSchema)
}
Will we know when the new Swift API hits? I assume it hasn't yet
It's now merged to master if you want to play around with it. We'll make an official announcement once it's included in a release.
Nice! when is the anticipated release? Roughly within 2 weeks?
It would be awesome if the podspec could be updated to include the Swift release. Makes it a lot easier to test it out :)
One of the things I really don't like is specifying default values for my properties because some of them are required and now this isn't enforced at compile-time. Also, adding the initializers mentioned here will not work when you work with non-optional properties without a default value.
@depl0y we'll be writing a new podspec for RealmSwift. Subscribe to #1705 to know when that's done.
One of the things I really don't like is specifying default values for my properties because some of them are required and now this isn't enforced at compile-time. Also, adding the initializers mentioned here will not work when you work with non-optional properties without a default value.
Default values aren't strictly necessary, although calling init()
is required to succeed. Aside from that, you can create any number of initializers in any format you like, as long as it calls super.init()
at some point.
@jpsim yup, but you can't create a custom init, if there are required properties.
For example:
class MyOwnClass: RLMObject {
dynamic var on: Bool = false;
dynamic var someRelation: MyOtherClass
init(someRelationParameter: MyOtherClass) {
self.someRelation = someRelationParameter;
super.init();
}
}
This throws an error without a initialiser called init()
. So then you create a simple initialiser, like this:
override init() {
super.init();
}
Of course you get a compile error here, because you are calling super.init()
before the property someRelation
is set. So should I set the someRelation
property just to MyOtherClass()
?
override init() {
self.someRelation = MyOtherClass();
super.init();
}
Finally you end up with a bunch of initialisers (override init()
, override init!(objectSchema schema: RLMObjectSchema!)
and your custom initialiser). And this leads to all kinds of bugs, like objects being created with default values.
I created an example:
class DemoObject1: RLMObject {
dynamic var someObject: DemoObject2;
override init() {
self.someObject = DemoObject2();
super.init();
}
override init!(objectSchema schema: RLMObjectSchema!) {
self.someObject = DemoObject2();
super.init(objectSchema: schema);
}
init(someObject: DemoObject2) {
self.someObject = someObject;
super.init();
}
}
class DemoObject2: RLMObject {
dynamic var name: String = "";
}
Now, what should be the appropriate way to init a DemoObject2, init a DemoObject1 and commit them to the database?
I tried:
RLMRealm.defaultRealm().beginWriteTransaction()
var demo2 = DemoObject2()
demo2.name = "MyDemoObject"
RLMRealm.defaultRealm().addObject(demo2);
var demo1 = DemoObject1(someObject: demo2)
RLMRealm.defaultRealm().addObject(demo1);
RLMRealm.defaultRealm().commitWriteTransaction();
But this results in 2 DemoObject2 objects being stored, one with a name
and the other without. The DemoObject1 is stored in the database, but the someObject
property is always pointing to a record without the name
value set.
I tried splitting it up in multiple transactions, but this ends in the same problem. (This might even be a separate thread, sorry about that).
Convenience initialisers do work for me, without problems, but this has the need that all properties are either optional or filled with a default value.
Another example of a working init
@yoshyosh are you sure init()
and init!(objectSchema schema: RLMObjectSchema!)
are necessary at all here? All they do is call super.
They're needed for the default property values to work when the other initializers are called from obj-c. It's (probably) a swift bug.
We have run in to similar problems, but it seems to be gone in 0.91.3 and Swift 1.2. In fact the build failed when we had the override init() methods.
We'll be releasing a Swift-specific optimized API in the very near future, which will make using custom initializers in Swift much friendlier.
If this is required, why is it not mentioned in the swift docs located here: https://realm.io/docs/swift/latest/#models
It seems to me that the models section needs a init subsection with a copy of the latest / working examples listed here . . . .
@acoleman-apc this issue was last active over 4 months ago and is no longer relevant.
To override an Object
initializer, just add it to your model:
public class MyModel: Object {
required public init() {
// custom initialization logic
super.init()
}
}
@jpsim Thanks but I have already added similar code to my project. The point was that this isn't mentioned in the current documentation but is instead in an issue that has been open since November of 2014. At this point, doesn't it deserve a mention in the official documentation?
This issue is actually not open, but rather has been closed for over 4 months.
However, as with anything you may be struggling with in Realm, we're open to updating our docs in order to clarify. But we generally avoid documenting standard language behavior like overriding parent class methods or adding custom methods, as seems to be the case here.
@jpsim I'm kind of a newbie with Swift (hey, aren't we all?). Documentation in the main website stating that you have to implement the following would be very helpful in getting started with Realm (aka "the happy path" 😀).
To create a custom constructor:
import Realm
import RealmSwift
class MyModel: Object {
// Make sure to declare this constructor if you wanna have a custom one like below
required init() {
super.init()
}
// And this one too
required override init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
// Now go nuts creating your own constructor
init(myCustomValue: String) {
...
}
}
+1 to @frankradocaj I'm a newbie to Realm and this was actually quite frustrating. So a mention in the docs for someone just getting started would be really nice. Was going to skip on realm because it seemed harder than it should be.
+1 on including this in the docs. Especially an example with relations.
+1 for adding this into the "happy path" reference part. -3 hours.
+1 @frankradocaj for the example, that should be on the main documentation :tired_face:
@ed-mejia we have a section in our docs on this: https://realm.io/docs/swift/latest/#adding-custom-initializers-to-object-subclasses
Hey @jpsim thanks for your comment.
In my case it did't work just with that, maybe because I need other init since Im using a parsing Json library, look at my actual model:
import Foundation
import Realm
import RealmSwift
import Gloss
public final class RoleModel: Object, Decodable {
dynamic var roleId = 0
dynamic var title = ""
dynamic var updatedAt: NSDate? = NSDate()
override public static func primaryKey() -> String? {
return "roleId"
}
public init?(json: JSON) {
super.init()
// check for required values in order to return a valid object
guard let roleId: Int = "role_id" <~~ json, let title: String = "title" <~~ json else {
return nil
}
self.roleId = roleId
self.title = title
self.updatedAt = Decoder.decodeDate("updated_at")(json)
}
// MARK: - Required initializers to make Realm works with custom inits
required public init() {
super.init()
}
required override public init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
}
In my case the compiler requires me to implement _required public init()_
_public init(realm: RLMRealm, schema: RLMObjectSchema)_ is not required by the compiler but the App crash when I try to do this after a query:
let contents = realm.objects(RoleModel)
print("from DB: \(contents.count)")
print(contents[0]) //<- it crash here
And the crash indicates that I haven't implemented _public init(realm: RLMRealm, schema: RLMObjectSchema)_
@ed-mejia It is more simple to use public convenience init?(json: JSON)
instead public init?(json: JSON)
. If you implement the convenience initializer, the compiler doesn't require to implement other initializers.
Like the following (Change public init?
to public convenience init?
and super.init()
to self.init()
):
public final class RoleModel: Object, Decodable {
dynamic var roleId = 0
dynamic var title = ""
dynamic var updatedAt: NSDate? = NSDate()
override public static func primaryKey() -> String? {
return "roleId"
}
public convenience init?(json: JSON) {
self.init()
// check for required values in order to return a valid object
guard let roleId: Int = "role_id" <~~ json, let title: String = "title" <~~ json else {
return nil
}
self.roleId = roleId
self.title = title
self.updatedAt = Decoder.decodeDate("updated_at")(json)
}
}
@kishikawakatsumi You are totally right :+1: thanks for that clarification :smile:
Hey guys have the same issue but not able to resolve it by following the above steps. Can you help me with it??
@rameez-leftshift Please explain your case, attach examples and maybe we could help you ..
Here is my class which is causing the crash
public class StoryBoard: Object, Deserializable {
public dynamic var status: Status?
required public init() {
status = Status()
super.init()
}
required public init(data: [String: AnyObject]) {
let new_id: Int = data["id"]! as! Int
id = "\(new_id)"
super.init()
}
public class func fromJSON(data: [String : AnyObject]) -> StoryBoard? {
return StoryBoard(data: data)
}
}
let me know if you need anything else
Without knowing what's your crash about.
could you please try this first, replace your init this way:
public convenience init(data: [String: AnyObject]) {
self.init()
// PUT ALL your initialisation here and remember to delete super.init()
}
This is the error which i get : use of unimplemented initializer 'init(realm:schema:)' for class 'App.StoryBoard'.
Your mentioned solution doesn't work. Thank you for the help though. Also i'm using swift 2.1.1 and realm version 0.97.4.
Weird, that's the same error I got previously and it's working fine with the provided solution,
try to put this in your class:
// MARK: - Required initializers to make Realm works with custom inits
required public init() {
super.init()
}
And let's see if you can avoid the crash... this should not be the final solution though.
i already have the at the start of the code snippet, its immediately after my declarations.
Have created a new question for my problem (https://github.com/realm/realm-cocoa/issues/3185).
I'm noticing the docs on custom initializers aren't working for me.
import Foundation
import RealmSwift
class Session: Object {
dynamic var uuid: String = NSUUID().UUIDString
dynamic var startTime: NSTimeInterval
dynamic var endTime: NSTimeInterval
convenience init(startDate:NSDate) {
self.init()
self.startTime = startDate.timeIntervalSince1970
}
func spanString() -> String {
var returnString = ""
return returnString
}
override class func primaryKey() -> String {
return "uuid"
}
}
The compile error is : Missing argument for parameter 'startDate' It occurs at the self.init() call. I used the code example in the docs section on custom initializers. Any ideas?
Try setting initial values for both startTime and endTime.
On Mar 12, 2016, 8:40 PM -0800, Jimmy Hough Jr.notifications@github.com, wrote:
I'm noticing the docs on custom initializers aren't working for me. `import Foundation import RealmSwift
class Session: Object {
dynamic var uuid: String = NSUUID().UUIDString dynamic var startTime: NSTimeInterval dynamic var endTime: NSTimeInterval convenience init(startDate:NSDate) { self.init() self.startTime = startDate.timeIntervalSince1970 } func spanString() ->String { var returnString = "" return returnString } override class func primaryKey() ->String { return "uuid" }
}`
The compile error is : Missing argument for parameter 'startDate' It occurs at the self.init() call. I used the code example in the docs section on custom initializers. Any ideas?
— Reply to this email directly orview it on GitHub(https://github.com/realm/realm-cocoa/issues/1101#issuecomment-195874431).
So from reading this and the linked issue, I infer that Realm simply cannot support optionals and convenience initializers. Is that the case?
So from reading this and the linked issue, I infer that Realm simply cannot support optionals and convenience initializers. Is that the case?
That's not the case, see Realm's documentation on custom initializers: https://realm.io/docs/swift/latest#adding-custom-initializers-to-object-subclasses
I realize this is an old thread, but it would be REALLY handy if stuff like this were documented right up front for Realm. Important details like, "You have to use the default init()
method on Object
subclasses" is something to tell folks right away, in the "Welcome to your first Realm project" tutorials. Otherwise, you spend hours writing your own init()
methods only to get a crash and then dig through GitHub issues to discover yet another arcane limitation of Realm.
Edit: It's also complicated because there are SO MANY different docs for Realm. There's the legacy stuff, the new MongoDB stuff, the individual SDK docs, etc. Googling for help with Realm leads to outdated sources and incorrect information.
It's mid-2023, and Realm 10.33 still has the same issue.
The last line results in
fatal error: use of unimplemented initializer 'init()' for class 'moduleName.Foo'
. Somehow Realm seems to want to call the default initializer, but ideally I wouldn't want to implement it because I don't necessarily have reasonable default values and would rather avoid optionals.