How Swift uses Access Control

1. Access Control

Access control can restrict other source files or modules from accessing your code. This feature allows you to hide the specific implementation of the code, so that the code has better encapsulation.

2. 5 keywords

For access control, Swift provides five keywords. According to the accessible priority, the order from high to low is: open, public, internal, fileprivate and private.

Here is a summary of the differences between these keywords:

  • open: This module and other modules can be accessed and can only be applied to classes or class members. Allow other modules to inherit or override.
  • public: This module and other modules can be accessed. Other modules are not allowed to inherit or rewrite.
  • Internal: This module can be accessed. The access control keyword is not written. It defaults to internal.
  • fileprivate: current source file access.
  • private: only allowed in the current definition.

Another point about public is worth noting: when using public to modify a type, the type is public, but its members and methods are internal by default. If you want it to be called by other modules, you must explicitly modify it with public. The purpose of this is to prevent internal code in this type from being exposed as public.

3. Guidelines

No entity can be defined in terms of other entity that has a lower (more restrictive) access level

An entity cannot be defined by entities with lower access levels.

Code example:

fileprivate class Student { }
// Error: Constant cannot be declared public because its type 'Student' uses a fileprivate type
public let stu = Student()

As shown in the above code, Stu is decorated with public, while Student uses fileprivate. This leads to higher access permissions for stu than for Student, resulting in compiler errors. Changing Student to public or open eliminates compiler errors.

Code example:

public class SomePublicClass {                  // explicitly public class
    public var somePublicProperty = 0            // explicitly public class member
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

Although SomePublicClass is public, someInternalProperty is internal, that is, other modules cannot call it and can only access the somePublicProperty of explicit public.

4. Tuple

Access control of tuple type ≤ the smallest of tuple types.

struct Dog { }
fileprivate struct Cat { }
fileprivate var ani: (Dog, Cat)

Because fileprivate is smaller than internal, ani can only use fileprivate or private decoration, otherwise there will be compiler errors.

5. Generics

The access control of generic type shall be ≤ the minimum value of type access level and access level of all generic type parameters.

struct Dog { }
fileprivate struct Cat { }
public struct Person<T1, T2> { }

fileprivate var p = Person<Cat, Dog>()

In the above code, although Person is decorated with public, Cat uses fileprivate, which is smaller than public and internal. Therefore, the access right modifier of p can only be modified with fileprivate or private, otherwise there will be a compiler error.

6. Members and nested types

The access control of types will affect the access control of members (properties, methods, constructors, subscripts) and nested types.

  • Generally, if the type is fileprivate or private, the member and nested types are also fileprivate or private by default.
  • Generally, if the type is internal or public, the member and nested types are public by default.
public class SomePublicClass {                  // explicitly public class
    public var somePublicProperty = 0            // explicitly public class member
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

class SomeInternalClass {                       // implicitly internal class
    var someInternalProperty = 0                 // implicitly internal class member
    fileprivate func someFilePrivateMethod() {}  // explicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

fileprivate class SomeFilePrivateClass {        // explicitly file-private class
    func someFilePrivateMethod() {}              // implicitly file-private class member
    private func somePrivateMethod() {}          // explicitly private class member
}

private class SomePrivateClass {                // explicitly private class
    func somePrivateMethod() {}                  // implicitly private class member
}

7. Rewriting of members

  • The access control of subclass overriding members must be ≥ subclass, or ≥ parent overridden members.
  • Members of a parent class cannot be overridden by subclasses defined outside the member scope.
public class Person {
    private var age = 0
}
// Error: Property does not override any property from its superclass
class Student: Person {
    override var age: Int {
        set {}
        get { 10 }
    }
}

Because the age access control in the subclass is internal, while the age access control in the parent class is private, internal ≥ private (access control of overridden members of the subclass ≤ overridden members of the parent class, which does not comply with the first article above), a compiler error is caused. Delete private to eliminate the error.

If you do not want to delete private, you can also modify it as follows:

public class Person {
    private var age = 0
    
    class Student: Person {
        override var age: Int {
            set {}
            get { 10 }
        }
    }
}

Because the scope of age is the whole brace of Person, this is in line with the second article above.

8. Getter,Setter

The access control of get/set is consistent with the environment by default, that is, if the type is private, get/set is also private.

In daily development, we often encounter such a problem: allow others to read the value of the attribute, but do not allow modification. How to achieve this? The answer is to use private(set).

public class Person {
    private(set) var age = 0
}

It is externally readable but not writable.

9. Constructor

  • If other modules want to call the default constructor of the class modified by public, they need to modify the default constructor explicitly by public. Because the default constructor is internal.
  • If there are fileprivate and private storage attributes in the structure, the member constructor should also be expected to be consistent.

10. Enum

  • All case s are automatically consistent with the access control of enum.
  • case cannot set access control separately.
public enum CompassPoint {
    case north // public
    case south // public
    case east // public
    case west // public
}

All case s of CompassPoint are public. No other access control can be set for them.

11. Protocol

The content defined in the protocol is automatically consistent with the access control of the type, and the access control cannot be set separately.

You can see whether the following code can be compiled:

public protocol PublicProtocol {
    func test() // public
}

public class PublishClass: PublicProtocol {
    func test() { }
}

The answer is no, because the PublishClass modified by public, its test() is internal by default, and the test() of PublicProtocol is public, so an error is reported. The correct code is as follows:

public class PublishClass: PublicProtocol {
    public func test() { }
}

12. Extension

  • If the extension access control is explicitly set, the extension members will automatically receive the extension access control.
struct Student { }
private extension Student {
    func run() { } // private
}
  • If there is no explicit setting, the members in the extension are consistent with the member access control defined in the type.
extension Student {
    func write() { } // internal
}
  • Access control can be set separately for members in the extension.
extension Student {
    private func eat() { } // private
    func read() { } // internal
}
  • You cannot explicitly set the access control of an extension for protocol compliance.
protocol TestProtocol {
    func test()
}

// Error: 'internal' modifier cannot be used with extensions that declare protocol conformances
internal extension Student: TestProtocol {
    func test() { }
}

Posted on Fri, 26 Nov 2021 01:15:46 -0500 by Nothsa