Access Control

Last updated on September 14th, 2020

Basics

  • Access control is about controlling access to the entities in your code by other code.
  • By default Swift sets the appropriate default access level for various entities.
  • However Swift also allows developer to explicitly specify access level for any entity.
  • The boundaries of access are defined by
    1. The type declaration (class, struct etc)
    2. The source file (the physical file containing the source)
    3. The module (the library or application built and shipped as a single unit)
  • Access to any entity inside these  boundaries by a code outside the boundary will be restricted by the  access levels allowed implicitly/explicitly by that entity or the type declaration enclosing that entity.

Access Levels

Open Access

  • Allows access to the entity from anywhere within the project.
  • Applies only to classes and class members.
  • Open classes can be subclassed in any module.
  • Open class members can be overridden by any subclass in any module.
  • Syntax example
open class MyClass {
open var myVar: Int = 0
}

Public Access

  • Allows access to the entity from anywhere within the project.
  • Public classes can be subclassed only in the defining module.
  • Public class members can be overridden in the subclasses.
  • Syntax example
public class MyClass {}
public var myVar: Int

Internal Access

  • Allows access to the entity only from within the defining module.
  • Syntax example
internal class MyClass {}
internal var myVar: Int

File-Private Access

  • Allows access to the entity only from within the defining source file.
  • Syntax example
fileprivate class MyClass {}
fileprivate var myVar: Int

Private Access

  • Allows access to the entity only from within the enclosing type declaration.
  • Syntax example
private class MyClass {}
private var myVar: Int

Determination of access levels

Contextual default access level

    • All entities in the code without an explicit access level get a default access level automatically assigned to them.
    • Default access level for most entities is internal.
    • Default access level for entities inside a Public/Internal Type is internal.
    • Default access level for entities inside a File-Private/Private Type is File-Private/Private respectively.

Dependent maximum access level

  • Any entity defined in terms of other entities has a maximum allowed access level, which is the lowest among those other entities.
  • Basically no entity can have a higher access level than that of the entities it depends up on. For example you can not define a public variable belonging to a private type or a public function accepting or returning a private type.
  • If the contextual default access level is not adequate or is higher than the dependent maximum access level, then you must explicitly specify an appropriate lower access level.

Constants, Variables, Properties

  • They can not have an access level higher than their types.
fileprivate struct Test {}     // Test is a fileprivate struct
fileprivate let myVar = Test() // Hence myVar must be fileprivate/private

Type Aliases

  • They can not have an access level higher than their underlying types.
fileprivate struct Test {}        // Test is a fileprivate struct
fileprivate typealias Temp = Test // Hence Temp must be fileprivate/private

Functions and Methods

  • They can not have an access level higher than that of their parameter types and return type.
fileprivate struct Test {}                     // Test is a fileprivate struct

fileprivate func temp(value: Test) -> Int { // Hence temp should be fileprivate/private
  return 0
}

Initialiazers

  • They can not have an access level higher than that of their parameter types.
  • Required initializers should have the same access level as the class they belong to.
  • Default memberwise initializer in a struct can not have an access level higher than that of any of the stored properties.

Tuples

  • Access level for a Tuple type is the lowest of the access levels of its constituent types.
  • You can not explicitly specify the access level for a Tuple as it has no standalone definition statement.

Enums

  • Access level for the cases of an enum is exactly the same as that of the enum. It is not allowed to explicitly specify the access levels for the cases.
  • If raw/associative values are used, then the enum can not have an access level higher than that of the types of the raw/associative values.
fileprivate typealias Value = Int     // Value is a fileprivate alias for Int

fileprivate enum Arrows: Value  {   // Hence Arrows must be fileprivate/private
  case UP
  case DOWN
  case LEFT
  case RIGHT
}

Subclasses

  • Subclasses can not have higher access level than their superclasses.
  • Subclasses can choose to have higher access levels for the overridden superclass members.
class Parent {
  func hello() {}                  // Method hello() is internal in Parent class
}

class Child: Parent {
  override public func hello() {}  // Method hello() is public in Child class
}

Subscripts

  • A subscript can not have higher access level than its index type and return type.
fileprivate typealias Index = Int                 // Index is a fileprivate alias

struct Test {
  fileprivate subscript(index: Index) -> Int { // Hence subscript must be fileprivate/private
      return 0
  }
}

Getters and Setters

  • Access level for Getters is always the same as that of the constant, variable, property or subscript they belong to.
  • Default access level for Setters also is same as that of the corresponding constant, variable, property or subscript.
  • However Setters can be given a lower access level than the Getter, to better control the read-write scope of the target entity.
  • Above statements are true in the case of both stored and computed properties.
struct Score {
  var user: String                  // Getter and Setter are internal
  private var role: String          // Getter and Setter are private
  public private(set) var value: Int // Getter is public. Setter is private.
  private(set) var level: Int        // Getter is internal. Setter is private.
}

Protocols

  • Each protocol requirement automatically gets the same access level as that of the protocol itself. There is no provision to set individual access levels for each protocol requirement.
  • A protocol inheriting from another protocol can not have a higher access level than the parent protocol.
  • A type adopting a protocol can have any access level irrespective of the access level of that protocol.
  • The type members implementing the protocol requirements must have an access level at least as high as the minimum access level between the type and the protocol.
// Type member (for protocol) access level >= Minimum access level(Protocol, Type)
public protocol MyProtocol {
  var myVar: Int {get set}    // myVar requirement has public access level
}

open class MyClass : MyProtocol {
  public var myVar: Int = 0    // myVar must have access level >= public
}                                // which is the minimum of (public, open)

Extensions

  • Contextual default access level for an extension is the same as that of the original type it extends.
  • They can not have a higher access level than that of the original type.
  • Unless explicitly specified, the extension members get contextual default access level exactly in the same manner as the original type members.
  • An extension that declare protocol conformance can not specify an explicit access level. Extension members implementing the protocol requirements must have an access level at least as high as the minimum access level between the type and the protocol.
public class MyClass {}

fileprivate protocol MyProtocol {
  var myVar: Int {get}
}

extension MyClass : MyProtocol {
  fileprivate var myVar: Int { // myVar should have access level >= fileprivate
      get { return 0 } // which is the minimum of (public, fileprivate)
  }
}

Generics

  • A generic function can not have a higher access level than that of the parameter types, return types, constraint types and where clause types.
fileprivate protocol  MyProtocol {}
internal    class    MyClass {}
public      class    ReturnClass {}

// myFunction must have access level <= fileprivate
// which is the lowest among the types that it is depending upon
fileprivate func myFunction<FirstType: MyProtocol, SecondType>
  (firstVar: FirstType, secondVar: SecondType, thirdVar: MyClass) -> ReturnClass
  where SecondType: Equatable {
  return ReturnClass()
}
  • A generic type can not have a higher access level than that of the constraint types and where clause types.
fileprivate protocol MyProtocol {}

// MyClass must have access level <= fileprivate
// which is the lowest among the types that it is depending upon

fileprivate class MyClass <T: MyProtocol> where T: Equatable {}

Unit Testing

  • A unit test module can access any internal entity from the code module, provided the import statements in the unit test module for this code module are marked as @testable, and also that the code module is compiled with testing enabled.
// In testcase files in the unit test module
@testable import MyCodeModule

Leave a Reply

Your email address will not be published. Required fields are marked *