为什么Swift中的公共类/结构需要一个显式的公共初始化器?

Way*_*man 30 swift swift3

考虑模块中的跟随类(同样适用于结构):

public class Foo {
   public func bar() {
       // method body
   }
}
Run Code Online (Sandbox Code Playgroud)

注意,它没有显式初始化器; 这个例子不需要任何特殊的初始化.此类将被暴露给其他模块,因为它已被标记public.但是,当模块外部的代码尝试初始化它时,编译器会抱怨:

let foo = Foo() // 'Foo' initializer is inaccessible due to 'internal' protection level
Run Code Online (Sandbox Code Playgroud)

为了满足编译器,我必须定义一个标记的显式空初始化器public:

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

   public func bar() {
       // do something useful
   }
}
Run Code Online (Sandbox Code Playgroud)

为什么,如果类明确public,我是否需要明确定义公共初始化程序?它不应该暗含一个公共初始化器吗?

还有一个相关的问题在这里,关于单元测试,但我发现它并没有真正得到的是什么,我觉得是一个令人惊讶的问题设计的核心理念.

Mat*_*man 27

将类公开标记并不一定意味着开发人员希望公开初始化类.例如,我经常编写仅为我而存在的基类,以便能够对它们进行子类化.我给这些超类internal初始化程序,以便它们的子类可以访问它们,但外部世界的那些不应该直接使用它们.例如,OperationFoundation没有可访问的初始值设定项的情况下,该类是公共的.它只是意味着是子类.这被认为是Objective-C中的抽象类.

由于Swift不包含对抽象类的显式支持,因此将类公开但没有公共初始化器的行为基本上用作抽象类(除了每个函数必须仍然具有默认定义,无论是在类本身还是在某些协议扩展中) .

考虑到这一点,这里有一些Swift规则:

  • 如果您的类已标记private,则默认为所有变量,inits和函数private.
  • 如果您的类被标记internal(默认值),public或者open,则默认为所有变量,inits和函数internal.
  • 子类的超类必须至少是可访问的.
  • 由于Objective-C中没有这样的区别,因此public在Objective-C中声明的类和类成员被导入到Swift中open.

第二个是你遇到的那个.默认初始化是提取默认值,internal因为Swift想要做的最后一件事是将init公开为公共API,除非明确指示这样做.

注意:在我的测试中(至少在操场上),似乎引入了fileprivate:

  • 如果声明了一个类,private或者除非明确注释fileprivate,fileprivate否则类成员似乎默认为private.

  • 您关于使用此“功能”创建抽象类的建议非常有趣,但我没有考虑过。 (2认同)
  • 这真的很愚蠢(就像很多 swift 的决定一样),特别是当 swift 试图从子类化转向使用协议时。如果类编写者希望它是抽象的,他们应该必须将初始化器声明为内部的。或者 swift 应该只有一个抽象关键字。 (2认同)