I remember the the day I first learned about the internal
access modifier when reading The Swift Programming Language.
At the time, I was coding a take-home test for a job at Revolut. I was desperate to show how clever I was for knowing this keyword (which is synthesised by the compiler automatically). Subsequently, like a puppy marking its territory, I pissed internal
on every (API) surface I saw, in the hope that Storonsky-senpai would notice me.
Later, I worked with a… well, a Staff Engineer but because we were in consulting she was called “Senior Manager”. She’d modularised our codebase - a novel concept for me at the time - and our ModelKit
module was chock-full of properties marked:
public private(set) variableName: VariableType
As intimidated as I was, I swore that one day I would be intelligent enough wield access control keywords with the same finesse as she.
And one day, I was.
In order to protect the good people at Revolut from code snippets like the following abomination, I’m going to explain how to apply each access control level in Swift, and make you indistinguishable from the mighty Staff Engineer.
I do give Revolut big kudos for actually responding to my pathetic tech test with some helpful feedback, so you should apply for a job at Revolut if you need time away from your spouse. Revolut, please sponsor my Substack.
🗝️ Access control keywords 🗝️
Access control in Swift, like most languages, has a hierarchy:
Most restrictive
private
fileprivate
internal
public
open
Least restrictive
Quick aside for beginners: Why do we need access control?
Access control keywords like
public
andprivate
define what other code is able to see the code you write.It’s important to be thoughtful about this, because your programs become a lot easier to understand when, for instance, a given part of your code is only allowed to use a small number of functions and see a small number of variables.
Just think how complicated things would become if every part of your code could talk to every other part of your code!
🔒 Don't Peek at My Privates 🔒
The private
keyword is the exclusive VIP club - invisible to anything outside the scope of the entity in which it’s defined (by entity I mean any struct, enum, actor, or class).
If you’re new to Swift, when in doubt, go private! The Swift compiler will correct you if anything outside your entity needs access.
🖤 fileprivate: The Black Sheep 🖤
The fileprivate
keyword is the ‘quirky’ cousin of private who only really gets invited to big family gatherings. It denotes visibility scoped in a file. There are more useful keywords out there, but hey, it's family.
📦 internal: Implicit but not forgotten 📦
The internal
keyword is pretty vanilla - but trust me, we’re getting to the good part! The Swift compiler synthesises this ‘implicitly’ for every entity, method, and property - that means it’s free!
In Kotlin, the public
keyword is the one that’s synthesised implicitly, and internal
needs to be explicitly denoted on a property, entity, or method. Oh, and they’re called visibility modifiers, which makes my “invisible to” metaphor work a lot better.
…then why does the internal
keyword exist at all? 🤔
Read and find out!
🧩 One More Piece of the Puzzle 🧩
Quick history lesson.
Back in the Objective-C days, you didn’t just write a property for a class. You also had to write
get()
andset()
mini-methods explaining how to fetch and change the value.Now - I’ll blow your mind here - Swift does this too.
get()
andset()
methods are synthesised for all the properties of your entity, and these methods areinternal
by default.If you want to enjoy yourself, this StackOverflow thread from 2014 contains a bunch of iOS 7 devs with their minds blown that Swift 1.0 was doing this automatically.
🤯 private(set): wait, WHAT?! 🤯
private(set)
in your Swift code is identical to writing:
internal private(set)
This means the synthesised get()
method is internal
, But the set()
method synthesised by the Swift compiler is actually private
!
Being able to fine-tune access control differently for getters and setters like this is great for maintainability.
private(set)
means that code outside your entity (in the same module) can read the property, but aren’t allowed to modify it - that can only be done in the entity itself. This is extremely useful, for example, when you want to enforce unidirectional data flow in your UI - you might have a view model which calls some APIs to set its own properties, with multiple views that can observe changes to it without modifying them.
🚀 Going public 🚀
Now we come to the star of the show - the public
modifier, which allows a property, method, or entity to be accessed from anywhere in your codebase, even across different modules.
But beware! With great power comes great responsibility. Overuse of public
can lead to spaghetti code that’s hard to maintain and reason about. Thoughtful interface design starts to get real here.
If you aren’t modularising your app, or building an SDK, then you might not be writing public
much yourself - but rest assured that every library and framework (third-party and provided by Apple) is using them in every single API you can access!
💃 Shaking Things Up with public private(set) 🕺
We’ve come full circle. Before, I was just the Junior Engineer. Now, I come to this modifier as a Lead Engineer.
This marks the getter of a property as public
and the setter as private
. This means it can be read by anything that uses the module, but can only be changed by the entity in which it is declared.
A fairly common use case might be when implementing the Repository design pattern - wrapping a group of retrieval methods for a database or an API; which multiple modules might want access to.
The Repository design pattern is an abstraction layer between data sources and business logic, centralising data access and promoting code maintainability. It decouples data retrieval and storage from the rest of the application, allowing for easier data source swapping, testing, and separation of concerns.
🛠️ Fine-Tuned Control with public internal(set) 🛠️
This is largely similar to public private(set)
, albeit somewhat less glamorous - allowing anything in the module to modify it. Which approach to use depends on your specific use case and the way you’ve designed your modules.
Essentially, public internal(set)
is useful when you have a property that should be mutable within a module or SDK but immutable to external consumers.
The big win here, however, is we’ve uncovered the one situation where explicitly writing the internal
keyword is absolutely necessary for your code to work!
🌐 Embracing Extensibility with open 🌐
Last but not least, we have the open
keyword. This access control level is used with classes and their methods, enabling subclassing and overrides outside the module.
It’s a lot less commonly used in Swift because the language is oriented around composable protocols instead of subclasses. However, you might run into situations where you need a shared set of properties in a view model, subclassed by separate screens or features in different modules which specialise them.
Frankly though, that type of situation has happened to me once in the last 4 years, and eventually, after requirements changed 3 times, I realised setting it up in a composable way with protocols would have been preferable. You live and you learn.
And there you have it!
You're now the boss of access control in Swift. Whether you're working on a single-view app or a vast SDK, this knowledge will help keep your code clean, organised, and secure.
Remember, start restrictive and gradually loosen the access control as needed. By carefully considering the appropriate level of access for each property and method, you can ensure a clean and organised codebase that's easy to understand, modify, and test.
Now that you’ve reached the end…
…I hereby dub thee the title of Staff Engineer*
*This title is valid at any of the companies that sponsor my Substack. You can actually render this list of sponsors yourself by typing
<html><body><ul><li></li><ul></body></html>
into any text editor, and opening it in your favourite browser.