Patterns already inside Apple's frameworks

design · memo

In one line: You use GoF patterns daily: the responder chain is Chain of Responsibility, target–action/UndoManager is Command (+ Memento), NotificationCenter/KVO/Combine are Observer, class clusters are Abstract Factory, appearance() is a Proxy, a ViewModifier a Decorator. Senior answer = pattern + API + mechanism + trade-off.

Download PDF Print view LaTeX source

PatternWhere Apple put itMechanismSay it like this
Chain of Resp.responder chain: UIResponder.next; sendAction(_:to: nil, from:for:)selector not implemented? pass to next until someone does, or the chain endsCopy/Paste find a handler by walking the chain.
Command +Mementotarget–action, UIAction, UIKeyCommand, ObjC NSInvocation; UndoManagerrequest stored as data, run later; the undo closure captures the old value = the mementoUndoManager is a command stack over snapshots.
ObserverNotificationCenter · KVO · Combine @Published · @Observable (iOS 17)notify subscribers you don’t know; @Observable tracks the properties a body readbroadcast → KVO → typed streams → access tracking
Delegation +Strategytable delegate + data source; UICollectionViewLayout; keyDecodingStrategyweak helper called at fixed hooks; a layout object = a whole algorithm, swapped at runtimeDelegate: many hooks, one owner. Layout: Strategy.
Abstract Factoryclass clusters: NSArray, NSNumber, NSString, NSDictionarypublic abstract class; init returns a private subclass (__NSArrayI, __NSArrayM)Apple documents clusters as Abstract Factory.
ProxyUIAppearance: UINavigationBar.appearance(); ObjC NSProxyproxy records setters, applies them when a view enters a windowNot the bar — a recorder.
DecoratorSwiftUI ViewModifier, .padding(), .background()each call wraps: ModifiedContent<Content, Modifier>, nestedThe wrapping is in the type — order matters.
CompositeUIView/CALayer trees, SwiftUI view tree, UIMenu(children:)leaf + container share one interface; operations recurseHit-test walks it down, the chain up.
AdapterUIViewRepresentable (UIKit→SwiftUI), UIHostingController (SwiftUI→UIKit)makeUIView/updateUIView; its Coordinator adapts UIKit delegatesImperative view behind a declarative protocol.
IteratorSequence + IteratorProtocol, AsyncSequencemakeIterator(), then next() until nilfor-in is sugar for it.
Visitor-likeCodable: JSONEncoder().encode(v) → v.encode(to:) → c.encode(x, forKey:)hop 1 dispatches on the value’s type, hop 2 on the encoder’s (= format)Double dispatch: one encode(to:), any format.
Chain→StrategyURLProtocol; config.protocolClasses; URLProtocol.registerClassfirst class whose canInit(with:) says true loads it (registered: reverse order)How I stub the network in tests.
PrototypeNSCopying: copy(), mutableCopy()copy a configured instance; Foundation copies are shallowSwift: var b = a.
SingletonUIApplication.shared vs shared instances (URLSession.shared, FileManager.default)only UIApplication is one per process (UIApplicationMain); others you may createShared instance ≠ singleton; I still inject it.

Also (other sheets): Template Method = VC lifecycle, layoutSubviews · Factory Method = class var layerClass · Facade = URLSession · Flyweight = UIImage(named:) · Interpreter = NSPredicate · Object Pool = cell reuse.

Picture — the responder chain, exact rules

Patterns already inside Apple's frameworks — figure 1

Touches start at the hit-tested view; keys, menus and nil-target actions at the first responder. Scene step: Apple forum answer — the article still says window → app.

Interview traps

  • Hit-test (down) ≠ responder chain (up): two walks over one Composite.
  • A nil-target action nobody implements does nothing — no crash, no log.
  • appearance() changed while a view is on screen: no effect until it re-enters a window.
  • type(of: NSArray()) is a private subclass — never compare a cluster’s class.
  • URLSession.shared is a shared instance, not a singleton.

Remember

Chain walks up · Command stores the request · Observer fans out · Delegate answers back · Cluster hides the subclass · Proxy records · Modifier wraps · Codable dispatches twice.

Example — drive the patterns yourself

// Chain: nil target -> first responder -> up ("hide keyboard")
UIApplication.shared.sendAction(#selector(UIResponder
  .resignFirstResponder), to: nil, from: nil, for: nil)
// Chain -> Strategy: URLProtocol stubs the network in tests
final class StubProtocol: URLProtocol {
  nonisolated(unsafe) static var body = Data()
  override class func canInit(with r: URLRequest) -> Bool { true }
  override class func canonicalRequest(for r: URLRequest)
    -> URLRequest { r }
  override func startLoading() {
    client?.urlProtocol(self, didLoad: Self.body)
    client?.urlProtocolDidFinishLoading(self) }
  override func stopLoading() {} }
let cfg = URLSessionConfiguration.ephemeral
cfg.protocolClasses = [StubProtocol.self]
// Command + Memento: the closure captures the OLD value
@MainActor func rename(_ doc: Doc, to new: String,
                       undo: UndoManager) {
  let old = doc.title                     // the memento
  undo.registerUndo(withTarget: doc) { d in   // use d, not doc
    rename(d, to: old, undo: undo) }      // = registers redo
  undo.setActionName("Rename"); doc.title = new }

Doc: a final class with var title. Registering inside the undo handler is what makes redo work — during undo, UndoManager puts new registrations on the redo stack.

Likely questions

  1. A GoF pattern in UIKit? — responder chain = Chain of Responsibility.
  2. How does Copy find its handler? — nil-target action up the chain.
  3. Class cluster? — Abstract Factory: public abstract type, private concretes.
  4. Decorator in SwiftUI? — ViewModifier → ModifiedContent.
  5. Stub URLSession? — a URLProtocol in protocolClasses.
  6. Undo/redo in UIKit? — UndoManager: a Command stack; closures capture the old value.
  7. Is Codable a Visitor? — the same double dispatch: value type × encoder type.