Singleton Pattern
Part of the Object Oriented Programming section of Coddy's Swift journey. Lesson 42 of 57.
A design pattern is a proven solution to a common design problem. The singleton pattern makes sure a class has exactly one instance, reachable from anywhere. In Swift it is a static let holding the instance, plus a private init so nobody can create another:
final class Settings {
static let shared = Settings()
var theme = "light"
private init() {}
}
Settings.shared.theme = "dark"
print(Settings.shared.theme)Output:
darkEvery use of Settings.shared refers to the same object, so a change made in one part of the program is visible everywhere. static let is created lazily on first use and safely even when several threads start at once:
final class Counter {
static let shared = Counter()
private(set) var hits = 0
private init() {}
func hit() { hits += 1 }
}
func pageA() { Counter.shared.hit() }
func pageB() { Counter.shared.hit(); Counter.shared.hit() }
pageA()
pageB()
print(Counter.shared.hits)
print(Counter.shared === Counter.shared)Output:
3
trueBecause the initializer is private, Settings() does not compile outside the class, which keeps the "only one" promise:
// let other = Settings() // error: 'Settings' initializer is inaccessible due to 'private' protection levelUse singletons sparingly. A singleton is global state: any code can change it, and tests cannot easily start from a clean copy. They fit things that are truly one per program, such as a logger or the app's configuration; passing an object as a parameter is often clearer.
// clearer to test: the logger is passed in
func process(order: Order, log: Logger) { log.write("processing \(order.id)") }Challenge
EasyComplete the singleton Logger. It has a static let shared instance, a private initializer, a private(set) array of entries, and log(_:level:), which stores [INFO] message or [ERROR] message (level is info or error). errorCount is a computed property. The supplied code calls Logger.shared from two different functions, so both must reach the same instance.
The supplied code reads lines info,message or error,message, sends info lines through handleInfo and error lines through handleError, then prints every entry and errors: 1.
Your code goes in Logger.swift and Functions.swift. main.swift holds the supplied input/output code and cannot be edited.
Try it yourself
// Supplied input/output code: keep it as it is
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
let p = line.split(separator: ",", maxSplits: 1).map(String.init)
if p[0] == "error" { handleError(p[1]) } else { handleInfo(p[1]) }
}
for e in Logger.shared.entries { print(e) }
print("errors: \(Logger.shared.errorCount)")
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesMethods and selfInitializersClasses Are ReferencesRecap - Library Card4Encapsulation
Access ControlRead-Only From OutsideGuarding StateFailable InitializersRecap - Bank Account7Polymorphism and Generics
PolymorphismType CastingGeneric FunctionsGeneric TypesRecap - Shape Calculator5Inheritance
SubclassesOverriding MethodsInitializers and superfinal and HierarchiesRecap - Employee Hierarchy11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Swift playground