Upcoming and OnDemand Webinars View full list

Swift Access Control

Jeremy Sherman

Swift allows developers to control the context their types, methods, and
other names can be used from. Swift access control, as noted in Apple’s developer docs:

restricts access to parts of your code from code in other source files and modules. This feature enables you to hide the implementation details of your code, and to specify a preferred interface through which that code can be accessed and used.

Swift provides three levels of access:

  • public, meaning anyone can use the entity
  • internal, meaning any code in the same module (application or framework)
    can use the entity
  • private, meaning only code in the same source file can use the entity

Internal is Swift’s “do what I mean” default access level for all types.

Combining Types

There is one guiding principle: You can’t build a more public entity out of a more private entity.

On its face, this seems a natural rule. If I need to pass an object of a private type to a function,
there’d be no way for anyone to create that object unless they could
also call a private function. So the private wins out, and the function must be annotated private as well.

A Surprise

But working out the details of this principle across the many mechanisms for assembling instances of new types out of other types that Swift offers becomes quite complex.

This leaves room for surprising interactions. Take this seemingly innocuous UIViewController subclass as public, for example:

class ViewController : UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
  }
}

What happens if you open the view controller up to public access?

-class ViewController : UIViewController {
+public class ViewController : UIViewController {

Well, surprise!

ViewController.swift:4:19: Overriding instance method must be as accessible
as the declaration it overrides

Since the boilerplate for a new UIViewController includes quite a few
more overridden methods than that, that single word change in fact triggers
a wave of red compiler errors.

Oops.

Inheritance and Access Control

What happened here was an unpleasant interaction
between the default access level for a method
and how inheritance handles access levels.

A subclass must preserve the accessibility of its superclass’s methods
wherever the subclass might be used directly.
Otherwise, we would violate the substitution principle
that allows us to treat all kinds of UIViewController
as just another UIViewController.

Recall that the default access level is internal.
An internal type can’t have public methods,
so we couldn’t make the override public, anyway.
Anyone that could work with an instance of our internal type,
could also call our internal methods.
So the superclass’ public methods are still as accessible as they can be, and no harm is done.

As soon as we upgraded the class to public accessibility,
anyone could use our class. But not anyone could call an internal method,
and the default access level in a public class remains internal, so the compiler compels us to make the override public as well.

Guidelines Go Only So Far

We started with a seemingly simple guideline:
You can’t build a more public entity out of a more private entity.
But now we’ve wandered into the thick of it.
Inheritance and protocol conformance
lead to the most subtle interpretation of the guideline.
Most other forms of building new entities from others
yield to straightforward application of the guideline:

  • A tuple is as private as its most-private component type.
  • A function is as private as its most-private parameter or return type.
  • Methods and other things defined in the context of a class or struct
    factor in the access level of their context as another input to the
    “most private” calculation.

But classes and structs themselves have a lot going on.
Default initializers, superclasses, protocol conformance
and overrides all come together in these entities. You might be able to derive the resulting rules from the guideline,
but for these common cases, it’s likely faster to just learn the rules by rote:

  • Overrides can make a method more public, but not less.
  • A more public override can still call the less-public super variant,
    provided it would have access to it otherwise,
    without affecting its public-ness.
  • Protocols must actually have a single access level for all their contents.
    Conforming types must match the access level in their implementations,
    provided their access level allows it;
    you can’t have a public method on an internal or private type, after all.
  • New protocols that inherit from others can get more private, but not
    less. The same is true for class inheritance.
  • Class default initializers are subject to the default accessibility.
    There is no way for a public class to make its default initializer
    public save implementing it and marking it public.
  • Struct default initializers can only be as accessible as the most private
    of their fields.
    Again, there’s no way to make it more public;
    since this would actually require the caller to come up with an instance
    of that field, this rule at least is a straightforward application of the
    guideline.

Conclusion

Swift brings us access control driven by a guideline
we can colorfully paraphrase as “privacy spreads like the plague.”
This has some straightforward implications,
and some surprising ones.

If you want the word from the source, see The Swift Book. We’re also hard at work of a Swift programming guide of our own, and plan to publish it next year. Otherwise, you’ll internalize these fastest the classic way:
Head down, write code!

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project