internal is the default access level when nothing is specified — visible throughout the defining module, but not beyond it. fileprivate restricts visibility to the same source file; private restricts it further, to the same enclosing declaration (and, since Swift 4, extensions of that type in the same file). public makes something usable from another module, but NOT subclassable or overridable there; open is a genuinely separate, stronger level specifically permitting subclassing and overriding across module boundaries — a distinction most languages with public/private access control don't make at all, since they conflate "can use" and "can extend" into one level.
The five levels, from most to least restrictive
Without any modifier at all, internal is what's actually in effect — code anywhere else in the SAME module can use this class freely, but nothing outside that module can see it at all.
private class Secret { } // same enclosing declaration/file only
fileprivate class FileHelper { } // same source file only
class DefaultInternal { } // no modifier — internal by default, module-wide
public class PublicAPI { } // usable from other modules
open class ExtendableAPI { } // usable AND subclassable from other modulesopen vs public: the real, distinctive difference
A framework author marking a class public but not open is making a deliberate statement: use this class freely, but don't subclass it — a genuinely useful constraint public alone can't express, since it only controls usage, not extension.
// In a framework module:
public class Logger {
public func log(_ message: String) { print(message) }
}
open class Plugin {
open func execute() { }
}
// In a DIFFERENT module that imports this framework:
let logger = Logger() // fine — public allows usage
// class MyLogger: Logger { } // COMPILE ERROR: Logger is public, NOT open — can't subclass
class MyPlugin: Plugin { // fine — Plugin is open, subclassing across modules is allowed
override func execute() { }
}private and fileprivate: fine-grained same-file control
Since Swift 4, private is also visible to extensions of the SAME type within the same file — letting a type's implementation be split across an extension for organization, without needing the broader fileprivate.
class BankAccount {
private var balance: Double = 0 // visible only within BankAccount itself (and its extensions)
}
extension BankAccount {
func deposit(_ amount: Double) {
balance += amount // fine — same-file extension can see 'private' since Swift 4
}
}