Explain the access control levels in Swift.
TL;DR
Access control decides who can see your code.
From most open to most closed: open, public, package, internal, fileprivate, private.
The default is internal: visible in the same module only. Use the smallest level that works.
The six levels
| Level | Visible in | Notes |
|---|---|---|
open | Any module | Can be subclassed and overridden outside the module. Classes only |
public | Any module | Can be used, but not subclassed or overridden outside |
package | Modules in the same Swift package | Swift 5.9+ |
internal | Same module | The default |
fileprivate | Same file | |
private | Same declaration (and its extensions in the same file) |
A module is one target: your app, a framework, or a Swift package library.
open vs public
This is the most common question.
// In module NetworkKit
open class BaseClient {
open func headers() -> [String: String] { [:] }
public func send() { }
}
public class FinalClient { }// In the app module
import NetworkKit
class MyClient: BaseClient {
override func headers() -> [String: String] { ["Auth": "x"] } // OK, open
// override func send() { } // error: send is public, not open
}
// class Other: FinalClient { } // error: public class cannot be subclassed herepublic protects the library author. They can change internals without breaking subclasses they did not plan for.
private vs fileprivate
struct Account {
private var balance = 0
mutating func deposit(_ amount: Int) { balance += amount }
}
extension Account {
func describe() -> String { "Balance: \(balance)" } // OK, same file
}
// In the same file, but outside Account:
func peek(_ a: Account) -> Int {
// a.balance // error with private, OK with fileprivate
0
}Since Swift 4, extensions in the same file can see private members. So private is usually enough. Use fileprivate only when another type in the same file needs access.
private(set): read anywhere, write inside
final class Cart {
private(set) var items: [String] = []
func add(_ item: String) { items.append(item) }
}
let cart = Cart()
cart.add("Book")
print(cart.items) // OK
// cart.items = [] // error: setter is privateYou can mix levels: public private(set) var count = 0.
package
Useful when one Swift package has several modules that need to share code, but apps that use the package should not see it.
// In module CoreModels
package struct CacheKey { package let raw: String }
// In module Networking, same package: can use CacheKey
// In the app (outside the package): cannotRules you should know
- A type's members cannot be more visible than the type in practice. A
publicmethod on aninternaltype is still only internal. - A function cannot be more visible than its parameter and return types.
struct Secret { } // internal
// public func leak() -> Secret { } // error: Secret is internal@testable import MyApplets tests accessinternaldeclarations.- Default for members of a
publictype is stillinternal. You must mark each memberpublic.
Why it matters
- Smaller API surface. Fewer things can break.
- Clearer intent. Readers know what is meant for outside use.
- Better performance. The compiler can optimize
privateandfinalcode more, for example with static dispatch.
Common follow-up questions
- Can a struct be
open? No. Only classes and class members can beopen, because only classes support inheritance. - Does
@testable importgive access toprivate? No. Onlyinternaland above. - What is an access level on an
import? Since Swift 6 you can writeinternal import Fooorprivate import Foo. It stops the dependency from leaking into your public API. - What access level should protocol members have? They take the protocol's access level. You cannot mark requirements differently.
- Is
privatethe same as in Objective-C? No. Objective-C has no real access control for methods. Swift checks it at compile time.