12월 21, 2025
27 분 읽기

주니어 iOS 개발자 면접 질문: Swift, UIKit, SwiftUI

interview
career-advice
job-search
entry-level
주니어 iOS 개발자 면접 질문: Swift, UIKit, SwiftUI
Milad Bonakdar

Milad Bonakdar

작성자

Swift, UIKit, SwiftUI, 네트워킹, 데이터 저장, 비동기 코드까지 주니어 iOS 면접에서 자주 나오는 질문과 답변을 연습하세요.


소개

주니어 iOS 개발자 면접에서는 Swift 기본기를 설명할 수 있는지, UIKit 또는 SwiftUI로 간단한 UI를 만들 수 있는지, 데이터를 가져와 디코딩할 수 있는지, 기본적인 로컬 저장 방식을 고를 수 있는지, 메모리와 스레딩 실수를 피할 수 있는지를 주로 봅니다. 좋은 답변은 짧고 구체적이며 작은 앱을 어떻게 만들지와 연결됩니다.

이 가이드는 입문 iOS 면접에서 자주 나오는 질문을 개념의 의미, 사용하는 상황, 주니어가 조심해야 할 위험까지 함께 연습하도록 구성했습니다.


Swift 기본 사항 (6개의 질문)

1. Swift에서 varlet의 차이점은 무엇입니까?

답변:

  • var: 변경 가능한 변수를 선언합니다. 초기화 후 값을 변경할 수 있습니다.
  • let: 불변 상수를 선언합니다. 값이 설정되면 변경할 수 없습니다.
  • 모범 사례: 안전과 명확성을 위해 기본적으로 let을 사용하십시오. 값이 변경될 것임을 알 때만 var를 사용하십시오.
let name = "John"  // 변경할 수 없음
var age = 25       // 변경할 수 있음
age = 26           // 유효함
// name = "Jane"   // 오류: 값에 할당할 수 없음

희소성: 매우 흔함 난이도: 쉬움


2. Swift에서 옵셔널을 설명하십시오. 옵셔널 바인딩이란 무엇입니까?

답변: 옵셔널은 nil (값이 없음)일 수 있는 값을 나타냅니다.

  • 선언: 타입 뒤에 ?를 사용합니다: var name: String?
  • 언래핑 방법:
    • 강제 언래핑: name! (위험, nil이면 충돌)
    • 옵셔널 바인딩: if let 또는 guard let을 사용하여 안전하게 언래핑
    • Nil 병합: name ?? "Default"
    • 옵셔널 체이닝: user?.address?.city
var email: String? = "[email protected]"

// 옵셔널 바인딩
if let unwrappedEmail = email {
    print("이메일: \(unwrappedEmail)")
} else {
    print("이메일 없음")
}

// Guard 구문
guard let email = email else { return }
print(email)

희소성: 매우 흔함 난이도: 쉬움


3. Swift에서 classstruct의 차이점은 무엇입니까?

답변:

  • Class: 참조 타입 (힙에 저장). 상속을 지원합니다. 참조로 전달됩니다.
  • Struct: 값 타입 (스택에 저장). 상속이 없습니다. 복사본으로 전달됩니다.
  • 언제 사용해야 할까요:
    • Struct: 단순 데이터 모델의 경우, 값 의미 체계 (독립적인 복사본)를 원할 때
    • Class: 상속, 참조 의미 체계 또는 소멸자가 필요할 때
struct Point {
    var x: Int
    var y: Int
}

class Person {
    var name: String
    init(name: String) { self.name = name }
}

var p1 = Point(x: 0, y: 0)
var p2 = p1  // 복사본 생성됨
p2.x = 10    // p1.x는 여전히 0

var person1 = Person(name: "John")
var person2 = person1  // 동일한 참조
person2.name = "Jane"  // person1.name도 "Jane"

희소성: 매우 흔함 난이도: 중간


4. Swift에서 클로저란 무엇입니까?

