swift 4 中的访问控制

Access control in swift 4

Swift3 升级到 Swift4 时,我遇到了一些与 access control 有关的问题。

这是示例代码。在 Swift3 中,过去工作正常 -

open class MyClass {
    private let value: Int
    static var defaultValue: Int { return 10 }
    public init(value: Int = MyClass.defaultValue) {
        self.value = value
    }
}

要在 Swift4 中生成代码 运行,我必须将 defaultValueaccess control 更改为 public。 这里是Swift4,编译版本

open class MyClass {
    private let value: Int
    static public var defaultValue: Int { return 10 }
    public init(value: Int = MyClass.defaultValue) {
        self.value = value
    }
}

虽然我想知道发生了什么,但我尝试删除 MyClassopen 访问控制,它允许我删除 defaultValueaccess 标识符。甚至可以放到private.

class MyClass {
    private let value: Int
    private static var defaultValue: Int { return 10 }
    public init(value: Int = MyClass.defaultValue) {
        self.value = value
    }
}

我理解所有访问标识符,但我无法理解这种行为。特别是第一个案例 xcode 迫使我将 defaultValueaccess control 更改为 public.

请帮忙。

我原来的答案(如下所示)现在大部分已经过时了——弹性模型的开端 are to be implemented in Swift 4.2 引入了 @inlinable@usableFromInline 属性,对应于旧的@_inlineable@_versioned 属性。

此外,更重要的是,可公开访问的函数的默认参数可以引用的规则再次更改。回顾一下之前的规则:

  • 在 Swift 3 中,没有强制执行此类默认参数表达式可以引用的访问级别(允许您的第一个示例,其中 defaultValueinternal)。

  • 在 Swift 4 中,这样的默认参数只能引用作为模块接口的一部分公开的声明,包括那些不是其他模块中的用户无法直接看到(即 @_versioned internal)。

但是在 Swift 4.2 中,随着 SE-0193, the rule is now 的实现,公共可访问函数的默认参数表达式可以 引用可公开访问的声明(甚至 @inlinable internal@usableFromInline internal)。

相信这为在模块生成的接口文件中显示默认参数表达式铺平了道路。目前 Swift 只显示无用的 = default,但我相信这会更改为实际显示默认参数。这只有在新的访问控制限制就位时才会发生(编辑:这是now happening)。


旧答案(Swift 4)

此更改是由于针对弹性模型的工作,该模型已通过带下划线的属性(@_inlineable@_versioned@_fixed_layout)提供,但尚未正式确定(所以你可能不应该自己使用这些属性)。您可以阅读 full proposed details of the resilience model here, as well as the the Swift evolution discussion on it here.

简而言之,inlineable 函数的 实现 以及声明作为模块接口的一部分公开因此可以在从另一个模块调用时内联。因此,可内联函数也必须是可公开访问的(即 public 或更高版本)。

您 运行 所做的更改是 default argument expressions for publically accessible functions inlineable,这意味着它们必须可以直接在调用模块的二进制文件中进行评估。这减少了从另一个模块调用具有默认参数值的函数的开销,因为编译器不再需要为每个默认参数执行函数调用;它已经知道实现了。

我不认为此更改在 Swift 4 本身的发行版中有正式记录,但已被 Swift 编译器工程师 Slava Pestov 确认,who says:

Swift 3.1 added resilience diagnostics for inlineable code, which is not an officially supported feature, but in Swift 4 we switched these checks on for default argument expressions as well.

因此,如果您有一个带有默认参数表达式(例如 MyClass.defaultValue 在您的情况下)的可公开访问的函数,那么该表达式现在只能引用也是该模块接口一部分的内容。因此,您需要使 defaultValue 可公开访问。

不幸的是,目前无法使 private 函数的声明成为模块接口的一部分(这将允许您在默认参数表达式中使用它)。 @_versioned 可以促进这一点的属性,但由于以下原因 given by Slava Pestov:

而被 (file)private 禁止

It would be a trivial change to allow @_versioned on private and fileprivate declarations, but there are two pitfalls to keep in mind:

  • Private symbols are mangled with a ‘discriminator’ which is basically a hash of the file name. So now it would be part of the ABI, which seems fragile — you can’t move the private function to another source file, or rename the source file.

  • Similarly, right now a @_versioned function becoming public is an ABI compatible change. This would no longer work if you could have private @_versioned functions, because the symbol name would change if it became public.

For these reasons we decided against “private versioned” as a concept. I feel like internal is enough here.

可以 通过 @_versioned var defaultValue 实现此目标:

open class MyClass {

    private let value: Int

    @_versioned static var defaultValue: Int {
        return 10
    }

    public init(value: Int = MyClass.defaultValue) {
        self.value = value
    }
}

MyClass.defaultValue 的声明现在作为模块接口的一部分导出,但仍然不能从另一个模块的代码中直接调用(因为它是 internal)。但是,该模块的 编译器 现在可以在评估默认参数表达式时调用它。但是,如前所述,您可能不应该在这里使用带下划线的属性;您应该等到弹性模型最终确定。