Access Control in Swift Explained

Hussein TijaniHussein Tijani
5 min read

In the general concept of software development, access control refers to the practice of defining the scope in which entities are available within a codebase. In Swift, these entities range from Classes to Structs, to Properties and Methods.

Access control in Swift is achieved by a set of keywords provided to us by the Swift programming language. In this article, I would delve into the details of these keywords, that is, when to use them, and the differences between similar keywords.

Open Access Level

At the pinnacle of the access hierarchy is the open keyword, offering the broadest access. This level is tailored for crafting code meant to be utilized as libraries. Entities declared as open are not only open to subclassing and extension but also allow method overriding. This exemplifies the core of object-oriented principles.

The example below defines a class Staff which is declared as open and a class Janitor which is a subclass of the Staff class. The Staff class contains a method identity which prints I am a staff to the console. As a result of this subclassing, the Janitor class has access to the Staff class’s identity method. However, we see that this method can be overridden. This feature is utilized, and now the identity method of the Janitor class prints I am a Janitor Staff to the console.

open class Staff {

  func init(){}

  func identity(){
    print("I am a staff")
  }
}

class Janitor: Staff {

  func init(){}

  override func identity(){
    print("I am a Janitor Staff")
  }
}

let john : Staff = Staff()
let ken : Janitor = Janitor()

john.identity() // I am a Staff
ken.identity() // I am a Janitor Staff

Public Access Level

The next tier, public, echoes the flexibility of open, permitting subclassing. However, it differs in not allowing method overriding. Entities designated as public are accessible within their defining module and extend their reach to external modules that import their home module.

Consider the Staff and Janitor class declared below. They are similar to the Staff and Janitor classes defined in the open keyword example above however Staff is now defined as a public entity, hence the identity method of the Staff class can’t be overridden. A different method of the Janitor class janitorIdentity is now defined to print I am a Janitor Staff, while the Janitor class still has access to the identity method of the Staff class.

public class Staff {
  func init(){}

  func identity(){
    print("I am a staff")
  }
}

class Janitor: Staff {

  func init(){}

  func janitorIdentity(){
    print("I am a Janitor Staff")
  }
}

let ken : Janitor = Janitor()

ken.identity() // I am a Staff
ken.janitoIdentity() // I am a Janitor Staff

Internal Access Level

Moving one step down is the internal access level, a gentle yet crucial access level within a module. Entities with this level can be accessed within the module they inhabit but remain sealed off from the outside modules. internal is Swift's default access level when not explicitly stated.

FilePrivate Access Level

fileprivate access level limits the accessibility of an entity to the source file within which it is defined. The example below demonstrates the use of the fileprivate access level.

class Person {

  var name = "John"

  func introduce(){
    print("Hi, I am \(name)")
  }
}

fileprivate func exposeName(name){
  print(name)
}

In the above example, a class Person and a function exposeName are created in the same file*.* Although the function is defined outside the scope of the Person class, it still has access to the name variable defined within the scope of the Person class. This is because the name variable is defined as a fileprivate entity, hence is accessible throughout the same source file.

Private Access Level

A private access level is used when we wish to limit the accessibility of an entity to the scope in which it is defined and the extensions of that scope which exist in the same source file. When entities are defined as private a reference to that entity outside of its scope or an extension of its scope will result in an error.

Consider the example illustrated below. A class Person is defined which has a private property name. Outside the scope of the Person class, a function exposeName which tries to access the name property of the Person class is also defined. This attempt to access the name property results in an error.

class Person {

  private var name = "John"

  func introduce(){
    print("Hi, I am \(name)")
  }
}

func exposeName(){
  print(name) // Cannot access the name property, as it is private.
}

Major Differences Between Open and Public

KeywordAccessibilitySubclassingOverriding
publicWithin its module and any module that imports its defining module.Allowed within its module and any module that imports its defining moduleAllowed within its module and any module that imports its defining module
openAny moduleAllowedAllowed

Major Differences between File-Private and Private

KeywordAccessibilitySubclassingOverriding
privateWithin the scope it is definedNot AllowedNot Allowed
fileprivateWithin the source file it is definedNot AllowedNot Allowed

Conclusion

Congratulations on getting to the end of this article. As you continue your journey through Swift’s realms, remember that access control wields the power to sculpt your code’s architecture and fortify its security. Each level serves a unique purpose, protecting the integrity of your software.

11
Subscribe to my newsletter

Read articles from Hussein Tijani directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hussein Tijani
Hussein Tijani

Hussein Tijani is a Software Engineer based in Lagos, Nigeria. His area of expertise encompasses the entire product development cycle, API design and development and third-party integrations. He is passionate about sharing his Software Engineering knowledge through technical articles, verbal conversations and social media.