답변: 클로저는 코드에서 전달하고 사용할 수 있는 자체 포함된 기능 블록입니다. 다른 언어의 람다 또는 익명 함수와 유사합니다.

  • 구문: { (parameters) -> ReturnType in statements }
  • 값 캡처: 클로저는 주변 컨텍스트에서 변수에 대한 참조를 캡처하고 저장할 수 있습니다.
  • 일반적인 용도: 완료 핸들러, 콜백, 배열 작업.
// 간단한 클로저
let greet = { (name: String) -> String in
    return "안녕하세요, \(name)!"
}
print(greet("Alice"))

// 후행 클로저 구문
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled)  // [2, 4, 6, 8, 10]

희소성: 매우 흔함 난이도: 중간


5. weakunowned 참조의 차이점을 설명하십시오.

답변: 둘 다 참조 타입에서 순환 참조 (메모리 누수)를 방지하는 데 사용됩니다.

  • weak: 참조된 객체가 할당 해제되면 자동으로 nil이 되는 옵셔널 참조입니다. 참조가 객체보다 오래 지속될 수 있는 경우에 사용하십시오.
  • unowned: 객체가 항상 존재한다고 가정하는 비옵셔널 참조입니다. 할당 해제 후 액세스하면 충돌합니다. 참조가 객체보다 오래 지속되지 않을 것이 확실한 경우에 사용하십시오.
class Person {
    var name: String
    weak var apartment: Apartment?  // 순환 참조를 방지하기 위해 weak 사용
    init(name: String) { self.name = name }
}

class Apartment {
    var unit: String
    unowned let tenant: Person  // unowned - 아파트에는 항상 세입자가 있음
    init(unit: String, tenant: Person) {
        self.unit = unit
        self.tenant = tenant
    }
}

희소성: 흔함 난이도: 중간


6. Swift에서 프로토콜이란 무엇입니까?

답변: 프로토콜은 특정 작업 또는 기능에 적합한 메서드, 속성 및 기타 요구 사항의 청사진을 정의합니다. 클래스, 구조체 및 열거형은 프로토콜을 채택할 수 있습니다.

  • 유사: 다른 언어의 인터페이스와 유사
  • 프로토콜 확장: 기본 구현을 제공할 수 있습니다.
  • 프로토콜 지향 프로그래밍: Swift의 선호되는 패러다임
protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    var radius: Double
    
    func draw() {
        print("반지름 \(radius)인 원 그리기")
    }
}

struct Rectangle: Drawable {
    var width: Double
    var height: Double
    
    func draw() {
        print("사각형 \(width)x\(height) 그리기")
    }
}

희소성: 매우 흔함 난이도: 쉬움


UIKit 기본 사항 (5개의 질문)

7. UIViewUIViewController의 차이점은 무엇입니까?

답변:

  • UIView: 화면의 사각형 영역을 나타냅니다. 그리기 및 이벤트 처리를 처리합니다. 예: UILabel, UIButton, UIImageView.
  • UIViewController: 뷰 계층 구조를 관리합니다. 뷰 라이프사이클, 사용자 상호 작용 및 탐색을 처리합니다. 하나의 루트 뷰를 포함하고 하위 뷰를 관리합니다.
  • 관계: 뷰 컨트롤러는 뷰를 관리합니다. 하나의 뷰 컨트롤러는 여러 뷰를 가질 수 있습니다.

희소성: 매우 흔함 난이도: 쉬움


8. 뷰 컨트롤러 라이프사이클 메서드를 설명하십시오.

