[면접] AI와의 면접연습 - 1
질문:
ViewController 간 데이터 전달을 구현할 때 어떤 방식들을 사용할 수 있나요?
각 방식의 장단점을 비교해서 설명해주시고, 실무에서는 어떤 기준으로 선택하시는지도 말씀해주세요.(예: delegate, closure, NotificationCenter, Combine, RxSwift 등)
답변:
1대1로 데이터를 전달할때는 흔하게 쓰이는게 delegate 패턴이랑 closure일것 같습니다. delegate의 경우엔 한번 protocol을 선언하면 그 뒤에 필요한 함수들을 편리하게 확장할수 있다는 점에서 장점이 있구요, 대신 사용하는 함수나 프로퍼티가 적은데 protocol을 선언해서 delegate로 잇는 것보단 하나의 closure프로퍼티를 추가하는게 낫습니다. 대신 closure의 경우엔 block안에서 retain cycle이 일어나지 않도록 reference capture에 주의해야할 듯 합니다. 그리고 1대다로 데이터를 전달할 시엔 NotificationCenter를 통해 broadcasting방식으로 데이터를 전달하는게 좋지만 단방향으로 데이터를 전달하는데만 특화되어 있습니다.
피드백 & 보완 포인트
- Delegate
- ✅ 언제 좋은가: 1:1, 장기 관계, 역호출 여러 개, 명확한 역할 분리.
- ✅ 확장성: 프로토콜 + 기본구현(Protocol Extension)로 선택적 메서드 형태 구현 가능.
- ⚠️ 주의: delegate는 weak(class-only 프로토콜)로 잡아 순환 참조 방지.
- ⚠️ 초기화/해제: 라이프사이클이 길면 누가 누구를 소유하는지와 해제 타이밍을 명확히.
- Closure 콜백
- ✅ 언제 좋은가: 일회성/단일 이벤트, 설정이 간단할 때.
- ✅ 장점: 코드가 짧고 지역화됨(VC 간 결합도 ↓).
- ⚠️ 주의: @escaping 여부, capture list로 [weak self] 혹은 [unowned self] 처리, 해제/취소 경로(예: 화면 닫힐 때 콜백 무시).
- ⚠️ 여러 이벤트가 필요하면 클로저가 늘어나며 가독성/유지보수 악화 → 이땐 delegate나 스트림으로 격상.
- NotificationCenter
- ✅ 언제 좋은가: 1→N 브로드캐스트, 서로 몰라도 되는 느슨한 결합.
- ⚠️ 단점: 타입 세이프티 부족(userInfo 딕셔너리), 흐름 추적 난이도, 순서 보장 없음, 양방향/요청-응답에는 부적합.
- ⚠️ 스레드: 게시된 스레드 그대로 동작 → UI 업데이트는 반드시 Main으로 전환.
- ⚠️ 리소스: 토큰 기반 옵저버는 수명 관리 필요(또는 Combine/Rx로 AnyCancellable/DisposeBag로 관리).
- 실무 선택 기준
- 관계 수와 방향: 1:1 단발 → 클로저 / 1:1 다수 이벤트·장기 → delegate / 1:N → Notification·스트림(Combine/Rx).
- 계약의 명확성: 타입 안전·계약 중요 → delegate/프로토콜.
- 규모/팀 컨벤션: Reactive 아키텍처(Combine/Rx)를 쓰면 스트림이 기본값이 되는 편.
- 수명/메모리: 순환 참조, 해제 시나리오(취소·옵저버 제거) 설계.
- 대안들도 한 줄씩 언급하면 가점
- 의존성 주입(프로퍼티/이니셜라이저 주입): 단순 데이터 전달엔 가장 명확·예측 가능.
- Coordinator 패턴: 화면 전환 + 콜백 통합 지점(특히 클로저 콜백 정리하기 좋음).
- Combine/Rx: PassthroughSubject/PublishRelay로 1→N, 라이프사이클은 AnyCancellable/DisposeBag로 관리.
- async/await & AsyncStream: 단발/비동기 콜백을 구조화된 동시성으로 단순화.
질문:
weak self와 unowned self의 차이를 설명하고, 각각을 언제 쓰는지 선택 기준을 말해주세요.
capture list에서 둘을 잘못 선택했을 때 발생할 수 있는 크래시/메모리 누수 리스크도 함께 설명하고,
간단한 코드 예시를 들어주세요. (예: 비동기 클로저, 타이머/애니메이션, Combine/Rx 구독 등)
답변:
weak로 캡처했을 때는 self의 reference count가 0이 되면 자동으로 closure에서 캡처된 참조는 nil이 되어서 안전하게 사용할 수 있다. 다만 필히 optional type으로 캡처가 되기 때문에 클로저 안에서 사용 시 optional unwrapping이 필요하다.
안전하게 사용이 가능하지만 self의 참조 유지를 보장하지 않기 때문에 흔히 쓰이는 guard let self = self else { return } 을 사용할 경우, 그 아래에 있는 코드들이 의도치 않게 실행이 안될 수 있다.
unowned의 경우엔 non-optional type으로 사용가능하지만 self의 reference count가 0이 된 이후에 접근하면 이미 크래시가 발생할 수 있다. 때문에 closure와 self의 life cycle이 백프로 동일할 때 사용해야한다.
👍 잘한 점
- weak: zero-ing(RC=0 시 자동 nil)과 옵셔널 언래핑 필요를 정확히 설명.
- unowned: non-optional이지만 수명 불일치 시 크래시 위험이 있다는 점을 정확히 지적.
- 선택 기준을 “클로저와 self의 라이프사이클 동일 여부”로 잡은 것, 실무 판단에 유효.
➕ 보완하면 가점 받는 포인트
- 언제 unowned가 실전에서 “안전한가”를 더 구체화
- “클로저의 수명이 절대로 self를 넘지 않음”이 증명 가능한 구조일 때:
- self가 클로저를 소유(예: lazy var handler: () -> Void = { [unowned self] in ... })하고,
- 그 클로저가 외부로 escape/retain되지 않도록 불변 설계가 되어 있을 때.
- 반대로, 비동기 작업/타이머/애니메이션/네트워크 콜백처럼 escape 가능성이 있으면 unowned 지양.
- “클로저의 수명이 절대로 self를 넘지 않음”이 증명 가능한 구조일 때:
- retain cycle 관점 한 줄 덧붙이기
- 순환구조 예: self → (클로저 프로퍼티) → self
- 해결: 보통 **클로저에서 weak self**로 캡처해 강한 참조 고리를 끊음.
- AnyObject 제약
- weak은 class 인스턴스만 가능 → 프로토콜 델리게이트라면 protocol P: AnyObject로 클래스 전용 표시 필요.
- 패턴 제안(실무 팁)
- “아래 코드가 의도치 않게 스킵” 문제는
- (a) self 필요 없는 작업을 guard 이전에 배치,
- (b) 혹은 옵셔널 체이닝(self?.doWork())으로 부분 실행 보장.
- unowned 대신 weak + 강한 참조로 승격(strongify)하는 패턴 권장
- “아래 코드가 의도치 않게 스킵” 문제는
- 미세 팩트
- unowned(unsafe)도 있으나 일반적으로 쓰지 않음(런타임 체크 없음, 더 위험).
- weak은 런타임의 zero-ing 비용이 있으나(오버헤드), 보통 최적화 대상이 될 정도로 크지 않음.
결론:
- 일반적으로 weak를 쓰는게 안전하며 런타임에 오버헤드가 약간 있지만 크게 신경 쓸 수준이 아니다.
- unowned는 lazy var와 같이 근본적으로 closure가 escape이 불가능한 경우에만 쓰는게 맞음
질문:
ViewController의 생명주기(Lifecycle)를 설명하고,
viewDidLoad와 viewWillAppear의 차이를 실무적으로 어떻게 구분해서 사용하는지 말해주세요.
답변:
우선 init을 통해서 뷰 컨트롤러가 초기화가 되고요.
그리고 loadView 함수가 호출이 되면서 view를 메모리에 올리게 되고 viewDidLoad 함수가 그 이후에 호출이 되고요.
그 뒤에는 viewWillAppear, 그리고 viewWillAppear 다음에는 viewIsAppearing이라고 해서 iOS 13부터 추가된 콜백 메소드가 호출이 되고 그 뒤에 viewWillLayoutSubview, 그리고 viewDidLayoutSubview가 호출이 되고 그 뒤에는 viewDidAppear가 호출이 됩니다.
그리고 뷰가 사라지기 직전에는 viewWillDisappear, 그리고 뷰가 사라지고 나서는 viewDidDisappear가 호출이 되고요. 그리고 메모리에서 해제되기 직전에 deinit이 호출이 되게 됩니다.
그리고 viewDidLoad와 viewWillAppear의 차이는 우선 viewDidLoad의 경우에는 뷰가 로드가 되고 나서 그러니까 뷰 컨트롤러의 생명 주기 동안 딱 한 번만 처음에 딱 한 번만 호출이 되는 함수이기 때문에 뷰를 초기화하거나 하는, 딱 한 번만 수행되어야 하는 작업들의 경우에 viewDidLoad에서 주로 작업을 합니다. 애드 서브 뷰라든지 그러한 작업들이 뷰 디드 로드에서 이루어지게 되고요.
viewWillAppear의 경우에는 뷰가 다른 화면으로 이동을 했다가 다시 돌아오거나 했을 때 반복적으로 실행이 되어야 할 작업 같은 것들 예를 들어서 화면을 refresh 하기 위해서 api를 호출하는 작업이라던가 혹은 화면에 다시 돌아왔으면 알려주기냐 그런 로그를 보내는 작업 같은 것들이 해당할 것 같습니다.
피드백
💯 강점 (실무 면접에서 “이 지원자는 코드 짜봤네”라고 느낄 포인트)
- 전체 생명주기 흐름을 완전히 알고 있음.
단순히 “viewDidLoad–viewWillAppear–viewDidAppear” 수준이 아니라
loadView, viewIsAppearing(iOS 13+), viewWillLayoutSubviews, deinit까지 언급한 건 매우 높은 수준입니다. - “호출 시점과 호출 횟수”를 구분해 설명.
→ viewDidLoad는 1회만 호출, viewWillAppear는 매번 호출.
→ 실무에서 가장 중요하게 묻는 포인트 정확히 짚었어요. - “무엇을 그 안에서 해야 하는가” 실무 예시 제시.
→ addSubview, API refresh, analytics event 등 구체적 예시 좋습니다.
→ 면접관 입장에서는 “이건 실제로 화면 구현 경험이 있다”로 바로 느껴집니다.
보완하면 +α 점수 받는 포인트
- loadView()와 viewDidLoad()의 구분을 한 줄로 정리
→ loadView()는 커스텀 뷰를 코드로 만들 때 override해서 직접 self.view를 할당할 수 있는 지점.
→ 대부분 storyboard 기반이라 자동으로 호출되지만, 코드 기반 UI에서는 명시적 구현 가능.
이 한 줄만 덧붙이면 “UIKit 깊이 이해 있음” 평가로 넘어갑니다. - 레이아웃 관련 콜백(viewWillLayoutSubviews/viewDidLayoutSubviews)의 활용
→ “AutoLayout 적용 이후 프레임 기반 계산이 필요할 때 호출된다”
→ 예: scrollView contentSize 계산, dynamic height 조정 등. - 메모리 해제(deinit) 시점
→ deinit이 호출되지 않는다면 retain cycle이 있는지 반드시 확인해야 한다 — 이 멘트 하나면 “메모리 관리 감각 있는 개발자”로 보여요.
질문:
비동기 코드에서 DispatchQueue.main.async를 반드시 써야 하는 상황과, 써서는 안 되는 상황을 구체적으로 구분해서 설명해주세요.
답변:
일단 View와 관련된 업데이트 작업, UI와 관련된 업데이트 작업을 할 때는 Thread와 상관없이 main.async를 통해서 업데이트를 해야 됩니다. 그리고 main.async를 쓰면 안 되는 상황이라고 하면 이제 main.thread가 아닌 다른 Thread에서 main.queue가 아닌 다른 Queue에서 main.queue에 있는 어떤 정보를 동기적으로 가져와야 되는 상황이 있을 때는 async함수가 아닌 sync함수를 통해서 가져와야 될 것 같습니다.
👍 맞춘 점
- UIKit/SwiftUI는 스레드 세이프가 아님 → UI 변경은 메인 스레드에서 해야 함(보통 DispatchQueue.main.async).
🔧 보완해야 할 점 (면접 가점 포인트)
- “항상 async로 감싼다”는 오해 수정
- 이미 메인 스레드 위라면 굳이 main.async로 한 번 더 디스패치할 필요가 없습니다.
- 불필요한 재디스패치는 렌더 타이밍/이벤트 순서를 흐트러뜨릴 수 있음.
- main.sync 사용 주의 (특히 금지 케이스)
- 메인 스레드 위에서 DispatchQueue.main.sync 호출 → 100% 교착상태(deadlock).
- 백그라운드에서 main.sync는 가능은 하지만, 호출 스레드를 블로킹하고 상호 의존이 있으면 교착 위험. 실무에선 거의 쓰지 않음.
- “써야 하는 상황”을 더 구체화
- URLSession/async 작업 콜백, CoreData 백그라운드 컨텍스트 처리 후 UI 바인딩 시점.
- Combine/Rx 구독 콜백이 백그라운드로 올 수 있을 때 → receive(on: .main) 혹은 DispatchQueue.main.async.
- Swift Concurrency에선 @MainActor / MainActor.run {}로 명시.
- “쓰면 안 되는 상황”을 더 정확히
- 무거운 계산/디코딩/파일 I/O를 main.async로 올리는 행위(프레임 드랍, 스크롤 끊김).
- 레이아웃 중(layoutSubviews, viewDidLayoutSubviews, 애니메이션 블록 안) 이미 메인인 곳에서 불필요한 재디스패치.
- 순서를 보장해야 하는 섬세한 흐름에서 무분별한 async로 이벤트 순서가 뒤섞일 위험.
한 줄 정리(면접 마무리 멘트)
- “UI 변경은 Main에서, 무거운 일은 Background에서. 이미 Main이라면 굳이 async로 재디스패치하지 않고, main.sync는 사실상 금지합니다. Concurrency 환경에선 @MainActor를 선호합니다.”
질문:
TableView나 CollectionView에서 셀 재사용 시 발생할 수 있는 버그나 UI 이상 동작을 구체적 예시로 설명하고, 이를 방지하기 위한 설계 방법을 말해주세요.
답변:
셀 재사용시 발생할 수 있는 버그라고 하면은 셀에 바인딩되어 있던 데이터가 사라지지 않고 그 다음 셀에서 그대로 노출이 되는 그런 현상이 발생할 수 있습니다. 예를 들어서 셀에서 UI 이미지 뷰가 있는데 그 UI 이미지 뷰에 바인딩하는 이미지를 비동기적으로 네트워킹을 통해서 로딩을 한다고 치면 그 이전의 셀에 바인딩되어 있던 이미지가 그 다음 셀에서 새롭게 바인딩되는 이미지가 미처 로드 되기 전까지 계속해서 이미지가 남아있게 돼서 부자연스러운 UI를 유발하는 상황이 생길 수 있을 것 같습니다. 그래서 이를 방지를 하려면은 prepareForReuse에서 반드시 이런 이미지나 바인딩되는 데이터들을 초기화 시켜주는 작업을 해줘야 합니다.
👍 잘 짚은 점
- 비동기 이미지 로딩 시 이전 셀의 이미지가 남는 현상 설명
- prepareForReuse()에서 상태 초기화 필요성 언급
➕ 가점 받는 보완 포인트
- 비동기 작업 취소
- 재사용 시점에 다운로드/Task 취소가 필수입니다. 초기화만 하면 “나중에 도착한 이전 응답”이 다시 덮어쓸 수 있어요.
- 레이아웃/성능 관련 이슈
- 제약 조건을 매 바인딩 때 중복 추가하지 않기(한 번만 설치).
- contentView의 서브뷰만 사용(셀에 직접 addSubview 지양).
- 고해상도 라운딩/마스크는 rasterize/cornerCurve 튜닝 또는 미리 렌더링된 이미지를 활용.
- 큰 리스트는 프리페치(UICollectionViewDataSourcePrefetching)로 체감 성능 개선.
- IndexPath 혼동 방지
- 응답이 도착했을 때 현재 indexPath와 바인딩 시점의 indexPath가 동일한지 확인(특히 라이브러리 미사용 시).
- 태그(tag) 기반 매칭은 취약; 가능하면 셀이 직접 관리하거나 뷰모델의 아이디(식별자)로 검증.
- 상태성 UI의 리셋
- 스위치/체크박스/버튼 선택 상태, 진행바, 재생 애니메이션 등도 모두 초기 상태로 리셋.
- addTarget/gesture 중복 추가를 피하고, prepareForReuse()에서 구독/제스처 정리.
- 플레이스홀더 & 캐싱 전략
- 로딩 중 플레이스홀더 이미지/스켈레톤 사용.
- 메모리+디스크 캐시를 통해 스크롤 시 플리커 최소화. 응답 도착 시 fade 애니메이션은 재사용 플리커와 충돌하지 않게 조건부 적용.
- Diffable Data Source와 안정 식별자
- 안정된 itemIdentifier를 사용해 잘못된 애니메이션/스냅샷 섞임 방지.
- 스냅샷 적용은 메인스레드에서, 빈번한 apply 최소화(coalescing).
한 줄 정리(면접 마무리 멘트)
- “재사용 문제의 본질은 이전 상태와 비동기 작업의 잔존입니다. 따라서 prepareForReuse의 상태 리셋 + in-flight 작업 취소 + 식별자 기반 검증을 3종 세트로 설계합니다. 여기에 프리페치/캐싱, 구독 누수 방지까지 더하면 실무에서 안정적입니다.”