Access Control Explained In Swift

Written by Reinder de Vries on December 6 2019 in App Development

Access Control Explained In Swift

In Swift, access control is used to restrict parts of your app’s source code. You’re essentially prohibiting other modules, frameworks, classes and code from using your code. A benefit of access control is the ability to clearly define a public API for your code, and hide private implementation details.

In this article, we’ll discuss how access control works and how it affects your Swift programming. We’ll look at the benefits and disadvantages of access control, and discuss a few hands-on examples.

Ready? Let’s go.

  1. What Is Access Control?
  2. The 5 Access Levels In Swift
  3. Rules For Access Levels
  4. Access Levels In Practical iOS Development
  5. Further Reading

What Is Access Control?

Let’s start with a quick example. We’re going to define a struct in Swift, like this one:

struct Invoice
{
    var ID = ""
    var amount = 0.0
    var date = Date()
}

Next, we’re creating an instance of Invoice. Like this:

var phoneBill = Invoice()
phoneBill.ID = "2019-ABC-42"
phoneBill.amount = 35.0

So far so good! We’ve created an instance of Invoice, assigned it to phoneBill, and changed the ID and amount properties in something sensible. We’re working with these properties from outside the struct.

Next, we’re going to change the access level of the ID property. Like this:

struct Invoice
{
    private var ID = ""
    var amount = 0.0
    var date = Date()
}

In the above code, the declaration for the ID property, with var ID = ..., now has the private modifier. This restricts access to the ID property to inside the struct; it is private now.

And, as expected, when we run the previous code with phoneBill, we now see an error:

‘ID’ is inaccessible due to ‘private’ protection level

We’ve effectively prevented anyone from using the ID property from outside the struct. Imagine shipping the Invoice struct to another coder, as part of a library or framework API. They can’t change the ID of an Invoice object now (as far as the example goes, of course). It’s now a private implementation detail, and that gives us greater control over how the struct’s APIs are used.

An API, which stands for Application Programming Interface, are the components, functions, properties and classes, built by someone else, that your code “talks to”. Twitter has a web-based API for coders to read tweets, and a framework or library has an API, comprised of classes and functions, that you call, when you work with that library.

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 13 apps with Swift 5 and Xcode 11.

The 5 Access Levels In Swift

Swift has 5 different access levels, like the private modifier we used before. They are, from least restrictive to most restrictive:

  1. open and public — anyone can access, within the module, and in code that imports the module
  2. internal — anyone can access, but only within the module (default)
  3. fileprivate — anyone can access, but only within the current Swift file
  4. private — only the enclosing declaration can access, such as a class or struct

A module is a single unit of Swift code, such as a framework, library, or your app itself. If you’re building a chat app project, your app project is one module, like ChatApp. The UIKit framework, that you import with the import statement, is a module too. And so is a library you’ve added via CocoaPods, such as Alamofire.

The open and public access levels are similar, but they differ in two ways. Firstly, open can only be used with classes, and their properties and functions. Secondly, a class marked with open can be subclassed and overriden from other modules. The open access level is important for code architecture, and it mostly only affects library and framework authors.

If you look closely, you’ll see that the access levels public, internal, fileprivate and private are listed from least restrictive to most restrictive. The fileprivate modifier, for example, limits the use of an entity, like a class, to the file, whereas public would make it available to the entire module. The public access level is more open than the fileprivate level, and so on.

Let’s get into the nitty-gritty, next…

In many programming languages, Swift’s access control is called method visibility, or just visibility. Access control determines whether a function is visible from outside the class (or other components), so to speak.

Rules For Access Levels

It’s important to point out that access levels, and their modifiers, can be used on any entity in your code, such as:

  • Classes, structs, enumerations, protocols
  • Properties, functions, computed properties and subscripts
  • Custom types, and nested types

In general, we can state that you can control the access of types (classes, structs), things you store (properties, subscripts, etc.) and functions. That’s pretty much everything except locally defined variables!

The default access level in Swift is internal. That means that any class you define in your code can be accessed from within your app’s module, and not outside of it.

This is a sensible default: you want to restrict your code to your module, and not allow outside access. If you’re building a public-facing API, though, you’ll have to consider whether a class or property is private or public.

Another principle is the following, taken from the Swift Language Guide:

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

In other words, you can’t give people access to your living room if your front door is locked. That makes sense: the lowest common denominator, at the deepest level, determines the access at a higher level.

Let’s look at an example. Check out this Swift code:

struct Invoice
{
    public var kind:InvoiceKind = .debit
}

private enum InvoiceKind {
    case credit
    case debit
    case estimate
}

We’ve defined an Invoice struct, and an enumeration called InvoiceKind. The Invoice struct uses the InvoiceKind enumeration for its kind property. We’re using access control in two places: on the kind property, which is public, and on the InvoiceKind enum, which is private.

The above code will produce the following error, for the kind property:

Property cannot be declared public because its type uses a private type

What’s going on? It’s simple: we’ve defined kind as public, but the type of kind is private. So, differently said, we want to give public access to the kind property, but also hide the implementation of the InvoiceKind enumeration.

That’s impossible! The InvoiceKind must have at least the same level as the kind property, given that the kind property cannot use a type that has a lower access level. You can’t make the property public, but its type private.

Access Levels In Practical iOS Development

All this theory begs the question: How are we’re going to use all this in practical, day-to-day iOS development? Let’s discuss that, next.

First, it’s important to understand the difference between designing an API, and using it. If you’re building an app, for yourself or your customers, you may define some APIs for your own use, but you’re mostly using APIs that other developers built.

As such, you don’t have to decide whether a class is public or private. Within your own project, the internal access level is perfectly OK. You need to know the difference between public and private though, in order to effectively use the APIs of other frameworks and libraries.

Then, by default, classes, functions, etc. are marked as internal. You can only use them in your app’s module, which is a sensible default for a single-target app. After all, you’re writing the code – so you can change it, too.

Conversely, if you’re building a library or framework, you want to guide how another developer uses your code. Take for instance the ID property we used before. You could make this property private, and create public getID() and setID(_:) functions, that also validate IDs as they’re added. You wouldn’t be able to validate an ID, when a developer chooses to use the ID property.

What if you’re working with more than one developer on the same project? You may have been asked to design the Invoice API, and you want to force other developers to use the getters and setters for the ID property, and not the property itself. The code you’re all writing is part of the same module. Again, you’re using access control to guide your fellow developers through effectively using the APIs you designed.

So, to conclude:

  • If you’re a library or framework author, you want to think about what components to make public, and what implementations to make private
  • If you’re working on your own app, you can stick with the internal default
  • If you’re working with more than one person on an app, and you’re designing an API, you want to think about what to make private or public
  • In any case, you want to know the difference between the access levels, so you can effectively use someone else’s APIs

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 13 apps with Swift 5 and Xcode 11.

Further Reading

When you think about it, access control, and access levels, is really about coordinating who gets access to what. It helps us to build modular software, by creating rules around APIs. Instead of using the ID property directly, we’re guiding you towards using the getID() and setID(_:) functions. If they’re public, that is…

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.