Framework Access Levels in Swift

head image of Framework Access Levels in Swift - A Curious Journey

Hello fellow developers, Leo here. We will discuss Framework Access Levels in Swift and how that can leverage your framework code in your day-to-day life.

Today will be a quick topic about framework building and the difference between public and open access control levels.

This masterpiece was painted by Jacques Fouquier, Jacques Fouquières or Jacob Focquier (c. 1590/91 – 1655) was a Flemish landscape painter and was chosen because a castle has various access layers. Got it? The Hortus Palatinus, or Garden of the Palatinate, was a Baroque garden attached to Heidelberg Castle, Germany. The garden itself was commissioned by Frederick V, Elector Palatine in 1614 for his new wife, Elizabeth Stuart, and became famous across Europe during the 17th century. At the time it was known as the ‘Eighth Wonder of the World’, and has since been termed ‘Germany’s greatest Renaissance garden.’

Let’s go!

 

Problem – Framework Access Levels

You want to share your code outside the framework but you are getting errors like No such “class/struc/func/etc”.

First of all, we need a little background knowledge about access control in Swift. Swift provides default access control levels, this means that you don’t need to specify explicitly access control levels in a single-target app.

Swift’s access control model has two concepts: modules and source files.

A module is everything that is a single unit of code distribution, this means every time you can build something in Xcode, it is a module. A module can be an app bundle or even a framework, Xcode will both as modules. Modules can be imported by other modules with Swift’s import keyword.

A source file is literally the “.swift” file. You can create and define as much as you want structs/classes/enums in a single source file and all will share the same source file.

That said we can now explore the five Swift’s access levels.

 

Access levels

There are five access levels: open, public, internal, fileprivate, and private. Open access is the highest (least restrictive) access level and private access is the lowest (most restrictive) access level.

 

Private and File Private

Private access restricts the use of the entity in the enclosing declaration. For example, if you declare a “private let name: String” inside a struct Person, that property name will only be accessible from inside the Person struct. Setting properties and func private or fileprivate conforms to the Open/Close principle of SOLID as you can check in the example below:

class Person1 {
    private let name: String = ""
}

class Person2: Person1 {
    init() {
        print(name)
    }
} 

This will result in:

Framework Access Levels private example image

Like many other languages, Swift allows a class to override methods and properties declared in its superclasses. This means that the program has to determine at runtime which method or property is being referred to and then perform an indirect call or indirect access.

This technique, called dynamic dispatch, increases language expressivity at the cost of a constant amount of runtime overhead for each indirect usage

Do you remember that we said earlier that Swift access control has two contexts? It’s now that we will apply that because *fileprivate* access control level is all about the file that it’s declared. You define something file private like the example :

fileprivate let nameList: [String]

This means that nameList array will be available only in the file it belongs. If any other “.swift” file tries to use nameList it won’t be possible because nameList doesn’t exist outside the file scope. Very neat isn’t it?

That’s the reason why Xcode refactor tool always marks your extracted methods are marked with a fileprivate keyword. If the method was used by any other members (class/structs/enums) in the same file, your refactor will still work! Very good shortcut for developers.

Wrap-up: *private* is the most restrictive access level giving access only to the enclosing type and *fileprivate* is the second most restrictive giving access to anyone in the same file, not necessarily the same type.

 

Internal

Internal is the default access level. Everything you declare that you don’t explicitly declare an access level, the compiler will add an internal to it. Transforming *automagically* this:

let nameList: [String]

To this:

internal let nameList: [String]

The default access level is important because it declares that everything inside that module context ( remember the contexts?) can use/see the declaration. This means that if you have a framework and an app, and you don’t want that the framework could inspect inside your app, this feature comes by default. And that’s great because in the next sections we will explore the least restrictive access levels and why they are important.

Wrap-up :

All entities in your code (with a few specific exceptions, as described later in this chapter) have a default access level of internal if you don’t specify an explicit access level yourself.

 

Public and Open

Public and Open are special cases used only if you want to your declaration be accessible outside the module context. For example, you are creating a framework and need to expose the main APIs/Entities to anyone who imports it. Like the struct below:

public struct HttpsHeader {
    let autentication: String
    