답변: 뷰 컨트롤러에는 특정 순서로 호출되는 특정 라이프사이클 메서드가 있습니다:

  1. viewDidLoad(): 뷰가 메모리에 로드될 때 한 번 호출됩니다. 한 번만 수행해야 하는 설정입니다.
  2. viewWillAppear(_:): 뷰가 화면에 나타나기 전에 호출됩니다. 여러 번 호출될 수 있습니다.
  3. viewDidAppear(_:): 뷰가 화면에 나타난 후에 호출됩니다. 여기에서 애니메이션을 시작하십시오.
  4. viewWillDisappear(_:): 뷰가 사라지기 전에 호출됩니다.
  5. viewDidDisappear(_:): 뷰가 사라진 후에 호출됩니다. 리소스를 정리하십시오.
override func viewDidLoad() {
    super.viewDidLoad()
    // 일회성 설정: UI 구성, 데이터 로드
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // 데이터 새로 고침, 알림 수신 시작
}

희소성: 매우 흔함 난이도: 쉬움


9. Auto Layout이란 무엇이며 제약 조건을 어떻게 사용합니까?

답변: Auto Layout은 다양한 화면 크기 및 방향에서 작동하는 적응형 UI를 만들 수 있는 제약 조건 기반 레이아웃 시스템입니다.

  • 제약 조건: 뷰 간의 관계를 정의합니다 (너비, 높이, 간격, 정렬).
  • 방법:
    • Interface Builder: Xcode의 시각적 편집기
    • 프로그래밍 방식: NSLayoutConstraint 또는 NSLayoutAnchor API
    • Visual Format Language: 문자열 기반 (현재는 덜 일반적임)
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)

NSLayoutConstraint.activate([
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    button.widthAnchor.constraint(equalToConstant: 200),
    button.heightAnchor.constraint(equalToConstant: 50)
])

희소성: 매우 흔함 난이도: 중간


10. framebounds의 차이점은 무엇입니까?

답변:

  • frame: 슈퍼뷰의 좌표계에서 뷰의 위치와 크기입니다. 뷰를 배치하는 데 사용됩니다.
  • bounds: 자체 좌표계에서 뷰의 위치와 크기입니다. 원점은 일반적으로 (0, 0)입니다. 뷰 내부의 콘텐츠를 그리는 데 사용됩니다.
  • 핵심 차이점: frame은 부모를 기준으로 하고, bounds는 자체를 기준으로 합니다.
let view = UIView(frame: CGRect(x: 50, y: 100, width: 200, height: 150))
print(view.frame)   // (50, 100, 200, 150) - 슈퍼뷰의 위치
print(view.bounds)  // (0, 0, 200, 150) - 자체 좌표계

희소성: 흔함 난이도: 중간


11. UITextField에서 사용자 입력을 어떻게 처리합니까?

답변: 여러 가지 접근 방식이 있습니다:

  • Target-Action: .editingChanged 이벤트에 대한 대상 추가
  • Delegate: UITextFieldDelegate 프로토콜 준수
  • Combine: 게시자 사용 (최신 접근 방식)
class ViewController: UIViewController, UITextFieldDelegate {
    let textField = UITextField()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Delegate 접근 방식
        textField.delegate = self
        
        // Target-Action 접근 방식
        textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
    }
    
    @objc func textChanged(_ textField: UITextField) {
        print("텍스트: \(textField.text ?? "")")
    }
    
    // Delegate 메서드
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

희소성: 매우 흔함 난이도: 쉬움


SwiftUI 기본 사항 (4개의 질문)

12. SwiftUI는 무엇이며 UIKit과 어떻게 다른가요?

답변: SwiftUI는 Apple의 선언형 UI 프레임워크입니다. 상태를 기준으로 UI를 설명하면, 상태가 바뀔 때 SwiftUI가 렌더링된 뷰를 업데이트합니다. UIKit은 명령형 방식으로, UIView, UILabel, UIViewController 같은 객체를 직접 만들고 변경합니다.

주니어 면접에서는 SwiftUI가 모든 곳에서 UIKit을 대체한다고 말하지 않는 것이 좋습니다. 많은 프로덕션 앱은 둘을 함께 사용합니다. 좋은 답변은 SwiftUI는 새 화면과 상태 기반 UI에 간결하고, UIKit은 기존 코드베이스, 커스텀 컨트롤, SwiftUI가 완전히 다루지 못하는 API에서 여전히 중요하다는 설명입니다.

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

