Swift OOP & Protocols
Irbisa · cheatsheetSeptember 13, 2026

Swift OOP & Protocols

Classes, structs, enums, protocols, extensions, protocol-oriented programming

Junior Developer20 itemscompressed for a skim
  1. 01

    What is the difference between struct and class in Swift?

    Easy

    A struct is a value type and a class is a reference type; almost every other difference follows from that one.

    // Struct — value semantics
    struct Point {
      var x: Int
      var y: Int
      // memberwise init synthesized for free
      // mutating method — required because struct is value type
    …
  2. 02

    What are protocols in Swift and how are they different from interfaces?

    Easy

    A protocol declares a contract — methods, properties, associated types, initializers — that conforming types must implement.

    protocol Greetable {
      var name: String { get }
      func greet() -> String
    }
    
    // Default implementation via extension
    …
  3. 03

    Explain enums with associated values and raw values in Swift.

    Medium

    A Swift enum is a full type that can carry data, which is why it models a closed set of alternatives far better than a set of integer constants.

    // Raw values
    enum HTTPStatus: Int {
      case ok = 200
      case notFound = 404
      case serverError = 500
    }
    …
  4. 04

    What are extensions in Swift and what can/can't they do?

    Medium

    Extensions add functionality to an existing type — your own types, standard library types, even types imported from a framework — without subclassing and without touching the original source.

    // Add helpers to stdlib types
    extension String {
      var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) }
      var isBlank: Bool { trimmed.isEmpty }
    }
    …
  5. 05

    What is protocol-oriented programming and how is it different from class inheritance?

    Medium

    Protocol-oriented programming (POP) — a Swift idiom championed at WWDC 2015 — composes behavior via small protocols and default implementations rather than deep class hierarchies.

    // Tiny composable protocols
    protocol Entity { var id: String { get } }
    protocol Persistable { func save() throws }
    protocol Loggable { func log() -> String }
    
    extension Loggable where Self: Entity {
    …
  6. 06

    Explain access control levels in Swift.

    Medium

    Swift has six access levels, and a member is always capped by the visibility of the type that contains it.

    // In a framework target
    open class Widget { /* subclassable from apps */ }
    public class Sealed { /* visible everywhere, NOT subclassable from outside */ }
    
    public struct User {
      public var name: String          // visible
    …
  7. 07

    What are property observers and lazy properties?

    Medium

    Swift lets a stored property carry behaviour in two ways: observers that fire around a change, and lazy, which defers the initializer until the first read.

    // Observers
    class Profile {
      var name: String = "" {
        willSet { print("Will change \(name) → \(newValue)") }
        didSet { if name != oldValue { rebuildHeader() } }
      }
    …
  8. 08

    Walk through class initialization in Swift: designated, convenience, and what the two phases are.

    Medium

    Swift will not let you touch self until every stored property in the whole inheritance chain has a value, and the designated versus convenience split is the rule system that guarantees it.

    class Vehicle {
      let wheels: Int
      var name: String
    
      init(wheels: Int, name: String) {   // designated
        self.wheels = wheels
    …
  9. 09

    What does your own type need before it can be a Dictionary key or live in a Set?

    Easy

    It has to conform to Hashable, which means being Equatable as well and hashing in a way that agrees with equality.

    struct Tag: Hashable {          // == and hash(into:) both synthesized
      let name: String
      let colorHex: String
    }
    
    var seen: Set<Tag> = []
    …
  10. 10

    Why is a delegate property almost always declared weak, and what must the protocol declare for that to compile?

    Medium

    Because the delegate normally owns the object that calls it, so a strong reference back would close a cycle and leak both sides forever.

    @MainActor
    protocol UploaderDelegate: AnyObject {          // ✅ class-bound, so weak is legal
      func uploader(_ uploader: Uploader, didProgress fraction: Double)
      func uploader(_ uploader: Uploader, didFinish url: URL)
      func uploader(_ uploader: Uploader, didFail error: Error)
    }
    …
  11. 11

    A method lives only in a protocol extension, and a conforming type defines its own version. Which one runs?

    Hard

    It depends on the type the compiler sees at the call site, because a method that is not a protocol requirement is dispatched statically rather than through the conformance.

    protocol Greeter {
      func greet() -> String        // ✅ a requirement — dynamically dispatched
    }
    
    extension Greeter {
      func greet() -> String { "hello from the default" }
    …
  12. 12

    What is the difference between static and class members, and what does final change?

    Medium

    A class member can be overridden by a subclass and a static member cannot — static is simply the final form of a type-level declaration.

    class Formatter {
      static let shared = Formatter()          // lazily initialized once, thread-safe
    
      static func staticName() -> String { "Formatter" }   // cannot be overridden
      class func kind() -> String { "generic" }            // subclasses may override
    …
  13. 13

    Your struct's method will not compile until you add one keyword, and the identical method on a class needs nothing — what does that keyword do?

    Easy

    mutating marks a method that changes the value, and the mechanism behind it is that self is passed inout — the write lands in the caller's storage instead of in a copy.

    struct Counter {
      private(set) var value = 0
      mutating func bump() { value += 1 }        // self is inout under the hood
      func peek() -> Int { value }               // no mutation, no keyword
    }
    …
  14. 14

    A protocol declares var id: String { get set }, your struct stores it as a let, and the conformance fails — what is the compiler asking for?

    Easy

    { get set } requires a property you can write to, and a let can only be read — make it a var, or supply a computed property with both accessors.

    protocol Identified {
      var id: String { get }            // read-only requirement
      var title: String { get set }     // must be writable
      static var kind: String { get }
    }
    …
  15. 15

    You copy a struct into a second view model, change one field on the copy, and the original changes too — how is that possible for a value type?

    Medium

    One of the struct's stored properties is a class reference: the copy duplicated the reference, not the object, and both structs now point at the same instance.

    final class Draft {                    // reference type hiding inside a value type
      var text: String
      init(text: String) { self.text = text }
    }
    
    struct Note {
    …
  16. 16

    You are modelling payment methods that each behave differently — enum with associated values, or a protocol with one type per method?

    Medium

    Enum when you own the whole set of cases and want the compiler to find every place that must change; protocol when code you do not control has to add a case you will never see.

    struct Receipt { let id: UUID }
    
    // Closed set: the compiler polices every switch
    enum PaymentMethod {
      case card(last4: String, expiry: Date)
      case applePay(token: String)
    …
  17. 17

    Can a subclass override a stored property, and what happens to the superclass's storage when it does?

    Medium

    It can override the access — with a computed property or with observers — but never the storage: the superclass's stored property is still there, and the override normally has to read and write it through super.

    class Vehicle {
      var speed: Double = 0                          // the storage lives here, always
      var summary: String { "moving at \(speed)" }
    }
    
    final class Train: Vehicle {
    …
  18. 18

    You add an initializer to a protocol, a non-final class conforms, and the compiler demands required — what is that rule protecting?

    Medium

    Conformance is inherited by every subclass, so each subclass is also a conformer — and required is the only way the compiler can guarantee they all really have that initializer.

    protocol Buildable {
      init(id: String)
    }
    
    final class Session: Buildable {            // final: no subclasses, no keyword
      let id: String
    …
  19. 19

    A type conforms to two protocols that each ship a default implementation of refresh() — what does Swift do, and how do you resolve it?

    Hard

    Swift has no linearization of protocol inheritance, so it refuses to guess: unless one default is strictly more specialized than the other, you get an error until the concrete type supplies its own implementation.

    protocol Reloadable { func refresh() }
    protocol Pollable { func refresh() }
    
    extension Reloadable { func refresh() { print("from cache") } }
    extension Pollable { func refresh() { print("from network") } }
    …
  20. 20

    You need a type that owns a file descriptor, closes it exactly once, and cannot be silently copied — what does Swift give you?

    Hard

    A noncopyable struct — struct FileHandle: ~Copyable — which suppresses the implicit Copyable conformance, gives the value exactly one owner, and lets a struct declare deinit for the first time.

    import System
    
    struct FileHandle: ~Copyable {
      private let fd: Int32
    
      init(openingAt path: String) throws {
    …