Video summary

iOS Dev Job Interview - Must Know Topics

Main summary

Key takeaways

Educational

Main ideas, concepts, and lessons

1) iOS networking basics with REST APIs + JSON (async/await + Swift)

Most iOS apps need to:

  • Fetch data from a server
  • Decode JSON responses into Swift models
  • Update the UI to display results

JSON basics

  • JSON objects use curly braces {} for key/value pairs.
  • JSON can also return arrays [] for lists of objects.

GitHub API example

  • Use the GitHub REST API documentation to find the correct endpoint URL.
  • Use curl to test endpoints and inspect real JSON responses.

Step-by-step methodology for building a network call

Step 1: Build UI with dummy data

  • Create the SwiftUI UI using placeholder/dummy values.
  • Determine which fields you need on screen (e.g., avatar URL, login/username, bio).
  • With third-party APIs, you’re limited to whatever data the API returns.

Step 2: Create Swift models from the JSON

  • Create a struct model (e.g., GitHubUser).
  • Make it conform to Codable.
  • Ensure JSON property names match Swift property names (or handle differences).
  • Common “gotchas”:
    • JSON might use snake_case (e.g., avatar_url)
    • Swift typically uses camelCase (e.g., avatarURL)
    • Bridge naming styles with:
      • JSONDecoder.keyDecodingStrategy = .convertFromSnakeCase

Step 3: Write the networking code

  • Define a function like getUser(...) that is:
    • async (for concurrency)
    • throws (because network errors are common)
  • Use:
    • URLSession.shared.data(from: url) (GET request)
  • Request/response handling:
    • Convert the endpoint string to a URL (and throw if invalid).
    • Check HTTP status code:
      • Expect 200 for success
      • Otherwise throw a custom error like invalidResponse
    • Decode JSON:
      • Create a JSONDecoder
      • Configure snake_case decoding strategy if needed
      • Decode into the model:
        • decoder.decode(GitHubUser.self, from: data)
    • Use do/try/catch to throw errors such as:
      • invalid URL
      • invalid response
      • invalid data (decode failure)
  • Introduce the idea of a custom error enum (e.g., GHError).

Step 4: Connect networking to the UI

  • In SwiftUI, trigger the async call using .task { ... }.
  • Store results in @State (e.g., @State private var user: GitHubUser?).
  • On success:
    • Assign the returned model to state so the UI updates automatically.
  • On failure:
    • Catch specific errors (invalid URL/response/invalid data) and handle appropriately.
  • UI binding examples:
    • Use optional-coalescing placeholders for missing user fields.
    • Use AsyncImage to load avatar images from a URL with a placeholder while loading.
  • Demonstrated that changing the endpoint username updates the displayed data.

2) Interview concept: Classes vs structs (value types vs reference types)

Core rule

  • Classes = reference types
  • Structs = value types

Value type behavior (struct)

  • Assigning/copying a struct creates a copy.
  • Mutating the copy doesn’t affect the original.
  • Analogy: copying/emailing an Excel spreadsheet to someone else.

Reference type behavior (class)

  • Multiple variables reference the same underlying object.
  • Mutating via one reference affects all references.
  • Analogy: a shared Google Sheets document.

When to use which

  • Use classes when you need inheritance or reference semantics.
  • Use structs for lightweight/value semantics (highlighted as especially common in SwiftUI).

3) Generics (and the “balancing act”)

  • Generics let you write type-safe code that works across many types.
  • Generic syntax pattern:
    • func ...<T: SomeProtocol>(...) -> ... (angle brackets + constraints)
  • Example idea:
    • Constrain T to Comparable so you can compare values of the same type.
  • Standard library example:
    • Arrays are generic (e.g., Array<Element>).

Real-life example shown

  • A generic network fetch pattern that decodes any Decodable type:
    • Fetch User → decode User.self
    • Fetch Repository → decode Repository.self
    • With a generic function → decode into T.self

Balancing act lesson

  • Don’t add generics everywhere just for “future-proofing.”
  • Prefer concrete types unless generics clearly reduce repetition (described as a “slam dunk”).

4) Closures, shorthand, and trailing closure syntax

  • Closures are blocks of functionality that can be passed around (like functions).

Structure

  • Parameters in parentheses (with types)
  • Return type after parentheses
  • Body inside { ... }

Closure shorthand

  • $0, $1, etc. correspond to parameters when parameter names are omitted.

Trailing closure syntax

  • Used when the closure is the last argument to a function.
  • Common in SwiftUI and Swift standard library APIs.

5) Escaping closures + memory management concerns (retain cycles)

  • Closures marked @escaping can outlive the function that accepted them.
  • In network calls:
    • The completion closure must persist until the async work finishes.

Retain cycle risk

  • Closures may capture self to update UI afterward.
  • Use weak self (or similar) to avoid retain cycles and memory leaks.

6) Functional programming: filter / map / reduce / compactMap / flatMap

  • Big idea: these are “shorthand for loops” over collections.

  • filter

    • Keeps elements that match a condition (predicate returns true/false).
  • map
    • Transforms each element (e.g., extract a property or compute a new value).
  • reduce
    • Combines all elements into one value.
    • Typically uses an initial value + operator.
  • compactMap
    • Removes nil values from an array of optionals (map + filter out nil).
  • flatMap
    • Flattens an array of arrays into one array.
    • Can include transformation inside the flatMap closure.

7) Sets vs Arrays (collection choice)

