36

Consider the follow class (equally applicable to a struct, as well) in a module:

public class Foo {
   public func bar() {
       // method body
   }
}

Note, it does not have an explicit initializer; this example doesn't need any special initialization. This class would be exposed to other modules because it is marked public. However, when code outside the module attempts to initialize it, the compiler complains:

let foo = Foo() // 'Foo' initializer is inaccessible due to 'internal' protection level

In order to satisfy the compiler, I have to define an explicit empty initializer marked public:

public class Foo {
   public init() {
       // This initializer intentionally left empty
   }

   public func bar() {
       // do something useful
   }
}

Why, if the class is explicitly public, do I need to explicitly define a public initializer? Shouldn't it implicitly have a public initializer?

There is a related question here, pertaining to unit testing, but I find it doesn't really get at the core of the design philosophy of what I find to be a surprising issue.

Community
  • 1
  • 1
Wayne Hartman
  • 18,369
  • 7
  • 84
  • 116
  • 3
    Related: [How can I make public by default the member-wise initialiser for structs in Swift?](http://stackoverflow.com/questions/26224693/how-can-i-make-public-by-default-the-member-wise-initialiser-for-structs-in-swif) – Hamish Oct 25 '16 at 21:08

1 Answers1

34

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.
Matthew Seaman
  • 7,952
  • 2
  • 37
  • 47
  • 2
    Your suggestions on using this 'feature' for creating abstract classes is very interesting and something that I had not considered. – Wayne Hartman Oct 26 '16 at 02:32
  • 2
    This is really stupid (like quite a few swift decisions), especially as swift is trying to move from subclassing to using protocols. If the class writer intends it to be abstract they should have to declare an initialiser as internal. or swift should just have an abstract keyword. – Jonathan. Nov 23 '17 at 12:53
  • 1
    In addition to abstract class case, I find the feature useful in another scenario where users are expected to create instance by using class static method rather than creating an instance directly. – rayx Mar 27 '21 at 06:58