# iOS 개발자를 위한 Swift 면접 질문 Top 25 > iOS 면접에서 가장 자주 출제되는 Swift 질문 25개를 코드 예제와 함께 완벽 정리. 옵셔널, 클로저, ARC, 프로토콜, async/await 및 고급 패턴까지 체계적으로 다룹니다. - Published: 2026-01-29 - Updated: 2026-04-12 - Author: SharpSkill - Tags: swift, ios, interview, career, apple - Reading time: 15 min --- iOS 기술 면접에서는 Swift의 기초부터 고급 개념까지 폭넓게 평가합니다. 본 가이드에서는 채용 담당자가 가장 빈번하게 출제하는 25개 질문을 언어 기본부터 최신 동시성 패턴까지 체계적으로 다룹니다. > **본 가이드 활용법** > > 각 질문에는 코드 예제가 포함된 상세한 답변이 제공됩니다. 질문은 기초부터 고급 개념 순으로 난이도별로 구성되어 있습니다. ## Swift 기초 ### 1. `let`과 `var`의 차이점은 무엇입니까? `let`은 초기화 후 값을 변경할 수 없는 상수를 선언하고, `var`는 변경 가능한 변수를 선언합니다. 참조 타입(클래스)의 경우 `let`은 참조의 재할당을 방지하지만 객체 자체의 프로퍼티 변경은 허용합니다. ```swift // Constants.swift // let = constant, immutable value let maximumAttempts = 3 // maximumAttempts = 5 // ❌ Compile error // var = variable, mutable value var currentAttempt = 0 currentAttempt += 1 // ✅ OK // Careful with reference types class User { var name: String init(name: String) { self.name = name } } let user = User(name: "Alice") user.name = "Bob" // ✅ OK - modifying object, not reference // user = User(name: "Charlie") // ❌ Error - reassignment forbidden ``` **모범 사례**: 기본적으로 `let`을 사용하고, 변경이 필요한 경우에만 `var`로 전환합니다. 이렇게 하면 코드의 예측 가능성과 가독성이 향상됩니다. ### 2. Swift의 옵셔널에 대해 설명하십시오 옵셔널은 값이 존재하지 않을 수 있음을 나타내는 타입입니다. 지정된 타입의 값 또는 `nil`을 보유할 수 있습니다. Swift는 옵셔널을 사용하여 값이 누락될 수 있는 경우의 명시적 처리를 컴파일 타임에 강제하여 안전성을 보장합니다. ```swift // Optionals.swift // Declaring an optional with ? var username: String? = nil // Can hold a String or nil // Optional binding with if let (safe unwrapping) if let name = username { print("Hello, \(name)") // Only executes if username != nil } else { print("Anonymous user") } // Guard let for early return func greet(user: String?) { guard let name = user else { print("No user provided") return // Early exit if nil } print("Hello, \(name)") // name is guaranteed non-nil here } // Nil-coalescing operator (??) for default value let displayName = username ?? "Anonymous" // Force unwrapping (!) - DANGEROUS, avoid this // let forced = username! // Crashes if nil ``` ### 3. `struct`와 `class`의 차이점은 무엇입니까? `struct`는 값 타입(할당 시 복사)이고, `class`는 참조 타입(동일 인스턴스를 공유)입니다. 이 근본적인 차이는 성능, 메모리 관리, 코드 동작에 직접적으로 영향을 미칩니다. ```swift // ValueVsReference.swift // Struct = value type (copy) struct Point { var x: Int var y: Int } var p1 = Point(x: 10, y: 20) var p2 = p1 // Independent copy p2.x = 100 // Only modifies p2 print(p1.x) // 10 - p1 unchanged // Class = reference type (shared) class Rectangle { var width: Int var height: Int init(width: Int, height: Int) { self.width = width self.height = height } } let r1 = Rectangle(width: 10, height: 20) let r2 = r1 // Same shared instance r2.width = 100 // Modifies the shared instance print(r1.width) // 100 - r1 also modified! ``` **선택 기준**: - **Struct**: 단순한 데이터, 불변 값, 상속이 필요하지 않은 경우 - **Class**: 아이덴티티가 중요하거나, 상속이 필요하거나, 공유 동작이 요구되는 경우 ### 4. `switch`에서 패턴 매칭은 어떻게 동작합니까? Swift의 `switch`는 강력하며 전수적(exhaustive)입니다. 모든 가능한 케이스를 반드시 처리해야 합니다. 타입, 범위, 튜플에 대한 패턴 매칭을 지원하며 `where` 절로 추가 조건을 지정할 수 있습니다. ```swift // PatternMatching.swift enum NetworkError: Error { case timeout case serverError(code: Int) case noConnection } func handleError(_ error: NetworkError) { switch error { case .timeout: print("Request timed out") case .serverError(let code) where code >= 500: print("Critical server error: \(code)") case .serverError(let code): print("Server error: \(code)") case .noConnection: print("No connection") } // No default needed: all cases covered } // Pattern matching on ranges and tuples let point = (x: 5, y: 10) switch point { case (0, 0): print("Origin") case (let x, 0): print("On X axis at \(x)") case (0...10, 0...10): print("In the 10x10 square") default: print("Elsewhere") } ``` ### 5. 클로저와 그 문법에 대해 설명하십시오 클로저는 주변 컨텍스트에서 변수에 대한 참조를 캡처하고 저장하는 독립적인 코드 블록입니다. 다른 언어의 람다 또는 익명 함수에 해당합니다. ```swift // Closures.swift // Full syntax let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in return a + b } // Shorthand syntax (inferred types, implicit return) let multiply: (Int, Int) -> Int = { $0 * $1 } // Trailing closure syntax let numbers = [3, 1, 4, 1, 5] let sorted = numbers.sorted { $0 > $1 } // [5, 4, 3, 1, 1] // Closure capturing a variable func makeCounter() -> () -> Int { var count = 0 // Captured variable return { count += 1 // Closure "closes over" count return count } } let counter = makeCounter() print(counter()) // 1 print(counter()) // 2 - count remembered between calls ``` > **캡처 시맨틱스** > > 클로저는 기본적으로 변수를 참조로 캡처합니다. 값으로 캡처하려면 캡처 리스트를 사용합니다: `{ [count] in ... }`. ## 메모리 관리와 ARC ### 6. ARC(자동 참조 카운팅)는 어떻게 동작합니까? ARC는 각 클래스 인스턴스에 대한 강한 참조 수를 카운트하여 메모리를 자동으로 관리합니다. 카운트가 0이 되면 인스턴스가 해제됩니다. 가비지 컬렉션과 달리 ARC는 결정론적이며 예측 가능하게 동작합니다. ```swift // ARC.swift class Person { let name: String init(name: String) { self.name = name print("\(name) is initialized") } deinit { print("\(name) is deallocated") } } // Demonstrating lifecycle var person1: Person? = Person(name: "Alice") // refCount = 1 var person2 = person1 // refCount = 2 person1 = nil // refCount = 1 (not deallocated) person2 = nil // refCount = 0 → deinit called // Output: "Alice is deallocated" ``` ### 7. 순환 참조(리테인 사이클)란 무엇이며 어떻게 방지합니까? 순환 참조는 두 객체가 서로 강한 참조를 보유하여 해제를 방해하는 상태입니다. `weak`와 `unowned` 키워드를 사용하여 이 순환을 끊을 수 있습니다. ```swift // RetainCycle.swift class Department { let name: String var manager: Employee? // Strong reference init(name: String) { self.name = name } deinit { print("Department \(name) deallocated") } } class Employee { let name: String // weak prevents retain cycle - can become nil weak var department: Department? init(name: String) { self.name = name } deinit { print("Employee \(name) deallocated") } } // Without weak: retain cycle → memory leak // With weak: proper deallocation var dept: Department? = Department(name: "Engineering") var emp: Employee? = Employee(name: "Bob") dept?.manager = emp emp?.department = dept dept = nil // ✅ Deallocated thanks to weak emp = nil // ✅ Deallocated ``` ### 8. `weak`와 `unowned`의 차이점은 무엇입니까? 두 키워드 모두 순환 참조를 해소하지만 보장하는 내용이 다릅니다. `weak`는 옵셔널이며 참조 대상 객체가 해제되면 `nil`이 됩니다. `unowned`는 객체가 항상 존재한다고 가정하며 해제 후 접근하면 크래시가 발생합니다. ```swift // WeakVsUnowned.swift class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } } class CreditCard { let number: String // unowned because a card always exists with its customer unowned let customer: Customer init(number: String, customer: Customer) { self.number = number self.customer = customer } } // Card cannot exist without customer let customer = Customer(name: "Alice") customer.card = CreditCard(number: "1234", customer: customer) // If customer is deallocated, accessing card.customer would crash ``` **원칙**: 기본적으로 `weak`를 사용합니다. 참조 대상 객체의 생명주기가 동등하거나 더 긴 것이 보장되는 경우에만 `unowned`를 사용합니다. ## 프로토콜과 제네릭 ### 9. Swift의 프로토콜에 대해 설명하십시오 프로토콜은 채택하는 타입이 구현해야 하는 프로퍼티와 메서드의 계약(인터페이스)을 정의합니다. 다형성을 구현하는 핵심 수단이며 Swift의 프로토콜 지향 프로그래밍(POP)의 기반이 됩니다. ```swift // Protocols.swift // Protocol definition protocol Drawable { var color: String { get set } // Required property (read/write) func draw() // Required method } // Protocol extension with default implementation extension Drawable { func draw() { print("Default drawing in \(color)") } } // Protocol conformance struct Circle: Drawable { var color: String var radius: Double // draw() inherits default implementation } struct Square: Drawable { var color: String var side: Double // Override default implementation func draw() { print("Square \(color) with side \(side)") } } // Polymorphic usage let shapes: [Drawable] = [Circle(color: "red", radius: 5), Square(color: "blue", side: 10)] shapes.forEach { $0.draw() } ``` ### 10. 연관 타입(Associated Type)이란 무엇입니까? 연관 타입은 프로토콜이 채택하는 타입에 의해 구체화되는 제네릭 타입을 정의할 수 있게 합니다. 이 메커니즘 덕분에 `Collection`과 같은 프로토콜이 높은 유연성을 갖습니다. ```swift // AssociatedTypes.swift // Protocol with associated type protocol Container { associatedtype Item // Type defined by conformant var items: [Item] { get set } mutating func add(_ item: Item) func count() -> Int } // Implementation with Item = String struct StringBox: Container { typealias Item = String // Optional, Swift can infer var items: [String] = [] mutating func add(_ item: String) { items.append(item) } func count() -> Int { items.count } } // Implementation with Item = Int struct IntStack: Container { var items: [Int] = [] mutating func add(_ item: Int) { items.append(item) } func count() -> Int { items.count } } ``` ### 11. 제네릭은 어떻게 동작합니까? 제네릭을 사용하면 모든 타입에서 동작하는 유연하고 재사용 가능한 코드를 작성할 수 있습니다. 코드 중복을 피하면서도 컴파일 타임 타입 안전성을 유지합니다. ```swift // Generics.swift // Generic function func swap(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp } // Type constraint with where func findIndex(of item: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == item { return index } // Equatable required for == } return nil } // Generic struct struct Queue { private var elements: [Element] = [] mutating func enqueue(_ element: Element) { elements.append(element) } mutating func dequeue() -> Element? { guard !elements.isEmpty else { return nil } return elements.removeFirst() } } var intQueue = Queue() intQueue.enqueue(1) intQueue.enqueue(2) print(intQueue.dequeue()) // Optional(1) ``` ### 12. `Codable` 프로토콜에 대해 설명하십시오 `Codable`(`Encodable & Decodable`의 별칭)은 Swift 타입과 JSON 등의 포맷 간 자동 직렬화를 가능하게 합니다. 모든 프로퍼티가 Codable이면 컴파일러가 구현을 자동으로 생성합니다. ```swift // Codable.swift struct User: Codable { let id: Int let name: String let email: String let createdAt: Date // CodingKeys to map different JSON names enum CodingKeys: String, CodingKey { case id case name case email case createdAt = "created_at" // snake_case → camelCase } } // JSON decoding let json = """ { "id": 1, "name": "Alice", "email": "alice@example.com", "created_at": "2026-01-15T10:30:00Z" } """.data(using: .utf8)! let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 do { let user = try decoder.decode(User.self, from: json) print(user.name) // "Alice" } catch { print("Decoding error: \(error)") } // Encoding to JSON let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let data = try encoder.encode(user) ``` ## 동시성과 async/await ### 13. Swift의 async/await는 어떻게 동작합니까? `async/await`는 비동기 코드를 순차적으로 작성할 수 있게 하여 비동기 처리를 단순화합니다. `async` 함수는 스레드를 차단하지 않고 일시 중단될 수 있으며, 다른 태스크가 실행될 여지를 제공합니다. ```swift // AsyncAwait.swift // Asynchronous function func fetchUser(id: Int) async throws -> User { let url = URL(string: "https://api.example.com/users/\(id)")! // await suspends execution until response let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw NetworkError.invalidResponse } return try JSONDecoder().decode(User.self, from: data) } // Calling from async context func loadUserProfile() async { do { let user = try await fetchUser(id: 42) print("User: \(user.name)") } catch { print("Error: \(error)") } } // Calling from synchronous context with Task func buttonTapped() { Task { await loadUserProfile() } } ``` ### 14. Actor란 무엇입니까? Actor는 내부 상태를 동시 접근으로부터 보호하는 참조 타입입니다. 한 번에 하나의 태스크만 가변 프로퍼티에 접근할 수 있도록 보장하여 데이터 경합을 원천적으로 제거합니다. ```swift // Actors.swift // Actor protects its state automatically actor BankAccount { private var balance: Double = 0 func deposit(_ amount: Double) { balance += amount // Automatic thread-safe access } func withdraw(_ amount: Double) -> Bool { guard balance >= amount else { return false } balance -= amount return true } func getBalance() -> Double { return balance } } // Usage - await required to access actor let account = BankAccount() Task { await account.deposit(100) let success = await account.withdraw(30) let balance = await account.getBalance() print("Balance: \(balance)") // 70 } ``` ### 15. `Task`와 `TaskGroup`에 대해 설명하십시오 `Task`는 비동기 작업의 단위를 생성합니다. `TaskGroup`은 여러 태스크를 병렬로 실행하고 결과를 수집하는 기능을 제공합니다. ```swift // TaskGroup.swift // Simple Task let task = Task { return await fetchUser(id: 1) } let user = try await task.value // TaskGroup for parallelization func fetchMultipleUsers(ids: [Int]) async throws -> [User] { try await withThrowingTaskGroup(of: User.self) { group in // Launch all requests in parallel for id in ids { group.addTask { try await fetchUser(id: id) } } // Collect results as they complete var users: [User] = [] for try await user in group { users.append(user) } return users } } // All 3 requests run in parallel let users = try await fetchMultipleUsers(ids: [1, 2, 3]) ``` ### 16. `@MainActor`는 어떻게 동작합니까? `@MainActor`는 코드가 메인 스레드에서 실행되도록 보장합니다. UI 업데이트는 반드시 메인 스레드에서 수행되어야 하므로, 이 어노테이션은 UI 관련 코드에서 필수적입니다. ```swift // MainActor.swift // UI class annotated with @MainActor @MainActor class UserViewModel: ObservableObject { @Published var user: User? @Published var isLoading = false @Published var error: String? func loadUser() async { isLoading = true // ✅ On main thread automatically do { // Network operation on background thread user = try await fetchUser(id: 42) } catch { self.error = error.localizedDescription } isLoading = false // ✅ Automatic return to main thread } } // Or for a specific function func updateUI() async { await MainActor.run { // This block runs on main thread label.text = "Updated" } } ``` ## 패턴과 아키텍처 ### 17. 델리게이트 패턴에 대해 설명하십시오 델리게이트 패턴은 특정 책임을 다른 객체에 위임하는 디자인 패턴입니다. UIKit(UITableViewDelegate, UITextFieldDelegate 등)에서 광범위하게 사용됩니다. ```swift // DelegatePattern.swift // 1. Define the delegate protocol protocol DownloadManagerDelegate: AnyObject { func downloadDidStart() func downloadDidProgress(_ progress: Double) func downloadDidComplete(data: Data) func downloadDidFail(error: Error) } // 2. Class that uses the delegate class DownloadManager { // weak to avoid retain cycles weak var delegate: DownloadManagerDelegate? func startDownload(url: URL) { delegate?.downloadDidStart() // Simulated download Task { for progress in stride(from: 0.0, to: 1.0, by: 0.1) { try await Task.sleep(nanoseconds: 100_000_000) delegate?.downloadDidProgress(progress) } delegate?.downloadDidComplete(data: Data()) } } } // 3. Class that implements the delegate class ViewController: UIViewController, DownloadManagerDelegate { let manager = DownloadManager() override func viewDidLoad() { super.viewDidLoad() manager.delegate = self // Register as delegate } func downloadDidStart() { print("Started") } func downloadDidProgress(_ progress: Double) { print("\(progress * 100)%") } func downloadDidComplete(data: Data) { print("Complete") } func downloadDidFail(error: Error) { print("Error: \(error)") } } ``` ### 18. MVVM 패턴이란 무엇입니까? MVVM(Model-View-ViewModel)은 프레젠테이션 로직을 뷰에서 분리하는 아키텍처 패턴입니다. ViewModel은 뷰가 표시할 관찰 가능한 데이터를 노출하며, 뷰의 구체적인 구현 사항을 알 필요가 없습니다. ```swift // MVVM.swift // Model struct Article: Identifiable { let id: UUID let title: String let content: String let publishedAt: Date } // ViewModel @MainActor class ArticleListViewModel: ObservableObject { @Published private(set) var articles: [Article] = [] @Published private(set) var isLoading = false @Published var errorMessage: String? private let repository: ArticleRepository init(repository: ArticleRepository = .shared) { self.repository = repository } func loadArticles() async { isLoading = true errorMessage = nil do { articles = try await repository.fetchArticles() } catch { errorMessage = "Failed to load articles" } isLoading = false } } // View (SwiftUI) struct ArticleListView: View { @StateObject private var viewModel = ArticleListViewModel() var body: some View { Group { if viewModel.isLoading { ProgressView() } else { List(viewModel.articles) { article in Text(article.title) } } } .task { await viewModel.loadArticles() } } } ``` ### 19. 의존성 주입에 대해 설명하십시오 의존성 주입은 객체의 의존성을 내부에서 생성하는 대신 외부에서 제공하는 설계 기법입니다. 테스트 용이성과 느슨한 결합을 향상시킵니다. ```swift // DependencyInjection.swift // Protocol for abstraction protocol UserServiceProtocol { func fetchUser(id: Int) async throws -> User } // Real implementation class UserService: UserServiceProtocol { func fetchUser(id: Int) async throws -> User { // Real API call 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) } } // ViewModel with injection class ProfileViewModel: ObservableObject { private let userService: UserServiceProtocol // Constructor injection init(userService: UserServiceProtocol = UserService()) { self.userService = userService } func loadProfile(id: Int) async { // Uses injected service } } // Mock for testing class MockUserService: UserServiceProtocol { func fetchUser(id: Int) async throws -> User { return User(id: id, name: "Test User", email: "test@test.com") } } // In tests let viewModel = ProfileViewModel(userService: MockUserService()) ``` ### 20. 싱글턴 패턴은 어떻게 구현합니까? 싱글턴은 클래스가 전역적으로 접근 가능한 유일한 인스턴스를 가지도록 보장하는 패턴입니다. Swift에서는 static 프로퍼티와 private 이니셜라이저를 사용하여 구현합니다. ```swift // Singleton.swift class NetworkManager { // Single globally accessible instance static let shared = NetworkManager() // Private initializer prevents creating other instances private init() { // Initial configuration } private let session = URLSession.shared func request(_ url: URL) async throws -> T { let (data, _) = try await session.data(from: url) return try JSONDecoder().decode(T.self, from: data) } } // Usage let user: User = try await NetworkManager.shared.request(url) ``` > **싱글턴 사용 시 주의사항** > > 싱글턴은 테스트와 느슨한 결합을 어렵게 만드는 전역 상태를 생성합니다. 가능하면 의존성 주입을 우선 사용하는 것이 권장됩니다. ## 고급 개념 ### 21. 클로저의 `@escaping`에 대해 설명하십시오 클로저가 `@escaping`이라는 것은 해당 클로저를 받은 함수가 반환된 이후에도 호출될 수 있음을 의미합니다. 비동기 콜백이나 클로저 저장에서 일반적으로 사용됩니다. ```swift // Escaping.swift class DataLoader { // Storage of completion handlers private var completionHandlers: [() -> Void] = [] // @escaping because closure is stored and called later func loadData(completion: @escaping () -> Void) { completionHandlers.append(completion) DispatchQueue.global().async { // Async work... Thread.sleep(forTimeInterval: 1) DispatchQueue.main.async { // Closure called after loadData returns completion() } } } // Non-escaping by default: closure called before return func transform(data: Data, using transformer: (Data) -> String) -> String { return transformer(data) // Called immediately } } // With @escaping, watch for retain cycles class ViewController { var loader = DataLoader() var data: String? func load() { loader.loadData { [weak self] in // [weak self] avoids retain cycle self?.data = "Loaded" } } } ``` ### 22. `@propertyWrapper`란 무엇입니까? 프로퍼티 래퍼는 프로퍼티의 저장 및 접근 로직을 캡슐화하는 메커니즘입니다. 유효성 검사, 로깅, 영속화 등의 패턴을 재사용 가능하게 만듭니다. ```swift // PropertyWrapper.swift // Property wrapper for positive values only @propertyWrapper struct Positive { private var value: Int = 0 var wrappedValue: Int { get { value } set { value = max(0, newValue) } // Force positive } // Projected value accessible via $ var projectedValue: Bool { value > 0 } init(wrappedValue: Int) { self.wrappedValue = wrappedValue } } // Usage struct Player { @Positive var score: Int = 0 @Positive var health: Int = 100 } var player = Player() player.score = -50 // Becomes 0 (clamped) print(player.score) // 0 print(player.$score) // false (projectedValue) player.score = 100 print(player.$score) // true ``` ### 23. 결과 빌더(Result Builder)에 대해 설명하십시오 결과 빌더는 선언적 구문으로 복잡한 값을 구성하는 기능입니다. SwiftUI의 DSL 문법을 구현하는 핵심 메커니즘입니다. ```swift // ResultBuilder.swift // Result builder definition @resultBuilder struct StringBuilder { static func buildBlock(_ components: String...) -> String { components.joined(separator: " ") } static func buildOptional(_ component: String?) -> String { component ?? "" } static func buildEither(first component: String) -> String { component } static func buildEither(second component: String) -> String { component } } // Function using the builder func buildGreeting(@StringBuilder _ content: () -> String) -> String { content() } // Usage with declarative syntax let greeting = buildGreeting { "Hello" "and" "welcome" if Bool.random() { "!" } else { "." } } print(greeting) // "Hello and welcome !" or "Hello and welcome ." ``` ### 24. `some`과 불투명 타입(Opaque Type)은 어떻게 동작합니까? `some`은 불투명 타입을 선언합니다. 정확한 타입은 컴파일러가 알고 있지만 호출자에게는 숨겨집니다. 연관 타입이 있는 프로토콜에 필수적이며 컴파일러 최적화를 가능하게 합니다. ```swift // OpaqueTypes.swift // Without some: error because Collection has associated type // func makeCollection() -> Collection { ... } // ❌ Error // With some: exact type is hidden but consistent func makeArray() -> some Collection { return [1, 2, 3] // Always returns the same concrete type } // Used in SwiftUI for body struct ContentView: View { var body: some View { // Exact type inferred but hidden VStack { Text("Hello") Text("World") } } } // Difference with any (existential) func processAny(_ collection: any Collection) { // Can accept different types, runtime overhead } func processSome(_ collection: some Collection) { // Type fixed at compile time, no overhead } ``` ### 25. Swift 매크로에 대해 설명하십시오 매크로(Swift 5.9 이상)는 컴파일 타임에 코드를 생성하는 기능입니다. 타입 안전성과 디버깅 가능성을 유지하면서 보일러플레이트 코드를 줄여 줍니다. ```swift // Macros.swift // Freestanding macro: generates an expression let (x, y) = #unwrap(optionalX, optionalY) // Expands to: guard let x = optionalX, let y = optionalY else { ... } // Attached macro: modifies a declaration @Observable // Macro that generates Observable boilerplate class UserModel { var name: String = "" var email: String = "" } // Automatically generates @ObservationTracked, ObservationRegistrar, etc. // Macro for Codable with customization @Codable struct Product { let id: Int @CodableKey("product_name") let name: String // Renames JSON key @CodableIgnored var cache: Data? // Excludes from coding } // Creating a custom macro @attached(member, names: named(init)) public macro AutoInit() = #externalMacro(module: "MyMacros", type: "AutoInitMacro") @AutoInit struct Point { let x: Int let y: Int // init(x: Int, y: Int) generated automatically } ``` > **매크로 디버깅** > > Xcode에서 매크로를 우클릭하고 "Expand Macro"를 선택하면 생성된 코드를 확인할 수 있습니다. 이해와 디버깅에 유용합니다. ## 결론 이 25개 질문은 iOS 면접에서 성공하기 위해 모든 Swift 개발자가 숙달해야 할 핵심을 망라합니다. ARC를 통한 메모리 관리부터 최신 동시성 패턴까지, 각 개념은 Swift 에코시스템에 유기적으로 통합되어 있습니다. ### 복습 체크리스트 - 옵셔널과 다양한 언래핑 방법을 숙달합니다 - 값 타입과 참조 타입의 차이를 이해합니다 - weak/unowned를 통한 순환 참조 식별 및 해결 방법을 파악합니다 - async/await와 Actor를 사용한 동시성 처리를 실습합니다 - 델리게이트, MVVM, 의존성 주입 패턴을 구현할 수 있도록 합니다 - 프로토콜, 제네릭, Codable을 이해합니다 - 고급 개념을 파악합니다: 프로퍼티 래퍼, 결과 빌더, 매크로 ### 추가 리소스 보다 깊은 학습을 위해 Apple 공식 Swift 문서가 가장 신뢰할 수 있는 레퍼런스입니다. 개인 프로젝트와 코딩 연습을 통한 정기적인 실습이 이러한 개념의 정착에 도움이 됩니다. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/ios/top-25-swift-interview-questions