희소성: 매우 흔함 난이도: 쉬움


13. SwiftUI의 @State, @Binding, @ObservedObject를 설명하세요.

답변: 이 래퍼들은 상태가 어디에 있고 누가 변경할 수 있는지를 나타냅니다.

  • @State: 하나의 뷰가 소유하는 private 상태입니다. 간단한 로컬 UI 상태에 주로 씁니다.
  • @Binding: 부모 뷰가 소유한 상태와의 양방향 연결입니다. 자식 뷰는 소유하지 않고 읽고 쓸 수 있습니다.
  • @ObservedObject: 외부 observable 모델 데이터에 대한 참조입니다. 다른 객체가 데이터를 소유하고, 변경 시 뷰가 갱신되어야 할 때 사용합니다. 최신 SwiftUI 코드에서는 Observation 프레임워크와 @Observable도 볼 수 있지만, 면접에서는 여전히 이 래퍼들을 자주 묻습니다.
class UserData: ObservableObject {
    @Published var name = ""
}

struct ParentView: View {
    @State private var isOn = false
    @ObservedObject var userData = UserData()

    var body: some View {
        VStack {
            ChildView(isOn: $isOn)
            Text(userData.name)
        }
    }
}

struct ChildView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

희소성: 흔함 난이도: 중간


14. SwiftUI에서 목록을 어떻게 만듭니까?

답변: List 뷰를 사용하여 스크롤 가능한 데이터 컬렉션을 표시합니다.

struct ContentView: View {
    let fruits = ["Apple", "Banana", "Orange", "Grape"]
    
    var body: some View {
        List(fruits, id: \.self) { fruit in
            Text(fruit)
        }
    }
}

// 사용자 지정 모델 사용
struct Task: Identifiable {
    let id = UUID()
    let title: String
}

struct TaskListView: View {
    let tasks = [
        Task(title: "Buy groceries"),
        Task(title: "Walk the dog")
    ]
    
    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

희소성: 흔함 난이도: 쉬움


15. SwiftUI에서 뷰 수정자란 무엇입니까?

답변: 뷰 수정자는 수정된 속성을 가진 새 뷰를 만드는 메서드입니다. 체인으로 연결할 수 있습니다.

  • 일반적인 수정자: .padding(), .background(), .foregroundColor(), .font(), .frame()
  • 순서가 중요: 수정자는 순서대로 적용됩니다.
Text("Hello, World!")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.yellow)
    .cornerRadius(10)
    .shadow(radius: 5)

// 사용자 지정 수정자
struct PrimaryButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

extension View {
    func primaryButton() -> some View {
        modifier(PrimaryButtonStyle())
    }
}

희소성: 흔함 난이도: 쉬움


데이터 및 네트워킹 (5개의 질문)

16. iOS에서 네트워크 요청은 어떻게 하나요?

답변: URLSession을 사용합니다. 최신 Swift에서는 주니어 답변에서도 async/await를 보여주고, 오래된 코드베이스에서는 completion handler도 여전히 쓰인다고 말할 수 있습니다. 더 실무적인 답변은 HTTP 응답 검증, 오류 처리, Codable 모델 디코딩, UI 업데이트를 main actor에서 수행하는 부분까지 포함합니다.

struct User: Codable {
    let id: Int
    let name: String
}

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode(User.self, from: data)
}

희소성: 매우 흔함 난이도: 중간


17. Swift에서 Codable이란 무엇입니까?

답변: CodableEncodable & Decodable 프로토콜의 타입 별칭입니다. Swift 타입과 JSON과 같은 외부 표현 간의 쉬운 변환을 허용합니다.

  • 자동 합성: 모든 속성이 Codable이면 Swift는 자동으로 인코딩/디코딩 코드를 생성합니다.
  • 사용자 지정 키: CodingKeys 열거형을 사용하여 다른 속성 이름을 매핑합니다.
struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

// JSON 디코딩
let json = """
{
    "id": 1,
    "name": "John Doe",
    "email": "[email protected]"
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: json)

// JSON으로 인코딩
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(user)

희소성: 매우 흔함 난이도: 쉬움


18. iOS에서 데이터를 로컬에 어떻게 유지합니까?

답변: 로컬 데이터 지속성을 위한 여러 옵션:

  • UserDefaults: 소량의 데이터 (설정, 기본 설정)를 위한 간단한 키-값 저장소
  • 파일 시스템: 문 또는 캐시 디렉토리에 파일 저장
  • Core Data: 객체 그래프 관리 및 지속성을 위한 Apple의 프레임워크
  • SQLite: 관계형 데이터베이스
  • Keychain: 중요한 데이터 (암호, 토큰)를 위한 보안 저장소
// UserDefaults
UserDefaults.standard.set("John", forKey: "username")
let username = UserDefaults.standard.string(forKey: "username")

// 파일 시스템
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let filePath = documentsPath.appendingPathComponent("data.json")

// 쓰기
try data.write(to: filePath)

// 읽기
let loadedData = try Data(contentsOf: filePath)

희소성: 매우 흔함 난이도: 쉬움


19. 동기 작업과 비동기 작업의 차이는 무엇인가요?

답변: 동기 작업은 끝날 때까지 현재 스레드를 막습니다. main thread에서 실행하면 앱이 멈춘 것처럼 보일 수 있어 위험합니다. 비동기 작업은 일을 시작한 뒤 현재 스레드가 계속 실행되게 하고, 결과는 나중에 async/await, completion handler, delegate, publisher 등으로 전달됩니다.

iOS 면접에서는 UI 반응성과 연결해 설명하세요. 네트워크 호출, 파일 작업, 무거운 처리는 main thread를 막지 않아야 하고, UI 변경은 main actor에서 실행해야 합니다.

func loadProfile() async {
    do {
        let user = try await fetchUser(id: 42)
        await MainActor.run {
            self.nameLabel.text = user.name
        }
    } catch {
        await MainActor.run {
            self.errorLabel.text = "Could not load profile"
        }
    }
}

희소성: 흔함 난이도: 쉬움


20. Grand Central Dispatch (GCD)란 무엇인가요?

답변: GCD는 dispatch queue에 작업을 예약하는 Apple의 저수준 API입니다. main queue는 UI 작업에 사용하고, global queue는 서로 다른 QoS 우선순위로 백그라운드 작업을 실행할 수 있습니다.

최신 Swift 코드에서는 비동기 코드를 더 읽기 쉽게 만들기 위해 Swift concurrency(async/await, Task, actor)를 먼저 사용하는 경우가 많습니다. 그래도 많은 UIKit 프로젝트, 오래된 코드베이스, callback 기반 API가 DispatchQueue.main.async나 background queue를 사용하므로 GCD 이해는 여전히 중요합니다.

DispatchQueue.global(qos: .userInitiated).async {
    let image = renderThumbnail()

    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

희소성: 흔함 난이도: 중간

Newsletter subscription

실제로 효과가 있는 주간 커리어 팁

최신 인사이트를 받은 편지함으로 직접 받아보세요

채용률을 60% 높이는 이력서 만들기

몇 분 만에 면접을 6배 더 많이 받는 것으로 입증된 맞춤형 ATS 친화적 이력서를 만드세요.

더 나은 이력서 만들기

이 게시물 공유

75% ATS 거부율을 극복하세요

4개 중 3개의 이력서는 사람의 눈에 닿지 않습니다. 우리의 키워드 최적화는 통과율을 최대 80%까지 높여 채용 담당자가 실제로 당신의 잠재력을 볼 수 있도록 합니다.