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

Middle Developer · 18 questions

  1. 01

    What do DRY, KISS and YAGNI mean, and when does removing duplication make code worse?

    DRY: every piece of knowledge has one authoritative place.

  2. 02

    What are coupling and cohesion, and how do they relate to the Single Responsibility Principle?

    Cohesion is how strongly the parts of one type or module belong together; coupling is how much one type depends on another's details.

  3. 03

    How do you implement a singleton in Swift, is it thread-safe, and what is wrong with singletons?

    A static let shared plus a private init().

    @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?…
  4. 04

    How do you implement the delegate pattern in Swift, and why do Cocoa delegate methods pass the sender and use will, did and should?

    Declare a class-bound protocol for the events, give the delegating object a weak var delegate, and call it at the right moments.

    @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}…
  5. 05

    Which SOLID principle does CachedFeedLoader break, and what happens to the feed(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) }  }}

    Liskov substitution: callers of FeedLoader rely on its promise of exactly one completion, and CachedFeedLoader calls it twice.

  6. 06

    What does Interface Segregation look like in Swift, and how can a protocol's default implementations hide a violation?

    No client should depend on methods it does not use.

    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…
  7. 07

    What is the difference between dependency injection and dependency inversion, and which module should own the protocol?

    Dependency injection is a technique: an object receives its collaborators instead of creating them.

    // 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 }}…
  8. 08

    What is the difference between Factory Method and Abstract Factory, and where do you meet each in iOS code?

    A factory method is one overridable method that creates an object, so a subclass or conformer decides the concrete type.

    // 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 }}…
  9. 09

    Do you need the Builder pattern in Swift, given default arguments and memberwise initializers?

    Rarely in its classic form: default arguments and memberwise initializers already solve the telescoping-constructor problem a Java builder exists for.

    // 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…
  10. 10

    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?

    An adapter makes one existing type fit an interface your code expects; a facade puts one simple interface in front of a whole subsystem.

    // 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)}…
  11. 11

    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?

    A decorator conforms to the same protocol as the object it wraps and adds behaviour around each call, so caching, logging or retry stack per instance.

    protocol ImageLoading {  func image(for url: URL) async throws -> UIImage} struct RemoteImageLoader: ImageLoading {  func image(for url: URL) async throws -> UIImage {…
  12. 12

    A delegate has a single listener. How do you notify several observers without keeping them alive?

    Keep the observers in a collection of weak references, such as NSHashTable.weakObjects(), and loop over a snapshot when something changes.

    @MainActor protocol PlayerObserver: AnyObject {  func player(_ player: AudioPlayer, didChange state: AudioPlayer.State)} @MainActor final class AudioPlayer {  enum State { case idle, playing, paused }…
  13. 13

    What is Chain of Responsibility, and how can a deeply nested cell reach its view controller through the responder chain instead of a delegate?

    A request travels along a chain of handlers until one handles it, and the sender never learns which one did.

    @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…
  14. 14

    What is the Strategy pattern, how do you express it in Swift, and how is it different from the State pattern?

    Strategy puts each variant of an algorithm behind one interface, and the caller picks or swaps the variant at runtime, so the code that uses it never switches over the variants.

    protocol ShippingPolicy {  func cost(for cart: Cart) -> Decimal} struct FlatRate: ShippingPolicy {  let amount: Decimal…
  15. 15

    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()  }}

    stopTapped() never ends the buffering, and bufferingDidFinish() starts playback without checking what the user wants now.

  16. 16

    What is the Command pattern, and how does UndoManager use it to give an app undo and redo?

    Command turns a request into an object that can be stored, queued, logged or reversed later.

    struct Shape: Identifiable { let id = UUID(); var color: String } @MainActor final class Canvas {  private(set) var shapes: [Shape] = []  let undoManager = UndoManager()…
  17. 17

    What is the wrong abstraction, how do you recognise one, and how do you back out of it without a rewrite?

    An abstraction is wrong when its callers must pass flags or special cases to get their own behaviour out of it.

    // 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…
  18. 18

    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?

    God objects, singletons used as hidden dependencies and a deep BaseViewController are the first three I look for.