Are You Using Access Levels in Swift

Even though access control is easily overlooked in Swift, it is a feature Objective-C developers have been asking for for a long time. Swift defines three access levels:

  • public
  • internal
  • private

Apart from a few exceptions, the default access level is internal. In other words, if you don't specify an access level, the access level is internal. Before we discuss when each access level should be used, I want to take a closer look at each access level.

Access Levels

Apple uses the term entities to refer to code constructs (classes, structures, functions, etc.) that can have access levels applied to them. I will use the same terminology in the rest of this discussion.

Public

If you need an entity to be accessible by entities of the same module as well as other modules, then that entity should be marked as public. The APIs of the iOS SDK your application accesses are marked as public, for example. In other words, a framework marks publicly accessible APIs as public to ensure the consumer of the framework can access them.

What are modules? In Swift, access control is slightly different from what you may be used to in other languages, such as PHP and Ruby. A module is defined as a single unit of code distribution. If your project has two build targets, then each target is considered a module. If your project includes a test target, then that too is a separate module. I revisit testing a bit later in this article.

Internal

As I mentioned earlier, internal is the default access level. Entities that are marked as internal are only accessible by entities within the same module.

Private

Modules are an important aspect of access control in Swift, and so are source files. Entities that are marked as private are only accessible by entities in the same source file.

It is recommended to mark private methods as private, but keep in mind that those methods are not accessible by entities that are declared in other source files. If you are coming from a different language, then this is something you need to become used to. In Swift, there is no such thing as a protected access level.

Applying Access Levels

Applying access levels is easy. You prefix the entity with the public, internal, or private keyword.

class ProfileViewModel {

    internal let profile: Profile

    ...

    private func stringFromTimeInterval(timeInterval: Double) -> String {
        ...
    }

}

Note that you can omit the internal keyword, but you can add it to for clarity, emphasizing that an entity is marked as internal.

What is important is being aware of the access levels of the types, methods, and functions you define in your projects. Even though this may not seem important if you're not creating a framework or library, it is a good habit to cultivate. If an instance method should only be accessed by instances of a class, then the instance method should be declared as private. It's as simple as that.

It goes without saying that a class with an internal access level cannot declare a property or method with a public access level. Put differently, you cannot declare a public property or method for an internal class. This may seem obvious, but take a look at the following example.

class Car {  
    ...
}

class Tire {  
    ...
}

public func changeTiresOfCar(car: Car, withTires: [Tire]) {  
    ...
}

The compiler will tell you that changeTiresOfCar(_:withTires:) cannot be public. Why is that? The parameter types of changeTiresOfCar(_:withTires:) are declared internal. The changeTiresOfCar(_:withTires:) can only be declared public if both Car and Tire are declared as public.

Testing

You may be wondering how you can write tests if most of the entities in your project are internal or private. Remember that a test target is a separate module and, by definition, doesn't have access to the target that you are testing.

Fortunately, Swift defines the @testable attribute. By prefixing an import statement with the @testable attribute, the test target gains access to internal entities in the module that is imported.

import XCTest  
@testable import Samsara

Private, however, remains private. There is no clean workaround to test or access private entities in a test target.

What About Protected?

Almost two years ago, the Swift team wrote a compelling article, explaining why Swift doesn't define a protected access level. In many programming languages, protected means that a method is only accessible by subclasses of a class, making it inaccessible from anywhere else. If you have been asking yourself this question, then I recommend reading that blog post.

Are You Using Access Control?

If you are reading this, then I assume you are using Swift in your projects. Are you using access control? What is stopping you? Questions? Leave them in the comments below or reach out to me on Twitter.