    func printAllHeader() {
        print("allHeader")
    }
}

Image the code above in your framework. Outside the framework, anyone can see this Struct, but they can create this? This is a tricky question and we need some initializer knowledge to answer that.

A default initializer has the same access level as the type it initializes unless that type is defined as public. This is still valid for memberwise initializer of structs too, so for the code above to be used outside the framework we need to add:

public struct HttpsHeader {
    let autentication: String
    
    public init(autentication: String) {
        self.autentication = autentication
    }
    
    func printAllHeader() {
        print("allHeader")
    }
}

Now you can initialize the HttpsHeader struct outside the framework. As you might’ve been thinking right now the function printAllHeader is not visible outside the framework.

It’s important you notice that public and Open are both OPT-IN access levels. This is brilliantly designed by the Swift developers because this behavior will assure that everything you are exposing outside the module it’s wanted. You can’t unintentionally expose some internal function or properties. This is amazing and a very good language design choice.

Finally the open access control level. The open keyword is used only by class or class members. And that differs from public access because any class marked as open allows the code outside the module to subclass and override its features. It’s important to be very cautious using open keyword because if your framework depends on some feature implemented in that class if anyone subclass/override it, will the framework be able to work properly? These concerns don’t arise with the public keyword because any class market with public access level can’t be subclassed.

 

Principles Of Access Control Levels

As the docs say:

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

This is the most important part of understanding all of Swift’s access control. This drives all the compiler decisions and you should take that into account when choosing your entities/properties’ access control level. You don’t need to know everything about Swift by heart, because it’s impossible and it’s not productive but I’m sure that if really understand this principle your coding way will be way *smoooooother*.

This principle leads to various curious situations.

 

Curious situations – Framework Access Levels in Swift

For functions this code won’t compile:

public struct HttpsHeader {
    let autentication: String
    
    public init(autentication: String) {
        self.autentication = autentication
    }
    
    func printAllHeader(privateClass: PrivateClass) { // This won't compile
        print()
    }
    
    private class PrivateClass {
        
    }
}
 

Resulting:

result of a wrong use of Framework Access Levels

This means that the function can’t have an access level least restrictive that its parameters. And the result type is included in that rule too:

public struct HttpsHeader {
    let autentication: String
    
    public init(autentication: String) {
        self.autentication = autentication
    }
    
    func printAllHeader() -> PrivateClass { // This won't compile too with the same problem above
        return PrivateClass()
    }
    
    private class PrivateClass {
        
    }
}

To compile you need to add private keyword to the function declaration too.

The tuple follows this principle too and this shouldn’t compile:

“`

struct HttpsHeader {
    public let autentication: String
    let httpsTuple: (PublicClass, FilePrivateClass)
    
    fileprivate class FilePrivateClass {}
    public class PublicClass {}
}

You have to give the httpsTuple property a fileprivate keyword to work. A tuple is a special case that it doesn’t have an access level it is automatically attributed to it in compile time following the same principle rule. The tuple final access level will be equal to the most restrictive access level among all the tuple types.

Talking about extensions, when you set an extension with an explicit control access level that makes a new default access level for all properties, functions, and members inside that extension.

You can mark an extension with an explicit access-level modifier (for example, private) to set a new default access level for all members defined within the extension. This new default can still be overridden within the extension for individual-type members.

And we are done!

 

Continue Architecture Studying

It is very nice to know other design patterns as well. I would recommend checking this article about the chain of responsibility design pattern. In that article, you will learn how to implement yourself a pattern that is the foundation for the UIKit framework.

And talking about architecture, you could be also interested in how to use coordinator pattern with tab bars in iOS development. It is amazing how fine-grained control you can achieve when you abstract the flow of the app to a coordinator pattern.

Summary – Framework Access Levels in Swift

Today we study a little bit about access levels and some curiosities about them. Also, we discover the guiding principle of all Swift’s language access controls decisions and how can you reason about the levels on your own code. It’s possible that if you only work with a single-target app, you never come across public, open, or even internal because you ever needed them. Because of SOLID principles, it’s more common to use the private and fileprivate ones.

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

Credits: image

Share this post:

Related posts

Sponsor