Swift interviews test your depth in optionals, value vs reference types, memory management (ARC), protocols, async/await concurrency, and SwiftUI. This guide covers 50 common questions — with concise answers and code examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Basics | let/var, type inference, optionals, String interpolation |
| Optionals | Optional binding, guard, nil coalescing, force unwrap |
| Types | Value vs reference, struct vs class vs enum, protocols |
| Memory | ARC, strong/weak/unowned, retain cycles |
| Closures | Capture lists, @escaping, trailing closure, @autoclosure |
| Concurrency | async/await, actors, Task, structured concurrency |
| Protocols | Protocol-oriented programming, Codable, Hashable |
| Error handling | throws, do-catch, Result, try? / try! |
| SwiftUI | @State, @Binding, @ObservedObject, @EnvironmentObject |
| Advanced | Property wrappers, result builders, Sendable, @MainActor |
Basics
1. What is the difference between let and var?
let name = "Alice" // constant — immutable
var count = 0 // variable — mutable
count = 1 // OK
name = "Bob" // compile error
let enforces immutability at the binding level. For value types (struct, enum), the entire value is frozen. For reference types (class), the reference is frozen but the object's properties can still change (unless also declared let).
2. How does Swift's type inference work?
let x = 42 // inferred as Int
let y = 3.14 // inferred as Double
let flag = true // inferred as Bool
let name = "Swift" // inferred as String
let nums = [1, 2, 3] // inferred as [Int]
The compiler determines the type from the initial value. Explicit annotation is required when inference is ambiguous or you want a different type:
let score: Float = 3.14 // explicitly Float, not Double
let empty: [String] = [] // can't infer from empty literal
3. What are optionals and why does Swift have them?
var name: String? = "Alice" // can hold String or nil
name = nil // valid
var required: String = "Bob" // can NEVER be nil — compile error if assigned nil
Optionals make the absence of a value explicit at the type level, eliminating the null pointer exceptions common in ObjC and Java. The compiler forces you to handle nil before using the value.
4. What are the ways to unwrap an optional?
var value: String? = "hello"
// 1. Optional binding (safe)
if let v = value {
print(v)
}
// 2. Guard (safe — exits scope on nil)
guard let v = value else { return }
print(v)
// 3. Nil coalescing (provides default)
let result = value ?? "default"
// 4. Optional chaining (propagates nil)
let count = value?.count // Int? — nil if value is nil
// 5. Force unwrap (unsafe — crashes if nil)
let forced = value!
// 6. Implicit unwrapping (use sparingly)
var implicit: String! = "hello"
print(implicit) // unwrapped automatically
| Method | Safe? | Use when |
|---|---|---|
if let |
Yes | Need conditional branch |
guard let |
Yes | Want to exit on nil |
?? |
Yes | Have a sensible default |
?. |
Yes | Chaining optional access |
! |
No | 100% certain it's non-nil |
String! |
Risky | IBOutlets, deferred init |
5. What is guard and when do you use it over if let?
func process(name: String?) {
guard let name = name else {
print("no name")
return // must exit scope
}
// name is non-optional from here on
print("Hello, \(name)")
}
guard is preferred for early exit / precondition checking at the top of a function. The unwrapped binding stays in scope for the rest of the function. if let is for branching where both paths are valid.
6. What is the nil coalescing operator ???
let username: String? = nil
let display = username ?? "Anonymous" // "Anonymous"
// Chains
let a: Int? = nil
let b: Int? = nil
let c: Int? = 42
let result = a ?? b ?? c ?? 0 // 42
Returns the left side if non-nil, otherwise evaluates and returns the right side. Short-circuits — right side is not evaluated if left is non-nil.
Value Types vs Reference Types
7. What is the difference between struct and class in Swift?
| Feature | struct |
class |
|---|---|---|
| Type | Value | Reference |
| Memory | Stack (usually) | Heap |
| Inheritance | No | Yes |
| ARC | No | Yes |
| Mutability | Needs mutating |
Methods can mutate freely |
| Copying | Deep copy | Shallow (shared reference) |
deinit |
No | Yes |
| Memberwise init | Auto-generated | Not generated |
struct Point { var x: Int; var y: Int }
var a = Point(x: 0, y: 0)
var b = a // copy
b.x = 10
print(a.x) // 0 — a is unchanged
class Node { var value: Int; init(_ v: Int) { value = v } }
var n1 = Node(1)
var n2 = n1 // same reference
n2.value = 99
print(n1.value) // 99 — both point to same object
Use struct for data models, coordinates, configurations. Use class for identity-based objects, shared state, UIKit components.
8. What is Copy-on-Write (CoW)?
Swift's standard collections (Array, Dictionary, Set, String) use lazy copying: the buffer is shared until a mutation occurs.
var a = [1, 2, 3]
var b = a // same buffer, no copy yet
b.append(4) // copy triggered here — a still [1,2,3]
CoW gives value semantics without performance penalty for read-only copies.
9. When should you use enum vs struct vs class?
// enum: finite set of cases, often with associated values
enum Result<T> {
case success(T)
case failure(Error)
}
// struct: data model, no identity needed
struct User { let id: UUID; var name: String }
// class: shared mutable state, reference identity matters
class NetworkManager {
static let shared = NetworkManager()
private init() {}
}
Swift enums are powerful — they can have methods, computed properties, and associated values.
Protocols
10. What is protocol-oriented programming (POP)?
POP is Swift's answer to the limitations of class inheritance. Protocols define capabilities; protocol extensions provide default implementations.
protocol Drawable {
func draw()
}
extension Drawable {
func draw() { print("default draw") } // default implementation
func highlight() { print("highlight") } // free behaviour for all conformers
}
struct Circle: Drawable { }
struct Square: Drawable {
func draw() { print("drawing square") } // overrides default
}
Structs can conform to multiple protocols, enabling composition over inheritance.
11. What is Codable and how does it work?
Codable is a type alias for Encodable & Decodable. The compiler auto-synthesises conformance when all properties are themselves Codable.
struct User: Codable {
let id: Int
let name: String
let email: String
}
// Decode from JSON
let json = """{"id":1,"name":"Alice","email":"a@b.com"}""".data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)
// Encode to JSON
let data = try JSONEncoder().encode(user)
// Custom keys
struct Product: Codable {
let productName: String
enum CodingKeys: String, CodingKey {
case productName = "product_name" // snake_case ↔ camelCase
}
}
12. What is the Hashable protocol?
struct Point: Hashable {
let x: Int
let y: Int
// Hashable auto-synthesised when all stored properties are Hashable
}
let points: Set<Point> = [Point(x: 0, y: 0), Point(x: 1, y: 2)]
let dict: [Point: String] = [Point(x: 0, y: 0): "origin"]
Types conforming to Hashable can be used as Set elements or Dictionary keys. Auto-synthesis works when all stored properties are Hashable.
13. What is Equatable?
struct Money: Equatable {
let amount: Decimal
let currency: String
// auto-synthesised == when all stored properties are Equatable
}
let a = Money(amount: 10, currency: "EUR")
let b = Money(amount: 10, currency: "EUR")
print(a == b) // true
Custom implementation needed when you want to ignore some properties:
struct User: Equatable {
let id: UUID
var lastSeen: Date
static func == (lhs: User, rhs: User) -> Bool { lhs.id == rhs.id }
}
Closures & Functions
14. What is a closure in Swift?
A closure is a self-contained block of code that captures values from its surrounding scope.
let greet = { (name: String) -> String in
return "Hello, \(name)!"
}
// Shorthand forms
let double: (Int) -> Int = { $0 * 2 }
let sum: (Int, Int) -> Int = { $0 + $1 }
// Trailing closure syntax
[1, 2, 3].map { $0 * 10 }
15. What is @escaping and when is it needed?
var completions: [() -> Void] = []
func register(completion: @escaping () -> Void) {
completions.append(completion) // outlives the function call → @escaping
}
// Non-escaping (default): closure called before function returns
func perform(action: () -> Void) {
action() // called synchronously — not @escaping
}
@escaping tells the compiler the closure outlives the function, enabling the compiler to skip certain optimisations and require explicit self capture.
16. What is a capture list and why is it important?
class ViewModel {
var count = 0
func startTimer() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
guard let self else { return }
self.count += 1
}
}
}
Without [weak self], the closure holds a strong reference to self, creating a retain cycle — the ViewModel is never released.
| Capture | Behaviour |
|---|---|
[weak self] |
Optional reference — self can become nil |
[unowned self] |
Non-optional — crashes if self is deallocated |
| Default (no list) | Strong reference — can cause retain cycles |
Use [weak self] for most async closures. Use [unowned self] only when you're certain the object outlives the closure.
17. What is @autoclosure?
func assert(_ condition: @autoclosure () -> Bool, _ message: String) {
if !condition() { print(message) }
}
assert(x > 0, "x must be positive") // x > 0 wrapped in closure automatically
// Caller writes expression, not { x > 0 }
@autoclosure wraps the argument expression in a closure automatically, enabling lazy evaluation and cleaner call sites.
Memory Management
18. How does ARC (Automatic Reference Counting) work?
ARC tracks the reference count of each class instance. When count drops to zero, the instance is deallocated.
class Dog {
let name: String
init(_ name: String) { self.name = name; print("\(name) init") }
deinit { print("\(name) deinit") }
}
var d1: Dog? = Dog("Rex") // count = 1
var d2 = d1 // count = 2
d1 = nil // count = 1
d2 = nil // count = 0 → deinit called
ARC is compile-time, not garbage collection — no pause, deterministic cleanup.
19. What is a retain cycle and how do you break it?
class Owner {
var pet: Pet?
}
class Pet {
var owner: Owner? // strong → retain cycle!
}
// Fix: use weak
class Pet {
weak var owner: Owner? // won't keep Owner alive
}
A retain cycle occurs when two objects hold strong references to each other, preventing deallocation. Fix with weak or unowned.
20. What is the difference between weak and unowned?
weak |
unowned |
|
|---|---|---|
| Optional | Yes (T?) |
No (T) |
| Becomes nil | Yes, when object deallocated | No — crashes if accessed after dealloc |
| Use when | Object can become nil during lifetime | Object guaranteed to outlive the reference |
// weak: delegate pattern
protocol Delegate: AnyObject { }
class View {
weak var delegate: Delegate? // View doesn't own the delegate
}
// unowned: child doesn't outlive parent
class Country {
var currency: Currency!
}
class Currency {
unowned let country: Country // always exists
init(_ country: Country) { self.country = country }
}
Error Handling
21. How does Swift's error handling work?
enum NetworkError: Error {
case invalidURL
case timeout
case serverError(Int)
}
func fetchData(from url: String) throws -> Data {
guard url.hasPrefix("https") else { throw NetworkError.invalidURL }
// ...
return Data()
}
// Caller must handle
do {
let data = try fetchData(from: "https://api.example.com")
} catch NetworkError.invalidURL {
print("Bad URL")
} catch NetworkError.serverError(let code) {
print("Server error: \(code)")
} catch {
print("Unknown: \(error)")
}
// Alternatives
let data = try? fetchData(from: url) // returns Optional<Data>
let data2 = try! fetchData(from: url) // crashes if throws
22. What is the Result type?
func fetchUser(id: Int, completion: @escaping (Result<User, NetworkError>) -> Void) {
// ...
completion(.success(User(id: id, name: "Alice")))
// or
completion(.failure(.timeout))
}
fetchUser(id: 1) { result in
switch result {
case .success(let user): print(user.name)
case .failure(let error): print(error)
}
}
Result<Success, Failure> is useful for callback-based async APIs, making success/failure explicit without try-catch.
Concurrency
23. What is async/await in Swift?
func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
// Calling async code
Task {
do {
let user = try await fetchUser(id: 1)
print(user.name)
} catch {
print(error)
}
}
async marks a function that can suspend. await suspends execution until the async operation completes without blocking the thread.
24. What is a Task in Swift concurrency?
// Unstructured task — runs concurrently
let task = Task {
let result = await fetchData()
return result
}
let value = await task.value
// Detached task — no actor inheritance
Task.detached {
// runs without inheriting actor context
}
// Task cancellation
task.cancel()
// Checking cancellation inside task
Task {
try Task.checkCancellation()
// or
guard !Task.isCancelled else { return }
}
25. What is structured concurrency and async let?
// Sequential — one after the other
let users = try await fetchUsers()
let posts = try await fetchPosts()
// Concurrent — both start immediately
async let users = fetchUsers()
async let posts = fetchPosts()
let (u, p) = try await (users, posts) // waits for both
Structured concurrency ensures child tasks are always awaited before the parent scope exits — no fire-and-forget leaks.
26. What is an actor in Swift?
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws {
guard balance >= amount else { throw AccountError.insufficientFunds }
balance -= amount
}
var currentBalance: Double { balance }
}
let account = BankAccount()
await account.deposit(100)
let bal = await account.currentBalance // always accessed via await
Actors provide data race protection — only one task can access the actor's mutable state at a time. await at call sites signals potential suspension.
27. What is @MainActor?
@MainActor
class ViewModel: ObservableObject {
@Published var items: [Item] = []
func load() async {
let data = await fetchFromNetwork() // runs on background
items = data // back on main — safe UI update
}
}
// Single function on main
@MainActor
func updateUI() {
label.text = "Done"
}
@MainActor guarantees code runs on the main thread. SwiftUI views are implicitly @MainActor.
SwiftUI
28. What is @State in SwiftUI?
struct CounterView: View {
@State private var count = 0 // owns the truth
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") { count += 1 }
}
}
}
@State is owned by the view. SwiftUI manages storage and triggers re-renders on change. Always private.
29. What is @Binding?
struct Toggle: View {
@Binding var isOn: Bool // reference to external state
var body: some View {
Button(isOn ? "ON" : "OFF") { isOn.toggle() }
}
}
struct Parent: View {
@State private var enabled = false
var body: some View {
Toggle(isOn: $enabled) // $ passes Binding
}
}
@Binding creates a two-way connection to state owned elsewhere. Changes propagate in both directions.
30. What is @ObservedObject vs @StateObject?
class CartViewModel: ObservableObject {
@Published var items: [Item] = []
func add(_ item: Item) { items.append(item) }
}
// @StateObject: view owns the object — created once
struct CartView: View {
@StateObject private var vm = CartViewModel()
var body: some View { /* ... */ }
}
// @ObservedObject: object passed in — not owned by this view
struct CartItemsView: View {
@ObservedObject var vm: CartViewModel
var body: some View { /* ... */ }
}
| Property wrapper | Who owns it | Re-created on re-render? |
|---|---|---|
@StateObject |
This view | No — persists |
@ObservedObject |
Parent / injected | Yes — risky if created inline |
31. What is @EnvironmentObject?
class AuthState: ObservableObject {
@Published var isLoggedIn = false
}
@main
struct MyApp: App {
@StateObject var auth = AuthState()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(auth) // inject at root
}
}
}
struct ProfileView: View {
@EnvironmentObject var auth: AuthState // read from environment
var body: some View {
Text(auth.isLoggedIn ? "Logged in" : "Guest")
}
}
@EnvironmentObject provides a dependency injection mechanism — pass data deep into the view hierarchy without explicit parameter passing. Crashes at runtime if not injected.
32. What is the @Published property wrapper?
class ViewModel: ObservableObject {
@Published var name: String = "" // emits change before value is set
@Published var isLoading = false
}
@Published automatically emits objectWillChange through ObservableObject when the value is about to change, triggering SwiftUI re-renders.
Generics
33. How do generics work in Swift?
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a; a = b; b = temp
}
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
Generics enable type-safe reusable code without sacrificing performance (monomorphisation at compile time).
34. What are generic constraints?
// Constraint: T must be Comparable
func max<T: Comparable>(_ a: T, _ b: T) -> T {
return a > b ? a : b
}
// Multiple constraints
func merge<T: Hashable & Codable>(_ a: [T], _ b: [T]) -> Set<T> {
return Set(a + b)
}
// Where clause
func allEqual<T: Equatable>(_ array: [T]) -> Bool where T: Hashable {
return Set(array).count <= 1
}
35. What is some vs any in Swift?
protocol Shape {
func area() -> Double
}
// some: opaque type — concrete type fixed at compile time, hidden from caller
func makeCircle() -> some Shape { Circle(radius: 5) }
// any: existential — type-erased, heterogeneous collections
func draw(_ shapes: [any Shape]) {
shapes.forEach { print($0.area()) }
}
// In SwiftUI: body is 'some View'
var body: some View { Text("Hello") }
some is more performant (no box allocation). any enables heterogeneous collections at the cost of indirection. Swift 5.7+ requires any keyword for existentials.
Collections
36. What is the difference between map, filter, reduce, and compactMap?
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 } // [2, 4, 6, 8, 10]
let evens = numbers.filter { $0.isMultiple(of: 2) } // [2, 4]
let sum = numbers.reduce(0) { $0 + $1 } // 15 (or reduce(0, +))
let strings = ["1", "two", "3", "four"]
let ints = strings.compactMap { Int($0) } // [1, 3] — removes nils
// flatMap: flatten nested collections
let nested = [[1,2],[3,4]]
let flat = nested.flatMap { $0 } // [1, 2, 3, 4]
37. What are lazy sequences?
let expensive = (1...1_000_000)
.lazy
.filter { $0.isMultiple(of: 3) }
.map { $0 * $0 }
.prefix(5)
// Only computes 5 results, not all 1,000,000
print(Array(expensive)) // [9, 36, 81, 144, 225]
.lazy defers evaluation until elements are consumed. Avoids creating intermediate arrays for large sequences.
Advanced Patterns
38. What is a property wrapper?
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Settings {
@Clamped(0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150 // clamped to 100
print(s.volume) // 100
Property wrappers add reusable behaviour around stored properties. Built-in examples: @Published, @State, @AppStorage, @UserDefault.
39. What is a result builder (@resultBuilder)?
@resultBuilder
struct HTMLBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: "\n")
}
}
@HTMLBuilder
func page() -> String {
"<html>"
"<body>Hello</body>"
"</html>"
}
Result builders power SwiftUI's @ViewBuilder — the DSL that lets you write VStack { Text("A"); Text("B") } without array literals.
40. What is @discardableResult?
@discardableResult
func save() -> Bool {
// save logic
return true
}
save() // no warning about unused return value
let ok = save() // still works with return value
Without the attribute, ignoring a function's return value produces a warning.
iOS Architecture
41. What is MVVM in iOS development?
// Model
struct Article: Codable { let id: Int; let title: String; let body: String }
// ViewModel — presentation logic, no UIKit/SwiftUI imports needed
@MainActor
class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
private let service: ArticleService
init(service: ArticleService = .live) { self.service = service }
func load() async {
isLoading = true
defer { isLoading = false }
articles = (try? await service.fetchArticles()) ?? []
}
}
// View — purely declarative
struct ArticleListView: View {
@StateObject var vm = ArticleViewModel()
var body: some View {
List(vm.articles, id: \.id) { Text($0.title) }
.task { await vm.load() }
}
}
42. What is dependency injection in Swift?
protocol HTTPClient {
func fetch(_ url: URL) async throws -> Data
}
// Live implementation
struct URLSessionClient: HTTPClient {
func fetch(_ url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
// Mock for testing
struct MockHTTPClient: HTTPClient {
var stubbedData: Data = Data()
func fetch(_ url: URL) async throws -> Data { stubbedData }
}
class DataService {
private let client: HTTPClient
init(client: HTTPClient = URLSessionClient()) { self.client = client }
}
Dependency injection via protocols enables easy test mocking without subclassing or singletons.
43. What is the Coordinator pattern?
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
func start()
}
class AppCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
private let window: UIWindow
init(window: UIWindow) { self.window = window }
func start() {
let nav = UINavigationController()
window.rootViewController = nav
let child = HomeCoordinator(nav: nav)
childCoordinators.append(child)
child.start()
}
}
The Coordinator pattern removes navigation logic from ViewControllers, keeping them focused on their view. Each coordinator owns its flow.
Testing
44. How do you write unit tests in Swift?
import XCTest
class CalculatorTests: XCTestCase {
var sut: Calculator! // System Under Test
override func setUp() {
sut = Calculator()
}
override func tearDown() {
sut = nil
}
func test_add_twoPositiveNumbers_returnsSum() {
let result = sut.add(2, 3)
XCTAssertEqual(result, 5)
}
func test_divide_byZero_throwsError() {
XCTAssertThrowsError(try sut.divide(10, by: 0)) { error in
XCTAssertEqual(error as? CalcError, .divisionByZero)
}
}
}
45. How do you test async code in Swift?
final class UserServiceTests: XCTestCase {
func test_fetchUser_returnsExpectedName() async throws {
let mock = MockHTTPClient(stubbedData: testUserJSON)
let service = UserService(client: mock)
let user = try await service.fetchUser(id: 1)
XCTAssertEqual(user.name, "Alice")
}
func test_fetchUser_onNetworkError_throws() async {
let mock = FailingHTTPClient()
let service = UserService(client: mock)
await XCTAssertThrowsErrorAsync(try await service.fetchUser(id: 1))
}
}
Use async throws test methods — XCTest supports async natively from Swift 5.5+.
Common Pitfalls
46. What are common memory leak patterns in Swift?
// Pattern 1: Strong self in closure
class Timer {
var onTick: (() -> Void)?
func start() { /* calls onTick periodically */ }
}
class Screen {
let timer = Timer()
func setup() {
timer.onTick = { [weak self] in // MUST be weak
self?.update()
}
}
}
// Pattern 2: Delegate without weak
protocol ViewDelegate: AnyObject { }
class SomeView {
weak var delegate: ViewDelegate? // MUST be weak
}
// Pattern 3: NotificationCenter without removeObserver
// In deinit: NotificationCenter.default.removeObserver(self)
47. What is the difference between == and ===?
struct Point: Equatable { var x, y: Int }
class Box { var value: Int; init(_ v: Int) { value = v } }
let p1 = Point(x: 1, y: 1)
let p2 = Point(x: 1, y: 1)
p1 == p2 // true — same value (Equatable)
let b1 = Box(1)
let b2 = Box(1)
let b3 = b1
b1 == b2 // true if Equatable implemented; false if default
b1 === b2 // false — different instances
b1 === b3 // true — same instance (identity)
== checks value equality (Equatable). === checks reference identity (class instances only).
48. Anti-patterns to avoid in Swift
| Anti-pattern | Problem | Fix |
|---|---|---|
Force unwrap ! everywhere |
Crashes on nil | Optional binding / guard |
[unowned self] carelessly |
Crash if object deallocated | Use [weak self] unless 100% certain |
| Strong delegate | Retain cycle | weak var delegate: Protocol? |
| Massive ViewController | Untestable, hard to maintain | MVVM, Coordinator, extract logic |
| Sync code on main thread | Freezes UI | Task.detached or DispatchQueue.global() |
UserDefaults for complex data |
Brittle, no migration | Core Data, SQLite, or file storage |
| Singleton abuse | Hidden dependencies | Dependency injection |
Ignoring Sendable warnings |
Data races in concurrency | Mark types Sendable or use actors |
Comparison Tables
49. Swift vs Kotlin
| Feature | Swift | Kotlin |
|---|---|---|
| Null safety | Optional<T> (String?) |
Nullable types (String?) |
| Closures | { param in body } |
{ param -> body } |
| Coroutines | async/await + actors |
suspend + coroutines |
| Data classes | Structs with memberwise init | data class |
| Sealed types | enum with associated values |
sealed class |
| Extension functions | Yes (extension Type {}) |
Yes (fun Type.foo()) |
| Property wrappers | @propertyWrapper |
None (but by delegation) |
| Compile target | Darwin (Apple platforms) | JVM, Android, KMP |
| UI framework | SwiftUI / UIKit | Jetpack Compose / XML |
50. Swift vs Objective-C
| Feature | Swift | Objective-C |
|---|---|---|
| Optional safety | Type-safe Optional<T> |
Any pointer can be nil (runtime crash) |
| Syntax | Concise, modern | Verbose, C-based |
| Closures | First-class | Blocks (complex syntax) |
| Generics | Full support | Limited (lightweight generics) |
| Enums | Powerful with associated values | Basic C enums |
| Structs | First-class value types | Rarely used (C structs) |
| Error handling | throws/do-catch |
NSError out-parameter |
| Interop | Full ObjC interop | N/A |
| Performance | Comparable or faster | Comparable |
| Dynamic dispatch | Opt-in (@objc, dynamic) |
Default |
FAQ
Q: Should I use SwiftUI or UIKit for new iOS projects? SwiftUI for new projects targeting iOS 16+. It's Apple's direction, offers better concurrency integration, and less boilerplate. Use UIKit when you need fine-grained control, target older OS versions, or wrap existing UIKit components.
Q: What is the difference between didSet and willSet?
willSet is called before the value changes (receives newValue). didSet is called after (receives oldValue). Use didSet for triggering side effects after a property changes.
var temperature: Double = 0 {
willSet { print("About to change to \(newValue)") }
didSet { print("Changed from \(oldValue)") }
}
Q: What is @objc and when do you need it?
@objc exposes Swift declarations to Objective-C runtime. Required for: selectors (#selector), delegates using @objc optional protocol methods, and integration with ObjC frameworks.
Q: What is Sendable in Swift concurrency?
Sendable marks types that are safe to pass across concurrency boundaries (between tasks/actors). Value types are implicitly Sendable. Classes must be manually marked (final class Foo: Sendable) and must have only immutable or thread-safe properties.
Q: What is the difference between async let and TaskGroup?
async let is for fixed number of concurrent operations known at compile time. TaskGroup handles dynamic fan-out (unknown number of tasks at runtime, e.g., processing an array of items concurrently).
Q: How do you prevent a retain cycle with @escaping closures?
Use a capture list [weak self]. Always guard the optional: guard let self else { return } (or guard let strongSelf = self else { return }). Never use [unowned self] with escaping closures unless the object is guaranteed to outlive all calls.