Irbisa · Cheatsheet

Swift OOP & Protocols

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

Junior Developer · 24 questions

  1. 01

    What is the difference between a struct and a class in Swift?

    Beyond copy vs share, a struct has no inheritance, deinit or identity, and a class has no memberwise initializer, mutating or automatic Sendable.

  2. 02

    What are the main principles of OOP, and how does Swift express each one?

    Encapsulation, inheritance, polymorphism and abstraction; Swift supports all four, but leans on protocols and value types where classic OOP languages lean on class hierarchies.

  3. 03

    What is the difference between overloading and overriding, and what can a subclass override?

    Overloading is several functions with the same name and different signatures, chosen by the compiler at the call site; overriding is a subclass replacing an inherited implementation, chosen at runtime by the object's actual class.

    class Shape {  var name: String { "shape" }  func area() -> Double { 0 }  final func describe() -> String { "\(name): \(area())" }}…
  4. 04

    What is a protocol in Swift, and how does it differ from an interface or an abstract class?

    A protocol is a contract of methods, properties, initializers and associated types that classes, structs and enums can all adopt, even types you do not own; unlike an abstract class it holds no stored state.

    protocol Named {  var name: String { get }               // a let or a var satisfies it} protocol Greeter: Named {                // protocol inheritance  var greeting: String { get set }       // needs something writable…
  5. 05

    What is the difference between raw values and associated values in a Swift enum?

    A raw value is one fixed constant of the same type for every case, known at compile time; an associated value is a payload attached to a case when you create it, and each case can carry different types.

    enum HTTPStatus: Int {  case ok = 200, notFound = 404}let status = HTTPStatus(rawValue: 404)   // Optional(.notFound)print(HTTPStatus.ok.rawValue)            // 200…
  6. 06

    What can an extension add to a type in Swift, and what can it not?

    An extension adds computed properties, methods, initializers, subscripts, nested types and protocol conformances to any type, even one you do not own; it cannot add stored properties or override existing members.

    extension String {  var isBlank: Bool { trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }} extension Array where Element: Numeric {      // constrained extension  func sum() -> Element { reduce(0, +) }…
  7. 07

    What access levels does Swift have, and what is the difference between public and open?

    Swift has six levels: open, public, package, internal (the default), fileprivate and private; public and open are both visible to other modules, but only open lets those modules subclass a class or override a member.

    // Inside a framework moduleopen class BaseViewController {               // apps may subclass and override  open func configure() {}} public class Logger {                         // apps may use, not subclass…
  8. 08

    What is a lazy property, how does it differ from a computed and an observed property, and can it be computed more than once?

    A lazy var runs its initializer on first read and caches the result, while willSet/didSet run code around every assignment; lazy computes once, unless two threads hit the first read at the same time.

    final class ProfileHeader {  var name: String {    willSet { print("about to show \(newValue)") }    didSet {      guard name != oldValue else { return }   // observers fire even for equal values      setNeedsLayout()…
  9. 09

    What does mutating do, and why do struct methods need it while class methods do not?

    mutating marks a struct or enum method that changes self: self is passed inout, so the change is written back into the caller's variable.

    struct Counter {  private(set) var value = 0  mutating func bump() { value += 1 }} enum Step {…
  10. 10

    What do Equatable and Hashable require, and what happens when two different values have the same hash?

    Equatable requires ==, and Hashable adds hash(into:) with one rule: equal values must produce equal hashes.

    struct Tag: Hashable {                  // == and hash(into:) synthesized  let name: String  let colorHex: String} struct User: Hashable, Identifiable {…
  11. 11

    This delegate declaration does not compile. What is the error, and how do you fix it?

    protocol DownloaderDelegate {  func downloader(_ downloader: Downloader, didFinish text: String)} final class Downloader {  weak var delegate: DownloaderDelegate?   func finish(with text: String) {    delegate?.downloader(self, didFinish: text)  }}

    weak works only with class instances, and nothing stops a struct from conforming to this protocol, so the compiler reports 'weak' must not be applied to non-class-bound 'any DownloaderDelegate'.

  12. 12

    What is deinit, when is it called, and on which thread does it run?

    deinit is a class's cleanup method, called automatically just before ARC frees the instance, when the last strong reference goes away.

    final class ImageLoader {  private var task: Task<Void, Never>?   func start() { task = Task { /* download */ } }   deinit {…
  13. 13

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

    Both declare members on the type rather than on an instance, but a class member can be overridden by a subclass and a static one cannot: in a class, static means class final.

    class Formatter {  static func make() -> Formatter { Formatter() }   // cannot be overridden  class var kind: String { "generic" }             // subclasses may override  // class var cache = 0      // error: class stored properties not supported}…
  14. 14

    How does class initialization work in Swift: designated vs convenience initializers, the two phases, and failable inits?

    A designated initializer gives every stored property of its class a value and then calls a designated initializer of the superclass; a convenience initializer must call another initializer of the same class.

    class Vehicle {  let wheels: Int  var name: String   init(wheels: Int, name: String) {      // designated: own properties, then up    self.wheels = wheels…
  15. 15

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

    Protocol-oriented programming builds behaviour from small protocols with default implementations in extensions instead of a tree of base classes.

    protocol Loggable { var label: String { get } } extension Loggable {  func log() { print(label) }}…
  16. 16

    What does this code print, and why do the two calls behave differently?

    protocol Greeter {  func greet() -> String} extension Greeter {  func greet() -> String { "default greet" }  func shout() -> String { "default shout" }}struct Robot: Greeter {  func greet() -> String { "beep" }  func shout() -> String { "BEEP" }} let robot: any Greeter = Robot()print(robot.greet())print(robot.shout())

    It prints beep and then default shout.

  17. 17

    Can a Swift protocol have optional methods? Compare @objc optional with a default implementation.

    Only an @objc protocol can mark requirements optional, and then only classes can conform; in pure Swift you get the same effect with a default implementation in a protocol extension.

    @objc protocol ChartDataSource {  func numberOfPoints() -> Int  @objc optional func title() -> String     // may be missing} final class SalesChart: NSObject, ChartDataSource {…
  18. 18

    Why does this struct not compile, and how would you model a linked list or a tree instead?

    struct Node {  let value: Int  var next: Node?} var head = Node(value: 1, next: Node(value: 2, next: nil))

    A struct is stored inline, so a Node containing an optional Node would need infinite size; the compiler reports "value type 'Node' cannot have a stored property that recursively contains it".

  19. 19

    When would you model variants with an enum with associated values, and when with a protocol and one type per variant?

    Use an enum when you own the whole set of cases and want the compiler to find every place that must change; use a protocol when new variants must be added without touching existing code, for example by another module.

  20. 20

    Find the bug: after the 2.3 update, orders cached by the previous version show the wrong status.

    enum OrderStatus: Int, Codable {  case draft      // added in 2.3  case paid  case shipped  case delivered} struct Order: Codable {  let id: Int  var status: OrderStatus} // Orders are cached on disk between launchesfunc loadCachedOrder(from data: Data) throws -> Order {  try JSONDecoder().decode(Order.self, from: data)}

    Implicit Int raw values are positions: inserting draft at the top shifts every case by one, so a cached 1 that meant shipped in 2.2 now decodes as paid, and old delivered orders show as shipped.

  21. 21

    Why must a non-final class mark the initializer that satisfies a protocol's init requirement as required?

    Every subclass inherits the conformance, so every subclass must also have that initializer, and required is how the compiler enforces it.

    protocol Buildable {  init(id: String)} class Node: Buildable {  let id: String…
  22. 22

    How does method dispatch work in Swift (static, vtable, witness table and message dispatch), and what does each cost?

    Swift uses the cheapest dispatch a declaration allows: direct calls for value types and final members, a vtable for overridable class methods, a witness table for protocol requirements, and Objective-C messaging only for @objc dynamic.

  23. 23

    Find the bug: pull-to-refresh calls refreshAll, but the feed screen never reloads.

    @MainActor protocol Refreshable: AnyObject {  func refresh()}extension Refreshable {  func refresh() {}  // default: nothing to reload} class BaseViewController: UIViewController, Refreshable {} final class FeedViewController: BaseViewController {  func refresh() { reloadFeed() }} @MainActor func refreshAll(_ screens: [any Refreshable]) {  screens.forEach { $0.refresh() }}

    BaseViewController conforms using the extension's empty default, so the protocol's witness table points at that default.

  24. 24

    A type conforms to two protocols that each provide a default refresh(). What does Swift do, and how do you resolve it?

    If neither default is more specific, Swift does not guess: either the conformance fails or the call is ambiguous, and the fix is to implement refresh() on the type itself.

    protocol Reloadable { func refresh() }protocol Pollable { func refresh() }extension Reloadable { func refresh() { print("from cache") } }extension Pollable { func refresh() { print("from network") } } struct Feed: Reloadable, Pollable {…