DEV Community

Naveen Ragul B
Naveen Ragul B

Posted on

Swift - Access Control

Access Control

Access control restricts access to parts of your code from code in other source files and modules.

  • Specific access levels can be assigned to individual types (classes, structures, and enumerations), as well as to properties, methods, initializers, and subscripts belonging to those types.

  • Protocols can be restricted to a certain context, as can global constants, variables, and functions.

Modules and Source Files

  • A module is a single unit of code distribution—a framework or application that’s built and shipped as a single unit and that can be imported by another module with Swift’s import keyword.

  • A source file is a single Swift source code file within a module.


Access Level

There are 5 different access level for entities in swift.

  1. Open access
  2. Public access
  3. Internal access
  4. File-private access
  5. Private access
  • Open access and public access enable entities to be used within any source file from their defining module, and also in a source file from another module that imports the defining module.

  • Open access applies only to classes and class members, and it differs from public access by allowing code outside the module to subclass and override.

  • Internal access enables entities to be used within any source file from their defining module, but not in any source file outside of that module.

  • File-private access restricts the use of an entity to its own defining source file.

  • Private access restricts the use of an entity to the enclosing declaration, and to extensions of that declaration that are in the same file.

  • Open access is the highest (least restrictive) access level and private access is the lowest (most restrictive) access level.


Principle of Access Levels

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

  • A public variable can’t be defined as having an internal, file-private, or private type, because the type might not be available everywhere that the public variable is used.
struct someType {   //implicitly declared as internal
    var someInt : Int
}

public var instance = someType(someInt: 5) // Error : Variable cannot be declared public because its type 'someType' uses an internal type
Enter fullscreen mode Exit fullscreen mode
  • function can’t have a higher access level than its parameter types and return type, because the function could be used in situations where its constituent types are unavailable to the surrounding code.

Default Access Levels

All entities in your code (with a few specific exceptions) have a default access level of internal if you don’t specify an explicit access level.

For Custom Type :

  • The access control level of a type also affects the default access level of that type’s members (its properties, methods, initializers, and subscripts).

  • If a type has access level as private or file private, the default access level of its members will also be private or file private respectively.

  • If you define a type’s acces level as internal or public, the default access level of the type’s members will be internal.

  • A public type defaults to having internal members (but can be explicitly set to public), not public members to avoid the internal workings of a type as public API by mistake.

example :

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

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

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

private class SomePrivateClass {                // explicitly private
    func somePrivateMethod() {}                  // implicitly private
Enter fullscreen mode Exit fullscreen mode

Tuple Types

The access level for a tuple type is the most restrictive access level of all types used in that tuple.
example :

fileprivate let object1 = (SomeFilePrivateClass(), SomePrivateClass())  // OK
internal let object2 = (SomeFilePrivateClass(), SomePrivateClass())  // Error : Constant cannot be declared internal because its type '(SomeFilePrivateClass, SomePrivateClass)' uses a private type
Enter fullscreen mode Exit fullscreen mode

Function Types

The access level for a function type is calculated as the most restrictive access level of the function’s parameter types and return type.

example :

func someFunction() -> (SomeInternalClass, SomePrivateClass) {  //Error : Function must be declared private or fileprivate because its result uses a private type
    // function implementation goes here
}

private func someOtherFunction() -> (SomeInternalClass, SomePrivateClass) {  // OK
    // function implementation goes here
}
Enter fullscreen mode Exit fullscreen mode

Enumeration Types

The individual cases of an enumeration automatically receive the same access level as the enumeration they belong to. Different access level can't be specified for individual enumeration cases.

  • Raw Values and Associated Values : The types used for any raw values or associated values in an enumeration definition must have an access level at least as high as the enumeration’s access level.

example :

struct UPC {  // implicitly internal
    let numberSystem : Int
    let manufacturer : Int
    let product : Int
    let check : Int

    init(_ numberSystem : Int, _ manufacturer: Int, _ product: Int, _ check: Int){
        self.numberSystem = numberSystem
        self.manufacturer = manufacturer
        self.product = product
        self.check = check
    }
}

fileprivate struct QRcode{  // explicitly fileprivate
    let productCode : String

    init( _ productCode : String ){
        self.productCode = productCode
    }
}

enum Barcode {  // implicitly internal
    case upc(UPC)
    case qrCode(QRcode)  // Error : Enum case in an internal enum uses a fileprivate type
}
Enter fullscreen mode Exit fullscreen mode

Nested Types

  1. The access level of a nested type is the same as its containing type, unless the containing type is public.
  2. Nested types defined within a public type have an automatic access level of internal(but can be explicitly changed).

Subclassing

  • Subclass can be created for any class that can be accessed in the current access context and that’s defined in the same module as the subclass.
  • subclass can be created for any open class that’s defined in a different module.
  • A subclass can’t have a higher access level than its superclass.
  • For classes that are defined in the same module, class member that’s visible in a certain access context can be overidden. For classes that are defined in another module, any open class member can overriden.
  • An override can make an inherited class member more accessible than its superclass version.
  • It’s even valid for a subclass member to call a superclass member that has lower access permissions than the subclass member, as long as the call to the superclass’s member takes place within an allowed access level context

example :

public class A {
    fileprivate func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}
Enter fullscreen mode Exit fullscreen mode

Constants, Variables, Properties, and Subscripts

