Why does this work when a parameter has no default value assigned?

I am subclassing NSPersistentContainer in my MacOS SwiftUI application as MyPersistentContainer, and don't quite understand how its initialiser works.

The initialiser for NSPersistentContainer is
public init(name: String, managedObjectModel model: NSManagedObjectModel).

So in my subclass, I have overridden it as follows:

public override init(name: String, managedObjectModel model: NSManagedObjectModel) {
  super.init(name: name, managedObjectModel: model)
}

(Note: I am not using the public convenience init(name: String) initialiser in my subclass.)

Then, in my AppDelegate I have:

lazy var persistentContainer: MyPersistentContainer = {
  let container = MyPersistentContainer(name: "Model_Name")
  container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    if let error = error {
      fatalError("Unresolved error \(error)")
    }
  })
  return container
}()

What I've found is that when persistentContainer is initialised with just a name passed in (as shown above), instead of getting a compiler error, my code builds and runs, and when persistentContainer is initialised, the designated initialiser is called, and model (an internal parameter mentioned in the signature) is automatically, and correctly, initialised even though I have not specified it in the call nor assigned a default value.

How does this work? My understanding is that default values must be provided, yet it appears they can be assumed? Does this have something to do with the fact that model is not a simple type?

NSPersistentContainer has an init method init(name: String) where the model name is used to find a model file of the same name and loads it. So when you call MyPersistentContainer(name: "Model_Name"), you are actually calling that init method on the superclass, not your overridden init method.

https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640557-init

(Edit: replaced earlier comment.)
Ok, so before my subclass is initialised, the superclass is initialised, then the code returns to my inititialiser, where I initialise the superclass again with super.init. This doesn't seem correct.

I don't think the superclass gets initialised first, instead your subclass inherits the convenience initializer due to rule #2 of Automatic Initializer Inheritance:

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

NSPersistentContainer has only that one designated initializer as you can see by writing import CoreData.NSPersistentContainer and CTRL+CMD+Clicking on it (that's the most reliable way I've found of opening that module interface file or whatever it's called).

The inherited convenience initializer calls your overridden initializer as per rule #2 of Initializer Delegation for Class Types:

A convenience initializer must call another initializer from the same class.

It works because self is bound to your subclass instance, so it jumps to that instead of the NSPersistentContainer init.

Simple test:

class A {
    init(foo: Int, bar: Int) {
        print("A designated init called")
    }
    convenience init(foo: Int) {
        print("A convenience init called")
        self.init(foo: foo, bar: 1)
    }
}

class B: A {
    override init(foo: Int, bar: Int) {
        print("B override init called")
        super.init(foo: foo, bar: bar)
    }
}

let foobar = B(foo: 1)

Output:

A convenience init called
B override init called
A designated init called
1 Like

@tem , @patrickgoley : thanks for your explanations, I understand now.

Terms of Service

Privacy Policy

Cookie Policy