Home A Beginner's Guide to Access Control in Swift
Post
Cancel

A Beginner's Guide to Access Control in Swift

Intro

Access control in iOS development plays a crucial role in maintaining the integrity of your code and ensuring that it is accessed in the correct way. Swift provides developers with a range of access levels that can be applied to variables, functions, and classes etc. In this article, we will dive into the five main access levels in Swift: private, fileprivate, internal, public, and open. We will explore the differences between each level and provide examples of when and how to use them in your iOS projects. Whether you’re a beginner or an experienced developer, understanding the concept of access control is essential for building robust, maintainable, and secure iOS applications.

Internal

This is the default access level for classes, functions, structs, enums etc in swift. It is useful when you want to share certain parts of the code within the same module. An internal member or function can be accessed from any file within the same module. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Car {
    var engine = "I am an internal engine moving the car"
    func move() {
        print(engine)
    }
}


class Person {

    func driveCar(){
        let car = Car()
        car.engine = "toyota engine"
        car.move()
        // will print "toyota engine"
    }
}

In the example above we can initialize the Car class, and access it’s engine property and move method from any where in our module. This is the default behavior as demonstrated by the Person class being able to call and modify the engine property and call the move method.

Private

A private member or function can only be accessed from within the same file that it is defined in. For example, in the following code snippet, the variable “engine” and the function “move” can only be accessed within the Car class and it’s extension defined in the same .swift file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Car {
    private var engine = "I am an internal engine moving the car"
    private func move() {
        print(engine)
    }

    private func startCar(){
        engine = "start"
    }
}

extension Car {
    private func moveLeft(){
        move() // this is allowed because it's an extension of Car that is in the same file
    }
}

class Person {

    func driveCar(){
        let car = Car()
        car.engine = "toyota engine" // this will not be allowed
        car.move() // this will not be allowed
    }
}

If the person class tries to access the engine property and move method like we initially did in the internal access control example, it won’t work because it’s restricted by the private keyword.

File Private

A fileprivate member or function can only be accessed from within the same file that it is defined in. It is similar to the “private” access level in that it limits the scope of the member or function to the file it is declared in, but it allows the member or function to be accessed from other classes within the same file. This level of access is useful when you want to share certain parts of the code within the same file but not outside of it. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Car {
    
fileprivate func start() {
        print("start the car")
}
    
private func reverse() {
        print("reverse the car")
  }
}


class Person {
    
    func driveCar() {
        let car = Car()
        car.start() // this is accessible because start() is a fileprivate method
        //car.reverse() // this won't compile because it's private and not fileprivate
    }
}

As long as Car and Person class exists in the same file, person object will be able to access the car’s start method because start is a fileprivate method.
So fileprivate is ideal for limiting a property or function or class’s accessibility to the specific file it is declared in, just as the name implies 😉. For most uses cases you should probably go with private over fileprivate.

Public

A public member or function can be accessed from any file, regardless of the module it is defined in. This level of access is useful when you want to share certain parts of the code across different modules. You will really only encounter this sort of access control when building libraries. For example, let’s say you want to expose your library’s classes and properties to be accessible to other developers for consumption but also prevent it from being subclassed / modified outside of the parent module, you would need to mark the specific classes with the public keyword to achieve this behavior.

In short, Public just means it is available and importable for use outside of the module it is defined in but it is not open for subclassing outside of the parent module. The intention here is to allow other modules to be able to access the class or property but prevent them from subclassing it. You can only subclass or override a public class or method in the same module it was defined in.

For example, let’s imagine we are building a framework called CarManufacturer. CarManufacturer has a Car class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// This Car class can be accessed outside of the CarManufacturer module but it can't be subclassed outside of the CarManufacturer module

// Car.swift file inside of our CarManufacturer module
public class Car {
    public init() {
        // This init can be access outside of the CarManufacturer module that Car was created in but it can't be overridden outside of it
    }

    public var isTinted: Bool = false

    public func drive(){
        // This method can be accessed outside of the CarManufacturer module that Car was created in but it can't be overridden
    }

    public func reverse(){
        performReverseAction()
    }

    func performReverseAction(){
        // do some internal logic that can't be accessed outside of the parent CarManufacturer module
    }
}

// Use case
// to use car we can 
import CarManufacturer 
class UseCar {
    let car = Car()
    car.isTinted = true 
    car.drive()
    car.reverse()
    car.performReverseAction() // this wont compile because it's an internal method that is only accessible within the CarManufacturer module
}

The public access control would prevent us from doing this:

1
2
3
4
5
6
7
8
import CarManufacturer 

class BMW: Car {
    override func drive() {

    }
}

As you can tell, public restricts subclassing or overriding of classes or methods outside of the parent CarManufacturer module. To do that we would need to open the Car class and it’s methods and properties that we want other modules to be able to subclass or override.

Open

An open member or function can be accessed and overridden by any class in any module. This level of access is typically used for classes that are intended to be subclassed. Just like Public, you will really only encounter this sort of access control when building or using libraries. The intention is to open them for use in any module from any file, and also open them for modification through subclassing or overriding. Great examples are the popular UIKit classes like the UIViewController class, UICollectionViewController class and the UITableViewController class etc. To use them in any iOS project we can import them from UIKit, and subclass them to do whatever we desire. This is the essence of open classes, properties, functions etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// This Car class can be accessed and subclassed outside of the CarManufacturer module 
// Car.swift file inside of our CarManufacturer module
open class Car {
    open init() {
        // This init can be access & overridden outside of the CarManufacturer module that Car was defined in
    }

    open var isTinted: Bool = false

    open func drive(){
        // This method can be accessed & overridden outside of the CarManufacturer module that Car 
    }

    open func reverse(){
        performReverseAction()
    }

    func performReverseAction(){
        // do some internal logic that can't be accessed outside of the parent CarManufacturer module
    }
}

The open access control would allow us to do this:

1
2
3
4
5
6
7
8
9
import CarManufacturer 

// Car can be inherited from because it's an open class inside of CarManufacturer
class BMW: Car {
    override func drive() {
        // drive can be overriden because it's a open method
    }
}

TLDR

  • Open: use when you intend for your class to be subclassed or overriden from any module.
  • Public: use when you want your class, members, and functions to be accessible from any file, regardless of the module it is defined in but not inheritable or overridden outside of the parent module.
  • Internal: This is the default. Use when you want your class, members, and functions to be accessible within the same module.
  • File Private: use when you want your class, properties, or functions to be accessible only within the same file.
  • Private: use when you want your class, members, and functions to be accessible only within the same class and it’s extensions that are in the same file.
This post is licensed under CC BY 4.0 by the author.