Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom RLMObject initializers in Swift #1101

Closed
maciejtrybilo opened this issue Nov 5, 2014 · 49 comments
Closed

Support custom RLMObject initializers in Swift #1101

maciejtrybilo opened this issue Nov 5, 2014 · 49 comments

Comments

@maciejtrybilo
Copy link

class Foo: RLMObject {
    dynamic var bar: String

    init(bar: String) {
        self.bar = bar

        super.init()
    }
}

let foo = Foo(bar: "😿")

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.

@tgoyne
Copy link
Member

tgoyne commented Nov 5, 2014

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.

@jpsim jpsim changed the title RLMObject subclass custom initalizers broken Support custom RLMObject initializers in Swift Nov 13, 2014
@mrh-is
Copy link
Contributor

mrh-is commented Nov 18, 2014

@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!

@alazier alazier added the backlog label Dec 5, 2014
@Pintouch
Copy link

@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 ?

@mrh-is
Copy link
Contributor

mrh-is commented Jan 22, 2015

@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.

@mrh-is
Copy link
Contributor

mrh-is commented Jan 22, 2015

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?)

@jpsim
Copy link
Contributor

jpsim commented Jan 22, 2015

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 😢.

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)
}

@jpsim
Copy link
Contributor

jpsim commented Jan 22, 2015

I understand this is suuuuper awkward, but as I said, this lays an important foundation.

@mrh-is
Copy link
Contributor

mrh-is commented Jan 22, 2015

Ah, that's ok! As long as there are clear directions. 👍 Thanks @jpsim! (YEAH SWIFT API)

@Pintouch
Copy link

Thanks! Overriding those constructors works!
Can't wait for the Swift API with generics Support!! :)

@xzf158
Copy link

xzf158 commented Feb 2, 2015

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

@loganwright
Copy link

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)
    }

@jdelaune
Copy link

Will we know when the new Swift API hits? I assume it hasn't yet

@tgoyne
Copy link
Member

tgoyne commented Mar 27, 2015

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.

@weixiyen
Copy link

Nice! when is the anticipated release? Roughly within 2 weeks?

@wimhaanstra
Copy link

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.

@jpsim
Copy link
Contributor

jpsim commented Mar 31, 2015

@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.

@wimhaanstra
Copy link

@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.

@yoshyosh
Copy link
Contributor

Another example of a working init
custominit

@jpsim
Copy link
Contributor

jpsim commented Apr 17, 2015

@yoshyosh are you sure init() and init!(objectSchema schema: RLMObjectSchema!) are necessary at all here? All they do is call super.

@tgoyne
Copy link
Member

tgoyne commented Apr 17, 2015

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.

@lbanders
Copy link

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.

@jpsim
Copy link
Contributor

jpsim commented Apr 28, 2015

We'll be releasing a Swift-specific optimized API in the very near future, which will make using custom initializers in Swift much friendlier.

@acoleman-apc
Copy link

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 . . . .

@jpsim
Copy link
Contributor

jpsim commented Sep 7, 2015

@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()
    }
}

@acoleman-apc
Copy link

@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?

@jpsim
Copy link
Contributor

jpsim commented Sep 8, 2015

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.

@frankradocaj
Copy link

@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) {
        ...
    }
}

@DrJid
Copy link

DrJid commented Nov 7, 2015

+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.

@kharmabum
Copy link

+1 on including this in the docs. Especially an example with relations.

@art-divin
Copy link

+1 for adding this into the "happy path" reference part. -3 hours.

@ed-mejia
Copy link

+1 @frankradocaj for the example, that should be on the main documentation 😫

@jpsim
Copy link
Contributor

jpsim commented Jan 22, 2016

@ed-mejia
Copy link

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)_

@kishikawakatsumi
Copy link
Contributor

@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)
    }
}

@ed-mejia
Copy link

@kishikawakatsumi You are totally right 👍 thanks for that clarification 😄

@rameez-leftshift
Copy link

Hey guys have the same issue but not able to resolve it by following the above steps. Can you help me with it??

@ed-mejia
Copy link

ed-mejia commented Feb 8, 2016

@rameez-leftshift Please explain your case, attach examples and maybe we could help you ..

@rameez-leftshift
Copy link

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

@ed-mejia
Copy link

ed-mejia commented Feb 8, 2016

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()
}

@rameez-leftshift
Copy link

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.

@ed-mejia
Copy link

ed-mejia commented Feb 8, 2016

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.

@rameez-leftshift
Copy link

i already have the at the start of the code snippet, its immediately after my declarations.

@rameez-leftshift
Copy link

Have created a new question for my problem (#3185).

@jhoughjr
Copy link

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?

@kharmabum
Copy link

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(#1101 (comment)).

@jhoughjr
Copy link

jhoughjr commented Apr 5, 2016

So from reading this and the linked issue, I infer that Realm simply cannot support optionals and convenience initializers. Is that the case?

@jpsim
Copy link
Contributor

jpsim commented Apr 5, 2016

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

@bdkjones
Copy link

bdkjones commented Apr 2, 2021

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.

@xingheng
Copy link

It's mid-2023, and Realm 10.33 still has the same issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests