Category: 03. Object Model

https://cdn-icons-png.flaticon.com/512/1705/1705340.png

  • Swift Equatable & Comparable

    Swift Equatable & Comparable

    Adopt Equatable and Comparable so your types support == and ordering operations.


    Derived vs Custom Conformance

    Derived types inherit conformance automatically, while custom conformance requires explicit implementation.

    Example

    struct Point: Equatable, Comparable {
      var x: Int, y: Int
      static func == (lhs: Point, rhs: Point) -> Bool { lhs.x == rhs.x && lhs.y == rhs.y }
      static func < (lhs: Point, rhs: Point) -> Bool { (lhs.x, lhs.y) < (rhs.x, rhs.y) }
    }
    
    let a = Point(x: 1, y: 2), b = Point(x: 1, y: 3)
    print(a == b) // false
    print(a < b)  // true


    Sorting with Comparable

    Conform to Comparable to sort custom types and use min()/max().

    Example

    struct Score: Comparable {
      let user: String
      let value: Int
      static func < (l: Score, r: Score) -> Bool { l.value < r.value }
    }
    
    let scores = [Score(user: "A", value: 10), Score(user: "B", value: 5), Score(user: "C", value: 7)]
    let sorted = scores.sorted()
    print(sorted.map { $0.value })
    print(sorted.last!.value)
  • Swift Value Semantics & COW

    Prefer value semantics for predictability.

    Swift collections use Copy-on-Write (COW) to keep copies cheap until mutation.


    Syntax

    var a = [1,2,3]

    Arrays, sets, and dictionaries use value semantics.


    Copy-on-Write

    Swift collections use Copy-on-Write (COW) to keep copies cheap until mutation.

    Example

    var a = [1,2,3]
    var b = a // shares storage
    b.append(4) // triggers copy for b only
    print(a) // [1,2,3]
    print(b) // [1,2,3,4]
  • Swift Deinitializers

    Swift Deinitializers

    Run cleanup code before a class instance is deallocated using deinit.


    deinit

    Deinitializers are called automatically when an instance is deallocated.

    Syntax: deinit { ... }

    Example

    class FileHandle {
      init() { print("open") }
      deinit { print("close") }
    }
    
    var h: FileHandle? = FileHandle()
    h = nil // prints "close"

  • Swift Initializers

    Swift Initializers

    Create and customize object setup with designated, convenience, and memberwise initializers.


    Memberwise and Custom Init

    Structs get a memberwise initializer by default.

    You can also define custom initializers.

    Syntax: init(params) { self.prop = ... }

    Example

    struct User { var name: String; var age: Int }
    let a = User(name: "Morgan", age: 30) // memberwise
    
    struct Point {
      var x: Int, y: Int
      init(_ x: Int, _ y: Int) { self.x = x; self.y = y }
    }
    let p = Point(1, 2)

    This example shows a memberwise initializer for a simple struct and a custom initializer with positional parameters.


    Class Designated vs Convenience

    Classes use designated initializers to fully initialize stored properties and convenience initializers to provide shortcuts.

    Syntax:

    • init(...) (designated)
    • convenience init(...) (must call self.init)

    Example

    class Person {
      let name: String
      let age: Int
      init(name: String, age: Int) {
    
    self.name = name; self.age = age
    } convenience init(name: String) { self.init(name: name, age: 0) } } let p1 = Person(name: "Robin", age: 30) let p2 = Person(name: "Elisabeth")

    The convenience initializer provides a default for age by delegating to the designated initializer.



    Failable and Throwing Init

    Use failable initializers to return nil on invalid input; use throwing initializers to signal errors.

    Syntax:

    • init?
    • init throws

    Example

    struct Email {
      let value: String
      init?(_ s: String) { if s.contains("@") { value = s } else { return nil } }
    }
    
    enum InitError: Error { case invalid }
    struct Port {
      let number: Int
      init(_ n: Int) throws { guard (1...65535).contains(n) else { throw InitError.invalid }; number = n }
    }
  • Swift Access Control

    Swift Access Control

    Restrict visibility of types and members with publicinternalfileprivate, and private.


    Levels

    Swift’s defaults are safe by design: internal is the default.

    Syntax:

    • public (visible to other modules)
    • internal (module-only, default)
    • fileprivate (this file)
    • private (this scope type/extension)

    Example

    public struct APIClient {
      public init() {}
      public func request() {}
    }
    
    struct Repository { // internal by default
      fileprivate var cache: [String: String] = [:]
      private func reset() { cache.removeAll() }
    }

    This example shows public APIs, a module-internal type, a file-scoped property, and a private helper.



    Types and Members

    A member cannot be more visible than its enclosing type.

    Syntax:

    • public struct S { internal var x: Int } (valid)
    • but not internal struct S { public var x: Int }

    Example

    internal struct Box { // whole type is internal
      public var value: Int // warning/error: member more visible than type
    }
  • Swift Extensions

    Swift Extensions

    Add functionality to existing types without subclassing, including methods, computed properties, and protocol conformances.


    Computed Properties and Methods

    Extend any type to add helpers that are scoped to that type.

    Syntax: extension Type { var computed: T { ... }; func util() { ... } }

    Example

    extension String {
      var isBlank: Bool { trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
      func repeated(_ n: Int) -> String { String(repeating: self, count: n) }
    }
    
    print("  ".isBlank)    // true
    print("Hi".repeated(3)) // HiHiHi

    This example adds a computed property and a helper method to String.


    Protocol Conformance in Extensions

    Adopt protocols outside the original type declaration to separate concerns.

    Syntax: extension Type: Protocol { ... }

    Example

    protocol Describable { func describe() -> String }
    
    struct User { let name: String }
    
    extension User: Describable {
      func describe() -> String { "User(\(name))" }
    }
    
    print(User(name: "Morgan").describe())

    This example adds Describable conformance to User via an extension.



    Nesting by Responsibility

    Organize large types using multiple extensions grouped by feature (e.g., networking, formatting).

    Syntax:

    • extension Type { /* Feature A */ }
    • extension Type { /* Feature B */ }

    Example

    struct Article { let title: String; let body: String }
    
    extension Article { // Formatting helpers
      var preview: String { String(body.prefix(40)) + "..." }
    }
    
    extension Article { // Networking stub
      static func fetchAll() -> [Article] { [] }
    }
  • Swift Generics

    Swift Generics

    Write reusable code with type parameters and constraints to ensure correctness without duplication.


    Generic Functions

    Generics let you write flexible, reusable functions and types.

    Use a placeholder type name like T and constrain when needed.

    Syntax:

    • func name<T>(_ a: T) -> T
    • struct Box<T> { ... }

    Constraints:

    • T: Comparable
    • where clauses for more complex bounds.

    Example

    func swapTwo<T>(_ a: inout T, _ b: inout T) {
      let tmp = a
      a = b
      b = tmp
    }
    
    var x = 1, y = 2
    swapTwo(&x, &y)
    print(x)
    print(y)

    This example defines a generic T-typed swap function and shows it working with integers.



    Generic Constraints (where)

    Constrain generic parameters to types that meet certain requirements, like Comparable, using where clauses.

    Example

    func minValue<T: Comparable>(_ a: T, _ b: T) -> T { a < b ? a : b }
    
    print(minValue(3, 7))        // 3
    print(minValue("b", "a"))  // a
  • Swift Protocols

    Swift Protocols

    Define behavior contracts that types adopt, and extend them to add default implementations.


    Defining and Conforming to Protocols

    protocol defines a blueprint of methods and properties.

    Types adopt a protocol by providing implementations.

    Syntax:

    • protocol P { var x: Int { get set }; func f() }
    • struct S: P { ... }

    Example

    protocol Greetable { func greet() -> String }
    
    struct Person: Greetable {
      var name: String
      func greet() -> String { "Hello, \(name)" }
    }
    
    let p = Person(name: "Swift")
    print(p.greet())

    This example demonstrates how a protocol is defined and adopted by a type, and how the type provides an implementation for the protocol’s method.

    Tip: Use protocol extensions to provide default method implementations.


    Protocol Extensions (Default Implementations)

    Provide default behavior for conforming types by adding implementations in a protocol extension.

    Example

    protocol Describable { func describe() -> String }
    
    extension Describable {
      func describe() -> String { "(no description)" }
    }
    
    struct User: Describable { let name: String }
    
    struct Car: Describable {
      let model: String
      func describe() -> String { "Car: \(model)" }
    }
    
    let u = User(name: "Morgan")
    let c = Car(model: "SwiftMobile")
    print(u.describe())
    print(c.describe())

    This example gives User a default describe() and overrides it in Car.



    Protocols with Associated Types

    Use associatedtype to make a protocol generic over a placeholder type.

    Conformers bind the placeholder to a concrete type.

    Example

    protocol Container {
      associatedtype Element
      mutating func append(_ item: Element)
      var count: Int { get }
      subscript(i: Int) -> Element { get }
    }
    
    struct IntStack: Container {
      private var items: [Int] = []
      mutating func append(_ item: Int) { items.append(item) }
      var count: Int { items.count }
      subscript(i: Int) -> Int { items[i] }
    }
    
    func allItemsMatch<C1: Container, C2: Container>(_ c1: C1, _ c2: C2) -> Bool
    where C1.Element == C2.Element, C1.Element: Equatable {
      guard c1.count == c2.count else { return false }
      for i in 0..<c1.count { if c1[i] != c2[i] { return false } }
      return true
    }
  • Swift Polymorphism

    Swift Polymorphism

    Treat related types uniformly via inheritance or protocol conformance.

    Override methods and rely on dynamic dispatch.


    Basic Polymorphism

    Use inheritance to treat related types uniformly.

    Example

    class Animal { func speak() { print("...") } }
    class Dog: Animal { override func speak() { print("Woof") } }
    class Cat: Animal { override func speak() { print("Meow") } }
    
    let animals: [Animal] = [Dog(), Cat()]
    animals.forEach { $0.speak() }


    Protocol Polymorphism

    Use a protocol to treat unrelated types uniformly.

    Example

    protocol Speaker { func speak() }
    struct Dog: Speaker { func speak() { print("Woof") } }
    struct Cat: Speaker { func speak() { print("Meow") } }
    
    let speakers: [Speaker] = [Dog(), Cat()]
    speakers.forEach { $0.speak() }
  • Swift Inheritance

    Subclass and Override

    Use override to override a superclass method.

    Example

    class Animal { func speak() { print("...") } }
    class Dog: Animal { override func speak() { print("Woof") } }
    let a = Animal(); a.speak()
    let d = Dog(); d.speak()


    Call super

    Use super to extend a superclass method when overriding.

    Example

    class Animal { func speak() { print("...") } }
    class Dog: Animal {
      override func speak() {
    
    super.speak()
    print("Woof")
    } } let d = Dog() d.speak()