Irbisa · Cheatsheet
OOP, SOLID & Design Patterns
OOP and SOLID in Swift, DRY, KISS and YAGNI, coupling and cohesion, dependency injection, GoF patterns, delegate, target-action, observer and coordinator, anti-patterns
What do DRY, KISS and YAGNI mean, and when does removing duplication make code worse?
What are coupling and cohesion, and how do they relate to the Single Responsibility Principle?
How do you implement a singleton in Swift, is it thread-safe, and what is wrong with singletons?
@MainActorfinal class SessionStore { static let shared = SessionStore() // created lazily, exactly once, even under contention private init() {} // nobody can create a second one private(set) var token: String?…How do you implement the delegate pattern in Swift, and why do Cocoa delegate methods pass the sender and use
will,didandshould?@MainActorprotocol RatingViewDelegate: AnyObject { // AnyObject, so the reference can be weak func ratingView(_ view: RatingView, shouldSelect stars: Int) -> Bool // asks first func ratingView(_ view: RatingView, didSelect stars: Int) // reports after}…Which SOLID principle does
CachedFeedLoaderbreak, and what happens to thefeed(from:)caller?protocol FeedLoader { func load(completion: @escaping (Result<[Post], Error>) -> Void) // contract: exactly once}struct CachedFeedLoader: FeedLoader { let cache: FeedCache let remote: FeedLoader func load(completion: @escaping (Result<[Post], Error>) -> Void) { completion(.success(cache.posts)) // cached posts at once remote.load(completion: completion) // then the fresh ones }}func feed(from loader: FeedLoader) async throws -> [Post] { try await withCheckedThrowingContinuation { continuation in loader.load { continuation.resume(with: $0) } }}What does Interface Segregation look like in Swift, and how can a protocol's default implementations hide a violation?
struct User { let id: String; var name: String } // before: every screen and every test double carries all of itprotocol UserService { func currentUser() async throws -> User func updateAvatar(_ jpeg: Data) async throws…What is the difference between dependency injection and dependency inversion, and which module should own the protocol?
// Feed module: the policy owns the abstraction and names it in its own termspublic struct Post: Sendable { public let id: String public init(id: String) { self.id = id }}…What is the difference between Factory Method and Abstract Factory, and where do you meet each in iOS code?
// Factory method: UIView asks its class which layer to create, a subclass decidesfinal class GradientView: UIView { override class var layerClass: AnyClass { CAGradientLayer.self } var gradientLayer: CAGradientLayer { layer as! CAGradientLayer }}…Do you need the Builder pattern in Swift, given default arguments and memberwise initializers?
// Java needs a builder for optional parameters; Swift has default argumentsstruct Toast { var message: String var icon: String? = nil var duration: Duration = .seconds(2) var isDismissible = true…What is the difference between Adapter and Facade, and how do you wrap a third-party SDK so it does not leak into your features?
// The interfaces the app wants, in the app's own termsprotocol SettingsStore { func bool(forKey key: String) -> Bool func set(_ value: Bool, forKey key: String)}…What is the Decorator pattern in Swift, how is it different from an extension or a subclass, and how does a Proxy differ from it?
protocol ImageLoading { func image(for url: URL) async throws -> UIImage} struct RemoteImageLoader: ImageLoading { func image(for url: URL) async throws -> UIImage {…A delegate has a single listener. How do you notify several observers without keeping them alive?
@MainActor protocol PlayerObserver: AnyObject { func player(_ player: AudioPlayer, didChange state: AudioPlayer.State)} @MainActor final class AudioPlayer { enum State { case idle, playing, paused }…What is Chain of Responsibility, and how can a deeply nested cell reach its view controller through the responder chain instead of a delegate?
@MainActor protocol CartActionHandling: AnyObject { func addToCart(productID: String)} extension UIResponder { // Walks up the chain (superviews, the controller, the window, the app) to the first match…What is the Strategy pattern, how do you express it in Swift, and how is it different from the State pattern?
protocol ShippingPolicy { func cost(for cart: Cart) -> Decimal} struct FlatRate: ShippingPolicy { let amount: Decimal…Find the bug: the user taps Stop while a video is still buffering, and a moment later it starts playing anyway.
@MainActor final class PlayerModel { private(set) var isBuffering = false private(set) var isPlaying = false private let engine: PlaybackEngine init(engine: PlaybackEngine) { self.engine = engine } func playTapped(_ url: URL) { isBuffering = true; engine.load(url) } func stopTapped() { isPlaying = false; engine.pause() } // The engine calls this once enough of the video has arrived func bufferingDidFinish() { isBuffering = false isPlaying = true engine.start() }}What is the Command pattern, and how does
UndoManageruse it to give an app undo and redo?struct Shape: Identifiable { let id = UUID(); var color: String } @MainActor final class Canvas { private(set) var shapes: [Shape] = [] let undoManager = UndoManager()…What is the wrong abstraction, how do you recognise one, and how do you back out of it without a rewrite?
// before: one row for every screen, and a flag for every new callerstruct ItemRow: View { let title: String var imageURL: URL? var price: Decimal? var isProfile = false // added for the profile screen…Which anti-patterns do you look for in an inherited iOS codebase, and how do you refactor out of a god object, singletons everywhere and a
BaseViewController?