Arrays

  • Preserve order
  • Allow duplicates
  • Lookups/mutations cost O(n)

Sets

  • No duplicates
  • Unordered
  • Require Hashable
  • Lookups/mutations are effectively constant time (conceptually “much faster”)

Set operations demonstrated

  • intersection
  • subtracting
  • isDisjoint
  • union
  • symmetricDifference
  • isSubset(of:), isSuperset(of:)
  • insert, remove, contains

8) Optionals: if let, guard let, nil-coalescing, force unwrap, optional chaining

  • Optionals represent “value or nil.”

Unwrapping strategies

  • if let
    • Works within the conditional scope; can lead to nesting (“pyramid of doom”).
  • guard let
    • Early exit pattern; avoids deep nesting.
    • Makes the unwrapped variable available for the rest of the function scope.
  • Nil-coalescing
    • value ?? default for a fallback.
  • Force unwrap
    • value! crashes if nil (generally avoid except in safe/controlled cases).
  • Optional chaining
    • When the whole object is optional (e.g., optionalUser?.name).
    • If the base is nil, the result becomes nil (to be handled with defaults or if let).

9) Unit testing (what/why/how)

  • A unit test checks a small function/module (“factory”) to ensure correct output.

Benefits

  • Prevent regressions
  • Increase confidence during refactoring
  • Enable faster team work safely

Key lesson

  • Tests help only when you cover edge cases you can foresee.

Example shown (tip calculator)

  • Happy path test:
    • Arrange inputs, Act to compute, Assert expected output
  • Edge case test:
    • Negative input should return nil
  • Uses XCTest assertions like:
    • XCTAssertEqual
    • XCTAssertNil

Practical note

  • Testing ROI depends on product/team size and how frequently code changes.
  • Not all-or-nothing: test critical/unchanging logic more selectively.

10) Singleton pattern (pros/cons + when to use)

  • Singleton = only one instance, globally accessible (e.g., UserDefaults.standard).

Use when

  • You truly need exactly one shared instance; multiple instances would break correctness (e.g., persisted settings).

Pros

  • Uniqueness
  • Convenience/global access

Cons

  • Global accessibility can create tangled dependencies (“spiderweb”).
  • Testing becomes harder because code depends on shared global state.

Implementation pattern

  • static let shared = ...
  • private init() to prevent external creation.

11) Dependency injection (DI)

  • Dependency injection provides dependencies from outside rather than creating them inside the object.

Benefits

  • Clearer data flow (more linear)
  • Better separation of concerns
  • Improved testability (swap real networking with mocks)
  • Easier replacement of components

Example shown

  • Parent view creates NetworkManager and Bag.
  • Child view model receives them via initializer injection.

12) Delegates + protocols (one-to-one communication)

  • Delegate pattern: one object communicates specific events to another via a protocol.
  • Protocol = “job description” (list of commands, e.g., didSelectProduct(name:imageName:)).
  • Delegate property in the sender (destination VC) is set to the receiver (main VC).

Flow described

  • Main VC presents bottom sheet (product selection VC).
  • Product selection VC calls delegate method when the user picks an option.
  • Main VC updates UI accordingly.

Emphasis

  • Protocol defines capability.
  • Delegate is the reference to the object that handles the callback.

13) View controller lifecycle methods (high-level ordering + use cases)

  • Lifecycle methods are called automatically by the system.

Key ones

  • viewDidLoad
    • When the content view is created/loaded in memory (configure outlets guaranteed here).
  • viewWillAppear
    • Just before the view is added to the view hierarchy on screen.
  • viewDidAppear
    • After the view is in hierarchy; good for animations.
  • viewWillDisappear
    • Just before the view is removed; good for saving work.
  • viewDidDisappear
    • After removal from hierarchy.

Layout-related

  • viewWillLayoutSubviews and viewDidLayoutSubviews
    • Called when bounds change (e.g., rotation), before/after subviews are laid out.

14) Concurrency + threading basics (main vs background, queues, GCD)

  • Concurrency = multiple tasks at the same time (enabled by multi-core CPUs).

Threads

  • Main thread handles UI and must stay responsive.
  • Background threads handle heavy tasks.

Queues

  • Serial queue
    • Tasks execute one-by-one in order; predictable but slower.
    • Avoids race conditions but can limit throughput.
  • Concurrent queue
    • Tasks can start in order but complete unpredictably.
    • Faster, but you must avoid dependencies on completion order.

GCD usage example

  • DispatchQueue.main.async { ... } for UI updates after background work.
  • Mentioned DispatchQueue.global(...) for background work (manual).

Race condition concept

  • Concurrent execution order can break logic when tasks depend on each other.

15) Automatic Reference Counting (ARC) + retain cycles + weak references

  • ARC tracks strong references to objects.
  • Objects deallocate when the strong reference count reaches zero.

Retain cycle

  • Two objects strongly reference each other, preventing deallocation.

Fix

  • Make one side a weak reference (or other non-strong reference).

Demonstrated example

  • With Person and MacBook:
    • Strong mutual references prevent deinit
    • Making owner weak allows both to deallocate

Speakers or sources featured

  • The video appears to be taught by Shawn (referenced as “Shawn” with links like shawnis.teachable.com and shawnallen.teachable.com).
  • Source material explicitly referenced:
    • GitHub REST API documentation (for endpoints and example JSON responses)
  • Apple frameworks/documentation implicitly referenced:
    • URLSession, JSONDecoder/Codable, SwiftUI, UIKit lifecycle, GCD, ARC

Original video