Does the Initializer of an 'Open' Class Need to Be Open as Well

Does the initializer of an `open` class need to be open as well?

From SE-0117 Allow distinguishing between public access and public overridability:

Initializers do not participate in open checking; they cannot be declared open, and there are no restrictions on providing an initializer that has the same signature as an initializer in the superclass.

You need not and you cannot declare a init method as open:

open class OpenClass {

open init() { // error: only classes and overridable class members can be declared 'open'; use 'public'

}
}

The default access level for all members of a class (properties
and methods) is internal, that applies to open classes as well.

Why does a public class/struct in Swift require an explicit public initializer?

Marking a class public does not necessarily imply that the developer wants the class to be initialized publicly. For example, I often write base classes that exist solely for me to be able to subclass them. I give these superclasses internal initializers so that their subclasses can access them, but those in the outside world shouldn't be using them directly. For example, Operation in Foundation has no accessible initializers, yet the class is public. It is simply meant to be subclassed. This is considered an abstract class in Objective-C.

Since Swift doesn't contain explicit support for abstract classes, the act of making a class public but without public initializers basically serves as an abstract class (except each function must still have a default definition, either in the class itself or some protocol extension).

With this in mind, here are some Swift rules:

  • If your class is marked private, all variables, inits, and functions will default to private.
  • If your class is marked internal (which is default), public, or open, all variables, inits, and functions will default to internal.
  • A subclass's superclass must be at least as accessible.
  • classes and class members declared public in Objective-C are imported into Swift as open, due to there being no such distinction in Objective-C.

That second one is the one you are running into. The default init is picking up the default internal because the last thing Swift wants to do is expose your init as public API unless it is explicitly instructed to do so.

Note: In my tests (at least in the playground), it seems that with the introduction of fileprivate:

  • If a class is declared private or fileprivate, it seems that class members default to fileprivate unless explicitly annotated private.

override initializer from generic class

I am not sure what exactly goes wrong for you. When I run your code everything seems fine. Are you sure you're instantiating your instances correctly?

let checkItem = CheckItem("Check item data") { (string: String) -> String in
return string + "checkItem"
}

let labelItem = LabelItem<String, String, Int>("Label item") { (string: String) -> String in
return string + "labelItem"
}

let dataTableItem = DataTableItem<String, String, Int>("Data table item") { (string: String) -> String in
return string + "dataTableItem"
}

In LabelItem and DataTableItem you have a generic V which is not used anywhere and is not a parameter, so you need to be explicit with your types upon instantiation since you're not passing the V type in the init and the compiler can't infer the type. Thus <String, String, Int> or any other types that meet the constraints.

EDIT:

