iOS 개발자를 위한 Swift 면접 질문 Top 25
iOS 면접에서 가장 자주 출제되는 Swift 질문 25개를 코드 예제와 함께 완벽 정리. 옵셔널, 클로저, ARC, 프로토콜, async/await 및 고급 패턴까지 체계적으로 다룹니다.

iOS 기술 면접에서는 Swift의 기초부터 고급 개념까지 폭넓게 평가합니다. 본 가이드에서는 채용 담당자가 가장 빈번하게 출제하는 25개 질문을 언어 기본부터 최신 동시성 패턴까지 체계적으로 다룹니다.
각 질문에는 코드 예제가 포함된 상세한 답변이 제공됩니다. 질문은 기초부터 고급 개념 순으로 난이도별로 구성되어 있습니다.
Swift 기초
1. let과 var의 차이점은 무엇입니까?
let은 초기화 후 값을 변경할 수 없는 상수를 선언하고, var는 변경 가능한 변수를 선언합니다. 참조 타입(클래스)의 경우 let은 참조의 재할당을 방지하지만 객체 자체의 프로퍼티 변경은 허용합니다.
// 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는 옵셔널을 사용하여 값이 누락될 수 있는 경우의 명시적 처리를 컴파일 타임에 강제하여 안전성을 보장합니다.
// 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 nil3. struct와 class의 차이점은 무엇입니까?
struct는 값 타입(할당 시 복사)이고, class는 참조 타입(동일 인스턴스를 공유)입니다. 이 근본적인 차이는 성능, 메모리 관리, 코드 동작에 직접적으로 영향을 미칩니다.
// 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 절로 추가 조건을 지정할 수 있습니다.
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. 클로저와 그 문법에 대해 설명하십시오
클로저는 주변 컨텍스트에서 변수에 대한 참조를 캡처하고 저장하는 독립적인 코드 블록입니다. 다른 언어의 람다 또는 익명 함수에 해당합니다.
// 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는 결정론적이며 예측 가능하게 동작합니다.
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 키워드를 사용하여 이 순환을 끊을 수 있습니다.
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 // ✅ Deallocated8. weak와 unowned의 차이점은 무엇입니까?
두 키워드 모두 순환 참조를 해소하지만 보장하는 내용이 다릅니다. weak는 옵셔널이며 참조 대상 객체가 해제되면 nil이 됩니다. unowned는 객체가 항상 존재한다고 가정하며 해제 후 접근하면 크래시가 발생합니다.
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를 사용합니다.
iOS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
프로토콜과 제네릭
9. Swift의 프로토콜에 대해 설명하십시오
프로토콜은 채택하는 타입이 구현해야 하는 프로퍼티와 메서드의 계약(인터페이스)을 정의합니다. 다형성을 구현하는 핵심 수단이며 Swift의 프로토콜 지향 프로그래밍(POP)의 기반이 됩니다.
// 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과 같은 프로토콜이 높은 유연성을 갖습니다.
// 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. 제네릭은 어떻게 동작합니까?
제네릭을 사용하면 모든 타입에서 동작하는 유연하고 재사용 가능한 코드를 작성할 수 있습니다. 코드 중복을 피하면서도 컴파일 타임 타입 안전성을 유지합니다.
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
// Type constraint with where
func findIndex<T: Equatable>(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<Element> {
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<Int>()
intQueue.enqueue(1)
intQueue.enqueue(2)
print(intQueue.dequeue()) // Optional(1)12. Codable 프로토콜에 대해 설명하십시오
Codable(Encodable & Decodable의 별칭)은 Swift 타입과 JSON 등의 포맷 간 자동 직렬화를 가능하게 합니다. 모든 프로퍼티가 Codable이면 컴파일러가 구현을 자동으로 생성합니다.
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 함수는 스레드를 차단하지 않고 일시 중단될 수 있으며, 다른 태스크가 실행될 여지를 제공합니다.
// 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는 내부 상태를 동시 접근으로부터 보호하는 참조 타입입니다. 한 번에 하나의 태스크만 가변 프로퍼티에 접근할 수 있도록 보장하여 데이터 경합을 원천적으로 제거합니다.
// 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은 여러 태스크를 병렬로 실행하고 결과를 수집하는 기능을 제공합니다.
// 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 관련 코드에서 필수적입니다.
// 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 등)에서 광범위하게 사용됩니다.
// 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은 뷰가 표시할 관찰 가능한 데이터를 노출하며, 뷰의 구체적인 구현 사항을 알 필요가 없습니다.
// 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() }
}
}iOS 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
19. 의존성 주입에 대해 설명하십시오
의존성 주입은 객체의 의존성을 내부에서 생성하는 대신 외부에서 제공하는 설계 기법입니다. 테스트 용이성과 느슨한 결합을 향상시킵니다.
// 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 이니셜라이저를 사용하여 구현합니다.
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<T: Decodable>(_ 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이라는 것은 해당 클로저를 받은 함수가 반환된 이후에도 호출될 수 있음을 의미합니다. 비동기 콜백이나 클로저 저장에서 일반적으로 사용됩니다.
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란 무엇입니까?
프로퍼티 래퍼는 프로퍼티의 저장 및 접근 로직을 캡슐화하는 메커니즘입니다. 유효성 검사, 로깅, 영속화 등의 패턴을 재사용 가능하게 만듭니다.
// 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) // true23. 결과 빌더(Result Builder)에 대해 설명하십시오
결과 빌더는 선언적 구문으로 복잡한 값을 구성하는 기능입니다. SwiftUI의 DSL 문법을 구현하는 핵심 메커니즘입니다.
// 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은 불투명 타입을 선언합니다. 정확한 타입은 컴파일러가 알고 있지만 호출자에게는 숨겨집니다. 연관 타입이 있는 프로토콜에 필수적이며 컴파일러 최적화를 가능하게 합니다.
// 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 이상)는 컴파일 타임에 코드를 생성하는 기능입니다. 타입 안전성과 디버깅 가능성을 유지하면서 보일러플레이트 코드를 줄여 줍니다.
// 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 문서가 가장 신뢰할 수 있는 레퍼런스입니다. 개인 프로젝트와 코딩 연습을 통한 정기적인 실습이 이러한 개념의 정착에 도움이 됩니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
태그
공유
관련 기사

SwiftUI: iOS를 위한 모던 인터페이스 구축 가이드
SwiftUI로 모던 UI를 구축하는 방법을 알아봅니다. 선언적 구문, 컴포넌트, 애니메이션, iOS 18 모범 사례를 종합적으로 다룹니다.

데이터 분석가를 위한 SQL: 윈도우 함수, CTE, 고급 쿼리 기법
SQL 윈도우 함수, CTE(공통 테이블 식), 고급 분석 쿼리를 실용적인 코드 예제와 함께 설명합니다. 데이터 분석가 면접 준비와 실무에 필수적인 기법입니다.

2026년 데이터 애널리틱스 면접 질문 TOP 25
2026년 데이터 애널리틱스 면접 대비 가이드입니다. SQL, Python, Power BI, 통계, 행동 면접에서 자주 출제되는 25개 질문을 코드 예시와 함께 상세히 해설합니다.