  • A constant, variable, or property can’t be more public than its type. It’s not valid to write a public property with a private type.

example :

private var privateInstance = SomePrivateClass()
Enter fullscreen mode Exit fullscreen mode
  • Getters and setters for constants, variables, properties, and subscripts automatically receive the same access level as the constant, variable, property, or subscript they belong to.

  • setter can have a lower access level than its corresponding getter, to restrict the read-write scope of that variable, property, or subscript. Like fileprivate(set), private(set), or internal(set) can be prefixed before the var or subscript.

  • This rule applies to stored properties as well as computed properties. Even though you don’t write an explicit getter and setter for a stored property, Swift still synthesizes an implicit getter and setter for you to provide access to the stored property’s

example :

public struct TrackedString {
    public private(set) var numberOfEdits = 0 // public getter and private setter
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}
Enter fullscreen mode Exit fullscreen mode

Initializers

  • Custom initializers can be assigned an access level less than or equal to the type that they initialize.

  • A required initializer must have the same access level as the class it belongs to.

  • The types of an initializer’s parameters can’t be more private than the initializer’s own access level.

  • A default initializer has the same access level as the type it initializes, unless that type is defined as public. For a type that’s defined as public, the default initializer is considered internal (public no-argument initializer can be provided explicitly).

  • The default memberwise initializer for a structure type is considered private if any of the structure’s stored properties are private. Likewise, if any of the structure’s stored properties are file private, the initializer is file private. Otherwise, the initializer has an access level of internal.


Protocols

  • The access level of each requirement within a protocol definition is automatically set to the same access level as the protocol.

  • A protocol's requirement can't be changed to a different access level than the protocol it supports.

  • For a public protocol, the protocol’s requirements require a public access level for those requirements when they’re implemented(not Internal like in other places).

  • The Inherited protocol can have at most the same access level as the protocol it inherits from.

  • A type can conform to a protocol with a lower access level than the type itself. For a public type that can be used in other modules, but whose conformance to an internal protocol can only be used within the internal protocol’s defining module.

  • The context in which a type conforms to a particular protocol is the minimum of the type’s access level and the protocol’s access level. For example, if a type is public, but a protocol it conforms to is internal, the type’s conformance to that protocol is also internal.

  • When you write or extend a type to conform to a protocol, you must ensure that the type’s implementation of each protocol requirement has at least the same access level as the type’s conformance to that protocol. For example, if a public type conforms to an internal protocol, the type’s implementation of each protocol requirement must be at least internal.

example :

public protocol SomeProtocol{
    func someMethod() -> Int
    func someOtherMethod()-> Int
}

class SomeClass : SomeProtocol{
    private func someMethod() -> Int { return 5 } // Error : Method 'someMethod()' must be as accessible as its enclosing type because it matches a requirement in protocol 'SomeProtocol'
    func someOtherMethod() -> Int { return 10 }  // OK , implicitly internal
}
Enter fullscreen mode Exit fullscreen mode

Extension

A class, structure, or enumeration can be extended in any access context in which the class, structure, or enumeration is available.

  • Any type members added in an extension have the same default access level as type members declared in the original type being extended.

If extended type is

  1. public or internal type, any new type members you add have a default access level of internal.
  2. file-private type, any new type members you add have a default access level of file private.
  3. private type, any new type members you add have a default access level of private.
  • Alternatively, you can mark an extension with an explicit access-level modifier to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual type members.

  • Explicit access-level modifier can't be used for an extension if that extension is used to add protocol conformance.

  • Private Members in Extensions
    Extensions that are in the same file as the class, structure, or enumeration that they extend behave as if the code in the extension had been written as part of the original type’s declaration. Below declarations are correct

  1. Declare a private member in the original declaration, and access that member from extensions in the same file.
  2. Declare a private member in one extension, and access that member from another extension in the same file.
  3. Declare a private member in an extension, and access that member from the original declaration in the same file.

example :

protocol SomeProtocol {
    func doSomething()
}

struct SomeStruct {
    private var privateVariable = 12
}

extension SomeStruct: SomeProtocol {
    func doSomething() {
        print(privateVariable)
    }
}
Enter fullscreen mode Exit fullscreen mode

Generics

The access level for a generic type or generic function is the minimum of the access level of the generic type or function itself and the access level of any type constraints on its type parameters.


Type Aliases

  • Any declared type aliases are treated as distinct types for the purposes of access control.
  • A type alias can have an access level less than or equal to the access level of the type it aliases. For example, a private type alias can alias a private, file-private, internal, public, or open type, but a public type alias can’t alias an internal, file-private, or private type.
  • This rule also applies to type aliases for associated types used to satisfy protocol conformances.

Top comments (0)