Access control in swift 4

2019-04-09 12:46发布

While upgrading to Swift4 from Swift3, I got some issues related to access control.

Here is the sample code. Which was there in Swift3, working fine in past times -

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

To make the code run in Swift4, I have to change access control for defaultValue to public. Here is the Swift4, compiling version

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

While I was wondering what is going on, I tried to remove open access control for MyClass, it allowed me to remove access identifier for defaultValue. Even can put it to private.

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

I understand all the access identifiers, but I am not able to understand this behaviour. Especially the first case where xcode forced me to change access control of defaultValue to public.

Please help.

1条回答
霸刀☆藐视天下
2楼-- · 2019-04-09 13:25

My original answer (shown below) is now mostly outdated – the beginnings of the resilience model are to be implemented in Swift 4.2 with the introduction of the @inlinable and @usableFromInline attributes, corresponding to the old @_inlineable and @_versioned attributes.

In addition, and more importantly, the rule for what default arguments of publically accessible functions can reference has changed again. To recap the previous rules:

  • In Swift 3 there was no enforcement of what access level such default argument expressions could reference (allowing your first example where defaultValue is internal).

  • In Swift 4, such a default argument could only refer to declarations exposed as a part of the module's interface, including those that aren't otherwise directly visible to users in another module (i.e @_versioned internal).

However in Swift 4.2, with the implementation of SE-0193, the rule is now that the default argument expression of a publicly accessible function can only refer to publicly accessible declarations (not even @inlinable internal or @usableFromInline internal).

I believe this is paving the way for the displaying of default argument expressions in a module's generated interface file. Currently Swift just shows an unhelpful = default, but I believe this will change to actually show the default argument. This can only realistically happen with this new access-control restriction in place (Edit: This is now happening).


Old answer (Swift 4)

This change is due to the work towards a resilience model that is already available via underscored attributes (@_inlineable, @_versioned, @_fixed_layout), but is yet to be officially finalised (so you probably shouldn't be using these attributes yourself yet). You can read about the full proposed details of the resilience model here, as well as the the Swift evolution discussion on it here.

In short, an inlineable function is one whose implementation, as well as declaration, is exposed as a part of a module's interface and can therefore be inlined when called from another module. An inlineable function must therefore also be publically accessible to begin with (i.e public or higher).

What you're running into is a change that makes default argument expressions for publically accessible functions inlineable, meaning that they must be available to be evaluated directly in the calling module's binary. This reduces the overhead of calling a function with default parameter values from another module, as the compiler no longer needs to do a function call for each default argument; it already knows the implementation.

I don't believe this change is officially documented in the release of Swift 4 itself, but it is confirmed by Swift compiler engineer 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.

So if you have a publically accessible function with a default argument expression (such as MyClass.defaultValue in your case), that expression can now only refer to things that are also a part of that module's interface. So you need to make defaultValue publically accessible.

Unfortunately, there's currently no way to make a private function's declaration part of a module's interface (which would allow for your usage of it in a default argument expression). The attribute that would facilitate this is @_versioned, but it is forbidden with (file)private due to the following reasons given by Slava Pestov:

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.

You could achieve this with a @_versioned var defaultValue though:

open class MyClass {

    private let value: Int

    @_versioned static var defaultValue: Int {
        return 10
    }

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

The declaration of MyClass.defaultValue is now exported as a part of the module's interface, but still cannot be directly called from another module's code (as it's internal). However, the compiler of that module can now call it when evaluating the default argument expression. But, as said earlier, you probably shouldn't be using an underscored attribute here; you should wait until the resilience model has been finalised.

查看更多
登录 后发表回答