After looking into your project code (the project didn't run on my Xcode, I only copied relevant code into my project) I still see no problem - both initializers of CheckItem compile:

open class TableItem: NSObject {
public var title: String?
}

open class DataTableItem<T, U: Equatable & CustomStringConvertible, V: CustomStringConvertible>: TableItem{
let data: T
let getter: (T) -> U

public weak var host: TableItemHost?

public init(_ data: T, getter: @escaping (T) -> U) {
self.data = data
self.getter = getter
}
}

public protocol TableItemHost: class {}

open class CheckItem<T, U: Equatable & CustomStringConvertible>: DataTableItem<T,U,Bool> {
public init(_ data: T, host: TableItemHost, getter: @escaping (T) -> U) {
super.init(data, getter: getter)
self.host = host
}

public override init(_ data: T, getter: @escaping (T) -> U) {
super.init(data, getter: getter)
}
}

Creating instances:

let checkItem1 = CheckItem("Check item 1 ") { (string: String) -> String in
return string
}

class Host: TableItemHost {}
let host = Host()
let checkItem2 = CheckItem("Check item 2 ", host: host) { (string: String) -> String in
return string
}

print(checkItem1.data)
print(checkItem2.data)

Copy paste my code into a playground and see for yourself. Perhaps there is something other than the initializer causing the error.

For a test you can also try commenting out both initializers of CheckItem and instantiate it with the inherited initializer. That should work, because CheckItem will not have its own designated initializer anymore (https://stackoverflow.com/a/31459131/1433612)

Designated Initializer, clarify please.

In Objective-C, the designated initializer of a class is the method that's responsible for properly initializing the class to put it in an appropriate state for use. There is nothing in the code that marks it as the designated initializer, so there is usually a comment in the header file for that. It is up to the developer who is using or extending that class to figure out which method is the designated initializer (usually init or prefixed with init) and to write code accordingly. This can lead to mis-use of a class if it is not properly documented and its source code is not available and is one of the shortcomings that Swift attempts to solve. So to address your questions...

  1. A designated initializer is not determined by the compiler. A subclass should also have a designated initializer that calls the super's designated initializer at some point.
  2. Each class should clearly state (via comments or documentation) which initializer is intended to be used as the designated initializer. It is up to the developer who is using the class to make sure that the designated initializer is invoked. It is up to the developer of the class itself to ensure that the super's designated initializer is invoked as intended. However, with properly written classes, any of the init methods should invoke the designated initializer. However, it is not guaranteed by the compiler.
  3. Other init methods need to be coded appropriately to invoke the designated initializer via [self init...] or [super init...]. Again, it is up to you to figure out how a class is intended to be used and to use it or extend it appropriately.

The designated initializer is the method that does the "heavy lifting" to prepare a new instance of a class for use. Other initializers are known as convenience initializers and are typically used to provide shorter signatures with implied defaults since you can't specify default parameter values for Objective-C method signatures.

In most cases, if a class is properly written, you shouldn't really have to worry too much since all convenience initializers should end up invoking the designated one.

A lot of thought was put into this during the development of Swift and even if you don't intend to learn it, you should read up on Swift Initializers as it will give you good insight into the proper chain of initialization that you could then use to guide you in creating initializers in Objective-C.

UPDATE:
Since Xcode 6, designated initializers can be marked as such, typically via the NS_DESIGNATED_INITIALIZER macro. This helps enforce properly written classes and is something that was brought back to Objective-C from Swift. Check out iOS Designated Initializers : Using NS_DESIGNATED_INITIALIZER.

What is the 'open' keyword in Swift?

open is a new access level in Swift 3, introduced with the implementation
of

  • SE-0117 Allow distinguishing between public access and public overridability

It is available with the Swift 3 snapshot from August 7, 2016,
and with Xcode 8 beta 6.

In short:

  • An open class is accessible and subclassable outside of the
    defining module. An open class member is accessible and
    overridable outside of the defining module.
  • A public class is accessible but not subclassable outside of the
    defining module. A public class member is accessible but
    not overridable outside of the defining module.

So open is what public used to be in previous
Swift releases and the access of public has been restricted.
Or, as Chris Lattner puts it in
SE-0177: Allow distinguishing between public access and public overridability:

“open” is now simply “more public than public”, providing a very simple and clean model.

In your example, open var hashValue is a property which is accessible and can be overridden in NSObject subclasses.

For more examples and details, have a look at SE-0117.

Override designated initializer of superclass

@justin is basically on the point.

Methods in Objective-C are inherited. That means if the superclass has an initializer method (initializers are just methods), and your subclass does not override it, then your subclass will inherit that superclass's initializer method. That means that people can always call that superclass's initializer on an object of your subclass (basic consequence of inheritance and subtype polymorphism). But that might not be what you expected. The superclass's initializer might not do all the initialization that your class needs.

That's why you should override the superclass's initializer. If you don't want people to use that initializer on an object of your class, you should throw an exception in that initializer. Otherwise, you should override it to do any appropriate initialization for your class.

Initializer in a Swift Class

If your object can be "re-initialized" then just initialize it to some base-state by default:

class Test {
var thing: String = "" // Add default value
init() {
reinit()
}

func reinit() {
self.thing = "Hello"
}
}

As a rule, I think this is probably a bad design. Instead of "reinitializing," replace this class with a struct, and just throw it away and replace it when you want to reset things. As a rule, structs are very cheap to create. But if you need this, then default values is ok.

Class member initializer to initialize ifstream with error check?

You can write and call an inline lambda-expression that performs the appropriate checks; such a lambda-expression has access to the data members:

class A {
std::string filename = "f1.txt";
std::ifstream filestream = [&] {
std::ifstream fs{filename};
if (!fs)
throw std::runtime_error("failed to open ifstream");
return fs;
}();
};

It may be clearer to separate out the logic into a reusable helper function taking filename as a parameter, e.g. a static member function:

class A {
std::string filename = "f1.txt";
std::ifstream filestream = openChecked(filename);
static std::ifstream openChecked(std::string const& filename)
{
std::ifstream fs{filename};
if (!fs)
throw std::runtime_error("failed to open ifstream");
return fs;
}
};


Related Topics



Leave a reply



Submit