Access Control Explained In Swift

Publish date: 2024-07-09
Back to blog
By Aasif Khan | December 27, 2021 5:45 pm  | 4-min read

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 tutorial, 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.

Table of Contents

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.

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

  • open and public — anyone can access, within the module, and in code that imports the module
  • internal — anyone can access, but only within the module (default)
  • — anyone can access, but only within the current Swift file
  • 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.

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

    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.

    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:

    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…


    Related Articles

    App Builder

    ncG1vNJzZmivp6x7orzPsqeinV6YvK57wJyanqujYrCwutOrpqVlpp7Aqq7IpaCtsV2oxKqy02afqK9dqbw%3D