개요
안녕하세요, iOS 개발자로 지원한 이규진입니다.
저는 지난 2년 4개월간 포시에스에서 iOS 개발을 전담하며 실무 역량을 쌓아왔습니다.
주요 업무로 전자문서 SDK 개발과 이를 활용한 앱 개발을 맡았습니다.
특히 Objective-C 기반 제품을 Swift로 전환하는 현대화 프로젝트를 주도하였습니다.
저는 업무에서 가장 중요한 가치는 신뢰라고 생각합니다.
제가 개발했던 SDK는 여러 서비스의 기반이 되는 만큼,
사소한 디테일 하나가 곧 신뢰로 이어진다고 생각했습니다.
따라서 작은 성능 저하나 예외 상황도 끝까지 책임지고 해결해왔습니다.
지금까지 쌓아온 저의 역량을 바탕으로, 이제는 더블미디어의 서비스에 기여하고 싶습니다. 잘 부탁드립니다.”
Objective-C → Swift 전환 프로젝트 관련
3만 라인 전환 시 가장 어려웠던 상호 운용성(Interoperability) 문제는 무엇이었으며, 어떻게 해결했나요?
- 가정 어려웠던 점은 ObjC의 동적 디스패치와 런타임의 유연함에 의존하던 레거시 로직을 엄격한 정적 디스패치 타입과 타입 시스템으로 옮기는 과정이었습니다.
objc_msgSend를 대체하기 위해 Static Dispatch 기반 Custom VTable을 설계했다고 하셨는데, 구체적인 설계 방식과 이를 통해 얻은 성능 이득은 무엇인가요?
- objc는 함수 호출이 메시징 기반으로 objc_msgSend를 사용하기 때문에 런타임에 메서드를 찾는다.
- 그러나 swift는 VTabel이나 Static Dispatch를 사용하여 컴파일 타임에 호출할 주소를 찾는다.
- 기존 objc에서는 웹에서 string으로 함수명이 넘어오면 objc_msgsend로 어떤 메서드든 실행가능
- 스위프트에서는 그게 안된다 그러나 모든 호출을 if else로 분기 처리하기엔 관리 포인트가 너무 많아졌다.
- 따라서 명령어 문자열과 클로저 형태의 실행 로직을 매핑하는 구조를 만들고 각 클래스 별로 init할 때 등록함
- 해당 클래스 맵을 웹뷰에서 들고있으며 명령이 들어오면 해당 기능를 객체의 테이블에서 찾음
- objc_msgSend는 매번 클래스 계층구조를 타고 올라가며 셀렉터를 찾지만, hash table에서 키밸류 형태로 하여 검색이 o(1)로 오버헤드가 없음
- Swift에서도 @objc_dynamic이라는 키워드로 쓸수있었지만, 저는 Swift 전환하는 목적중 하나가 런타임의 불확실성을 제거하고 성능을 최적화하는 것이었음
- 그래서 조금 번거롭더라도 직접 테이블 구조를 설계하여 컴파일 시간에 메서드 호출 타이밍을 정하는 스위프트의 특성을 살리려고 노력햇음
전환 후 코드 라인 수를 30% 줄였는데, Swift의 어떤 문법적 특성이 이를 가능하게 했나요?
- h,m을 나누어 작성하고 인터페이스와 구현부를 따로 하던거를 합친거
- 기존에 타입별로 각각 구현한 것을 제네릭으로 공통화, 웹뷰 메시지 처리시의 프로토콜기반의 룩업 테이블로 대체
- 고차함수
- swift concurrency 도입
- 구조체 적극 활용(initalizer) 생성 불필요 값타입
메모리 및 성능 관리
MRC 환경에서 C++ 코어 로직과 객체 생명주기를 동기화할 때 발생한 메모리 누수 위험을 어떻게 관리했나요?
- c++ 객체는 힙 메모리에 생성되어 수동으로 delete를 호출해야하지만 objc-C객체는 그게 아님
- c++ 객체의 생명주기를 objc 객체의 생명주기에 종속시킴 init에서 cpp객체도 만들고 dealloc될때 delete되게 함
- 그리고 cpp로직으로 objc 객체를 보내기 전에 retain을 하고 그 뒤에 release를 함
Instruments를 활용해 하이라이트된 Hang이나 Frame drop 이슈를 해결한 구체적인 사례가 있나요?
- 버그중 하나인데 잘못된 url로 쐇을때 리턴을 바로 안준다 왜냐면 보냈을때 얘가 서버가 잇는데 무응답인거랑 아예 없는지를 알수가 없기때문에 타임아웃을 기다림
- cpp 핵심 로직을 타기 때문에 그동안 화면이 멈추는 hang이 걸렷음 따라서 비동기로 처리함
- 불필요한 레이아웃 재계산이 프레임드랍의 원인이 됨 매번 layoutIfNeeded를 강제 호출해서 동기적으로 레이아웃을 잡는대신 setNeedsLayout으로 사용함
비동기 처리 및 아키텍처
서버 통신 시 발생한 ‘콜백 지옥’을 Swift Concurrency로 리팩토링할 때, 기존 Combine이나 GCD 방식과 비교해 어떤 이점이 있었나요?
- 신규앱 개발당시 서버 통신 -> 암복호화 -> 서버통신 -> 로컬 DB저장 -> UI업데이트로 이어지는 연속적인 비동기 로직
- 특히 모든 클로저마다 에러처리를 중복해서 작성해야함
- Combine은 선언적으로 처리할 수 잇어 깔끔해졌지만 로직이 복잡해질수록 하나의 코드 블럭으로 끝내야하는게 흐름을 쫒아가기 힘들었음
- 단일 비동기일때에도 불필요하게 스트림을 생성해야한다는게 에바쎄바
- asyncawait은 선형적인 코드 작성이 되었음
- 에러 처리를 중앙 집중화 가능함
- gcd방식은 개발자가 실수로 UI를 백그라운드에서 업데이트 할 위험이잇었음 이건 런타임에 발견됨
- swift concurrency의 @MainActor로 ui 업데이트 코드를 컴파일 타임에 체크할 수 있었음
- gcd는 작업이 블로킹 될때마다 새로운 스레드를 생성하여 스레드 폭발이 일어날 위험이 있음
- 그러나 swift concurrency는 코어 개수에 맞춰 스레드를 제한적으로 관리
- gcd로 작업을 던지면 시스템은 스레드 풀에서 노는 스레드가 잇는지 확인함 없으면 만듬
- 만약 실행중인 작업들이 네트워크 요청같은걸로 블락 되면 시스템은 작업 처리를 위해 새로운 스레드를 생성
- 스레드는 약 512kb~ 1mb의 스택메모리를 점유 그럼 스레드가 100개면 100메가 정도의 메모리를 생성함
- 작업을 위해 스레드를 늘리는게 gcd
- 앱이 실행되면 기본적으로 하나의 프로세스 메모리 공간을 가짐
- 여기서 gcd가 새로운 스레드를 만들면 각 스레드가 자기만의 로직을 실행할 수 잇도록, 프로세스의 공용 메모리 구역중 스택영역의 일부를 떼어줌
- 코드는 앱의 바이너리 크기와 직접적으로 연괌됨 앱 실행시에 램에 로드됨 (10메가 ~ 수백메가)
- 데이터는 전역이나 static이 저장되어 매우 작음 1~10메가 static은 lazy 할당 방식으로 실제 접근 시점에 로드됨
- 스택은 함수 호출 시 지역변수 복귀주소등을 저장함
- 힙은 앱 메모리의 대부분임 클로저 클래스 이미지 네트워크등 내가 대충 생각하는 데이터들이 여기에 동적으로 할당됨
- 잠깐 딴길로샛음
- await을 만나면 현재 실행중이던 함수는 그 즉시 cpu 사용권을 내려놓는다.
- 그럼 시스템은 그걸 위임받아 알아서 돌림 그리고 그 일을 하던 스레드는 스레드 풀로 돌아가 다른 작업을 함
- gcd엿으면 스레드가 램의 스택 공간을 통째로 잡고 잠들엇지만 sc는 필요한 정보만 힙으로 보냄
- 현재 함수의 지역 변수 값, 다음에 실행해야할 코드 위치등을 담은 continuation 객체를 생성해서 힙에 저장
- 스택은 해당 스레드가 꽉 잡고 잇는 공간이지만 힙은 모든 스레드가 공유하는 공간임
- 따라서 나중에 다른 스레드가 해당 정보를 가져와서 작업을 이어나갈 수 있음
- 스레드 폭발이 앵간해서 없음
- aysnc 함수 내부에서 await이 아니라 thread.sleep나 무한 루프등 동기적 블로킹을 하면
- 스레드가 굳어버림 cpu는 점유하고잇는데 일은 안하고잇음 gcd는 스레드를 더 만들어서 대응햇겟지만 sc는 아님
- await 전후의 데이터값을 보장할 수 없음 따라서 가변 데이터 같은 경우는 actor를 활용해야함
- 너무 가벼운 작업은 힙에 저장하고 스케쥴링 하는 비용이 더 비쌈 그래서 코드를 매우 잘짜야됨
- 너무 무거운 연산은 Task.detached같은 것으로 스레드를 양보하도록 설계해야됨
- await은 단순한 대기가 아니라 상태가 변할 수 있는 틈
- 따라서 actor를 써야하는데 내부적으로 직렬 큐처럼 동작함
- 한번에 하나의 task만 접근할 수 있도록 보장
- actor 내부 함수에서 await을 호출하면 잠시 중단될때 다른애들이 접근할 수 잇음 따라서 중요 상태 변경로직은 동기로 묶어서 처리
- actor안에 잇지만 굳이 보호받을 필요없을때는 nonisolated
Swift6
- 스위프트 6의 핵심은 데이터 레이스를 원천적으로 차단하게되엇음
- 이전에는 잠재적 동시성 오류들이 컴파일 에러로 처리됨
- 서로 다른 격리구역으로 데이터가 전달될때 데이터가 안전하게 복사되거나 공유될 수 잇는지 Sendable을 엄격하게 검사함
- 서로 다른 격리구역이란 actor나 스레드가 다를 때임
- 불변성이나 격리가 보장되어야함 이는 값타입이어야됨 final 클래스인데 모두 상수로 되어있으면 됨
- Actor는 직렬 접근을 보장하니깐 그 자체로 Sendable임
- @Sendable 키워드를 붙여서 클로저가 캡쳐한 변수들이 스레드간 이동에 안전함을 보장해야됨
- 클래스 객체를 비동기 Task에 담아 보낼때 오류가 뜨는데 값타입을 왠만해선 쓰거나 Actor로 선언하엿음
- 외부 라이브러리같이 수정이 불가능한 경우 preconcurrency를 사용하거나 데이터 레이스가 없음을 확신할 때 nonisolated(unsafe)를 사용함
- 그래도 보내야할 때가 필요하다면 클래스 인스턴스 자체를 보내지말고 실제 필요한 정보만 추출해서 보냄
현재 개인 프로젝트에서 TCA(The Composable Architecture)로 리팩토링 중이신데, MVVM에서 TCA로 넘어가려는 명확한 이유가 무엇인가요?
- 앱이 커지면 여러개의 뷰모델이 하나의 뷰에서 각자의 상태를 가짐
- 특정 이벤트가 발생했을 때 여러 뷰모델의 상태를 동시에 업데치트해야한다면 추적하기가 매우어려움
- 기능단위 state로 관리함 모든 변화는 액션을 통해야하고 리듀서에서만 처리함 누가 언제 왜 이값을 바꿨는지를 찾기 쉬움
- mvvm은 뷰모델 내부에서 함수가 함수를 부르고 그 안에서 다시 상태를 바꾸는등 데이터 흐름이 양방향임
- action->reducer -> state update -> View로 이어지는 단방향 데이터 흐름임
암호화 알고리즘 경량화를 통해 QR 인식률을 높였다고 하셨는데, 보안성과 인식 속도 사이의 트레이드오프를 어떻게 조율했나요?
- 기존의 무거운 공개키 방식 위주에서 대칭키 방식을 혼합하거나 EC 알고리즘으로 전환하여 보안성은 유지하면서 연산량을 줄엿습니다.
- body를 검증하기 전 헤더 정보를 검사하여 유효하지 않으면 드랍하는 예외처리
- Swift Concurrency 적용
- aes는 대칭키 암호화이다 암복호화를 같은 키를 사용한다. 상대방에게 키를 안전하게 전달하는 것이 어려움
- rsa는 비대칭키 암호화. 공개키로 잠그고 개인키로만 열 수 있음
- 이걸 내가 제안하진 않았다 안드로이드, 서버도 동일한 알고리즘으로 암복호화를 해야했기때문에 신입에겐 뭔가를 할 수 없었다
- 그러나 rsa 위주의 기반의 암복호화 로직을 ec로 바꾸는 것을 전담했고 실제 qr 인식률이 얼마나 좋아졌는지 검증하는 과정을 주도함
플러터 경험이 전혀 없었음에도 고객사 요청에 대응하기 위해 샘플을 제작했습니다. 생소한 기술 스택을 빠르게 익히는 본인만의 노하우가 있나요?
- 핵심 매커니즘의 공통점을 찾습니다.
- 모든 문법과 api를 익힌다기보다 제가 알고있는 iOS 기술 스택에서의 공통점과 차이점을 먼저 파악합니다.
- 플러터를 처음 접햇을 때 제가 알고 있는 SwiftUI의 선언형 ui와 비슷하였고 상태 변화에 따라 UI가 다시그려지는 이런 핵심원리부터 파악합니다.
- 그리고 가장 작은 단위 뷰를 빠르게 구현해보며 큰 구조와 작은 구조를 반복하며 최대한 익숙해지려고 합니다.
- 그리고 이 과정에서 궁금했던 것들은 제 개인 블로그에 적어놓습니다. 일단 진행하고 나중에 보면 자동으로 습득되는 질문들도 많기 때문이다
- 저에게 새로운 기술은 단지 처음 접하기 때문에 익숙하지 않은것이라고 생각합니다. 이처럼 어떤 환경에서도 핵심을 빠르게 짚어내는 인사이트를 가지고 있는 개발자입니다.
관리자에게 기술 부채 해소(Swift 전환)의 중요성을 설득했다고 하셨는데, 어떻게 했나여
- 결정권자에게 코드가 단지 더럽다는 이유로의 접근은 통하지 않았습니다.
- 신입의 입장에서 바라본 제품을 솔직하게 말씀드렸다 신규 입사자가 적응하는데에 오래걸린다 더군다나 저 전에 취업한 사람도 스위프트 하고싶다고 나간전적이 있었기에 설득이 쉬웠다
- 스위프트 특성상 런타임 크래시를 획기적으로 줄일수 잇다고 햇다. 그리고 처음부터 하는게 아니라 하나의 프로젝트에서 바텀 업방식으로 하는 예시를 보여드리면서 했다.
“디테일이 신뢰를 만든다”는 철학이 실제 코드 작성 시 어떻게 투영되나요?
- 불안 요소를 사전에 제거하여 신뢰를 구축하는 것
- 주석
- 사소한것
iOS를 넘어 안드로이드, 웹, 서버까지 다루는 개발자를 지향하신다고 했는데, 현재 이를 위해 구체적으로 준비하고 있는 것이 있나요?
- 언어와 특정 플랫폼 지식을 넘어서는 CS의 본질을 보려고 한다.
- 단순히 Swift Concurrency가 좋은 건 알겠다. 사용방법도 알겠다. 거기에서 그치는 것이 아니라 왜 그러는지 본질을 파악하려고 노력한다.
- 그러다 보면 웹이든 서버든 안드로이드든 본질은 똑같다는 것을 알게 되고 나중에는 큰 틀을 설계할 수 있는 능력을 가질수잇다고 생각한다.
- 현재는 iOS에 집중하고 있으며 계획으로는 Todaypic 개인프로젝트의 안드로이드와 서버를 직접 구현해서 하나의 큰 서비스를 만들어보는 계획을 향후 1년, 2년 안에 가지고있다.
더블미디어
패킷 구조: HTTP POST로 데이터 전송 시 헤더와 바디의 차이를 그리세요. (CRLF 개념 포함)
- Post /login HTTP/1.1
- Host: api.com.
- Content-Type: application/json
- Content-Length: 12
- crlf
- {“id”, “ho”}
패킷이 가로채지면 위험할텐데 그거 어떻게 방지하세요?
- 통로 자체를 암호화하는겁니다 그걸 한것이 https이구요
- 해커가 중간에서 가로채도 암호화된 덩어리만 보일뿐
https는 안전한데 왜 암호화해서 바디를 보내요?
- https는 통로를 보호하지만 서버로그나 중간 프록시 서버에서 데이터가 평문으로 노출될수잇다.
HTTP 메서드: GET, POST, PUT, PATCH, DELETE의 차이와 멱등성에 대해 설명하세요.
- get은 서버의 상태를 변경하지 않고 리소스를 가져옴, 멱등함 (100번을 가져와도 결과가 달라지지않음)
- post는 새로운 리소스를 생성함, 멱등하지 않음(100번을 가져오면 결과가 계속달라짐, 생성함)
- put 리소스를 통째로 갈아끼운다(멱등함, 100번을 갈아끼워도 달라지지않음)
- patch 리소스 일부만 변경(상황에 따라 다름)
- delete 멱등함(100번을 지워도 똑같음)
put이랑 post랑 둘다 생성하는데 뭔차이?
- 리소스의 id를 누가 붙이냐에 따라 다르다
- 앱에서 id를 정해서 서버에 등록하고싶으면 put
- 서버가 알아서 해주고싶으면 post
vtable
핵심 답변 요약
- Swift는 컴파일 타임에 타입과 메서드가 결정되는 Static Dispatch를 지향하므로,
- 런타임에 문자열만으로 메서드를 호출하는 objc_msgSend 같은 기능이 기본적으로 제한적입니다.
- 이를 해결하기 위해 함수 포인터를 딕셔너리에 저장하는 Custom VTable을 설계하여,
- 성능 손실을 최소화하면서도 런타임 유연성을 확보했습니다.
상세 답변 포인트
- Swift의 제약 사항 극복문제: Swift의 NSObject를 상속받지 않는 일반 클래스나 struct는 런타임에 메서드 이름을 문자열로 받아 호출할 방법이 없습니다.
- @objc dynamic을 사용할 수도 있지만, 이는 모든 메서드를 Objective-C 런타임에 의존하게 만들어 성능 오버헤드를 발생시킵니다.
- 해결: VTable 클래스를 통해 메서드를 클로저(([Any]) -> Any?) 형태로 등록(register)하고 관리함으로써, Swift 순수 코드만으로도 동적 호출 시스템을 구축했습니다.
- 타입 안전성(Type Safety) 확보설계 특징: VTable의 register 메서드는 제네릭(Generic)을 사용합니다.
- register<Arg1, Arg2>(…)와 같이 인자 개수별로 오버로딩되어 있습니다.
- 내부적으로 as? Arg1 형태의 타입 캐스팅(Type Casting) 과정을 거쳐, 잘못된 타입의 인자가 들어왔을 때 OZLogger.error를 발생시키고 실행을 차단합니다.
- 효과: objc_msgSend는 잘못된 인자를 넘기면 앱이 즉시 크래시(Crash)되지만, 이 구조는 안전하게 에러 로그를 남기고 종료됩니다.
- ③ 유연한 확장성 (DynamicCallable)DynamicCallable 프로토콜과 execute 메서드를 통해, 어떤 객체든 vtable만 가지고 있으면 외부(예: JavaScript 인터페이스, JSON 명령 등)에서 들어오는 문자열 명령을 즉시 실행할 수 있는 구조를 만들었습니다.
- 실제로 OZTotoFramework 클래스의 setupVTable()에서 openWindow, closeWindow 등을 등록하는 과정이 이 설계의 핵심 활용 사례입니다.
기술적인 장점
- 딕셔너리 기반의 함수 참조 호출이므로, 복잡한 Objective-C 런타임 메시지 전달보다 빠르고 예측 가능합니다.
- 디버깅OZLogger를 통해 어떤 메서드가 호출되었고, 어디서 인자 타입이 틀렸는지 추적이 매우 용이합니다.
- 결합도 분리호출자(Caller)는 객체의 구체적인 타입을 몰라도 execute(method:argument:)만으로 기능을 수행할 수 있습니다.
“Swift에서 objc_msgSend를 안 쓰고 왜 직접 VTable을 만드셨나요?”
- “Swift는 정적 타이핑 언어이기 때문에 런타임에 문자열 이름으로 메서드를 호출하는 것이 까다롭습니다.
- 특히 저희 프로젝트처럼 JavaScript와 네이티브 간의 브릿지 역할(applyAsyncReturn 등)이 빈번한 경우,
- 매번 수동으로 switch-case 문을 작성하는 것은 유지보수에 불리하다고 판단했습니다.그래서
- 제네릭을 활용한 Custom VTable을 설계했습니다.
- 메서드 등록 시점에 클로저가 실제 인자 타입을 캡처하고, 호출 시점에 as? 캐스팅을 통해 검증하기 때문에 Objective-C의 동적 호출보다 훨씬 안전합니다.
- 결과적으로 objMap[id]?.execute(method: name, argument: args) 한 줄로 어떤 객체의 메서드든 안전하게 실행할 수 있는 범용적인 동적 호출 시스템을 구축할 수 있었습니다.”
CORS: iOS Native 앱과 웹뷰 환경에서 CORS 에러가 발생하는 원인과 해결 방안은?
UDP vs TCP: 실시간 스트리밍 서비스에서 UDP가 선호되는 이유와 유실 대응 전략은?
OSI 7계층: 패킷이 실제 전기 신호로 바뀌어 나가는 과정을 계층별로 설명하세요.
- [iOS/Swift 심화] - 규진 님 이력서 검증 Swift 6 Concurrency: Strict Concurrency를 적용하며 겪은 트러블슈팅 사례와 Sendable 준수 방법은?
메모리 관리: Dirty Page와 Clean Page의 차이, 그리고 iOS에서 OOM이 발생하는 메커니즘은?
아키텍처(TCA): 단방향 데이터 흐름의 장점과, 거대해진 State를 관리할 때의 성능 최적화 방안은?
레거시 전환: 3만 라인의 Obj-C 코드를 Swift로 바꿀 때, 안정성을 확인하기 위한 본인만의 전략은?
암호화(ECC): 왜 RSA가 아닌 ECC를 선택했나요? 하드웨어 자원(CPU/RAM) 관점에서 수치적 이점은?
- [OS 및 CS 기초] - 하드웨어 이해도 프로세스 vs 쓰레드: 두 개념의 메모리 구조(Stack, Heap 공유 방식) 차이를 설명하세요.
멀티 쓰레드: iOS에서 Race Condition을 방지하기 위한 방법(Lock, DispatchSemaphore, Actor)들의 차이는?
UMA(통합 메모리): 애플 실리콘의 UMA 구조에서 CPU와 GPU 간 데이터 전송을 최적화하는 방법은?
- [상황 가정 및 문제 해결] - 후기 속 ‘답정너’ 대비 성능 vs 마감: “기능 구현은 끝났는데 메모리 점유율이 높습니다. 출시일은 내일입니다. 어떻게 하시겠습니까?”
기술 갈등: “본인은 Swift 6를 쓰고 싶지만, 팀장은 안정성을 위해 Swift 5와 기존 라이브러리 고수를 명령한다면?”
장애 추적: “특정 기기에서만 앱이 시작하자마자 죽습니다. 로그도 안 남는 상황이라면 어떻게 원인을 파악하겠습니까?”
코드 규칙: “팀에 컨벤션이 전혀 없어 코드가 엉망입니다. 본인이 이를 바로잡기 위해 가장 먼저 도입할 규칙은?”
- [인성 및 플랫폼 이해] - 더블미디어 특화
서비스 이해: “팬더티비 플랫폼의 특성(실시간성, 콘텐츠 민감도)에 대해 평소 어떻게 생각하시나요?”
- “플랫폼의 성격에 대해 충분히 인지하고 있으며, 저는 이를 ‘기술적 난도가 높은 비즈니스 모델’로 바라보고 있습니다.
팬더티비처럼 성인 타겟의 실시간 라이브 플랫폼은 일반적인 앱보다 훨씬 강력한 성인 인증 시스템, 개인정보 보호, 그리고 안정적인 결제 시스템(ECC 등)이 뒷받침되어야 합니다. 또한, 실시간으로 발생하는 방대한 데이터와 트래픽을 처리하는 기술력은 iOS 개발자로서 제가 가장 도전해보고 싶은 영역입니다.
저는 콘텐츠의 성격과 관계없이, 사용자가 신뢰하고 안전하게 서비스를 이용할 수 있도록 견고한 인프라와 직관적인 UI를 제공하는 것이 개발자의 본분이라고 생각합니다. 오히려 이러한 민감한 콘텐츠를 다루는 플랫폼일수록, 기술적으로 더 완벽한 보안과 운영 툴을 구축하는 데 기여하고 싶습니다.”
이직 사유: “이전 회사에서 큰 성과를 냈음에도 불구하고 우리 회사에 지원한 진짜 이유는 무엇입니까?”
“저는 B2B 환경에서 SDK를 개발하며, 한정된 자원 안에서 최상의 안정성과 성능을 뽑아내는 훈련을 해왔습니다. SDK는 수많은 서비스의 기초가 되기에 코드 한 줄의 실수가 대규모 장애로 이어질 수 있다는 책임감을 체득했습니다.
이제는 그 기술적 역량을 바탕으로, 실제 사용자의 목소리가 실시간으로 들리는 B2C 플랫폼에서 승부를 보고 싶어 이직을 결심했습니다.
팬더티비는 초 단위로 유저의 반응이 오가는 라이브 스트리밍 서비스입니다. 제가 SDK를 만들며 고민했던 메모리 효율화와 라이브러리 최적화 경험을 앱에 녹여낸다면, 수만 명의 동시 접속자가 몰리는 상황에서도 끊김 없는 시청 경험을 제공하는 데 큰 기여를 할 수 있다고 생각합니다. 공급자로서의 꼼꼼함과 해결사로서의 열정을 팬더티비에서 증명하고 싶습니다.”
협업 스타일: “동료 개발자가 본인의 코드 리뷰에 대해 감정적으로 대응한다면 어떻게 대처하시겠습니까?”
워라밸: “서비스 장애가 주말이나 새벽에 발생했을 때, 본인의 대응 기준은 무엇입니까?” (후기 중 ‘워라밸’ 질문 관련)
HTTP POST로 a=b라는 데이터를 보낼 때, 실제 패킷의 Header와 Body는 어떻게 구성되는지 화이트보드에 그려보세요.
- POST /login /api/data HTTP/1.1
- Host: example.com
- Content-Type: application/json
- Content-Length: 3
- User-Agent: iOS/APP
- 빈줄
- a=b (body)
- 헤더와 바디는 빈줄로 구분함
암호화된 데이터를 보낼 때는 어떻게 달라지나요?
- Content-Type이 application/octet-stream으로 보냄 바이너리 데이터 보낼때 사용
- image/jpeg, image/png
- application/pdf
- application/json : 데이터 객체
- application/x-www-form-urlencoded: 주소창 쿼리 스트링 형태
Get은요?
- body가 없고 데이터가 url 뒤에 query string으로 붙어 전송됨 헤더 첫줄에 모든 데이터가 포함됨
- 길이 제한이 있어 대용량 데이터를 보낼수없음, 서버의 상태를 변경할 수 없음, 캐싱가능
- GET /users?id=kyujin HTTP/1.1
- Host: api.todaypic.com
- Accept: application/json
PUT은요
- 리소스를 통째로 갈아끼움
- 이름 나이 주소일때 다 보내야됨 안보내면 null
- PUT /users/123 HTTP/1.1
- Content-Type: application/json
- Content-Length: 32
- 빈줄
- {“name” : “kyujin2”, “age” : 28}
PATCH는요
- 리소스의 일부 속성만 변경함
- 이름 나이 주소중 주소만 변경하고싶으면 사용
- PATCH /users/123 HTTP/1.1
- Content-Type: application/json
- Content-Length: 20
- 빈줄
- {“name” : “kyujin3”}
Delete는요
- 리소스를 삭제함
- Delete /users/123 HTTP/1.1
- Host: api.todaypic.com
iOS 앱(Native) 환경에서도 CORS 에러를 마주칠 수 있는 시나리오는 무엇이며, 이를 클라이언트와 서버에서 각각 어떻게 해결합니까?
- cors(cross origin resource sharing)은 protocol, host, port가 다른곳에서 온 요청을 거부하는것
- 너는 내 식구가 아닌데 왜 데이터를 달래? 하면서 에러 뱉음
- 근데 이건 브라우저의 보안정책으로 웹뷰를 사용할 때 발생할 수있음
- 서버 응답헤더에 Access-Control-Allow-Origin헤더에 클라이언트의 도메인을 설정하여 브라우저에게 접속을 허용함을 알려야함
- Native Proxy: UserScript나 MessageHandelr로 URLSession을 사용하도록함
그럼 iOS에서는 CORS같은 위험상황을 대처하는건 없는것인가요? 앱에서 post 요청을 보내면 어떤 구조인가요
- 브라우저는 불특정 다수의 사이트에서 접근한다 그러나 iOS 앱은 출처가 확실한 실행파일
- ATS(app transport security): 암호화되지 않은 http 통신은 차단
- 중간자 공격: cors가 안막아줘서 공공 와이파이등에서 패킷이 탈취될 가능성
[OS] 프로세스와 스레드의 차이를 메모리 구조(Stack, Heap) 관점에서 설명하고, 멀티 스레드 환경에서 발생할 수 있는 데이터 레이스를 하드웨어 레벨에서 어떻게 방지하나요?
[OS] 가상 메모리(Virtual Memory) 시스템에서 페이지 폴트(Page Fault)가 발생했을 때, OS가 이를 처리하는 과정을 단계별로 설명해주세요.
- iOS 및 Swift 심화 (이력서 기반) 규진 님이 기술한 ‘디테일’과 ‘최적화’ 성과를 검증하는 질문들입니다.
[Swift 6] Swift 6를 도입하면서 Strict Concurrency를 적용했을 텐데, 기존의 클로저나 콜백 기반 코드에서 Sendable 위반 문제를 어떻게 해결했나요?
[메모리] Dirty Page와 Clean Page의 차이를 설명하고, 왜 iOS에서는 Dirty Page가 OOM(Out of Memory)의 직접적인 원인이 되는지 설명해보세요.
[메모리] Instruments(VM Tracker)를 활용해 메모리 릭을 잡았던 구체적인 사례를 들려주세요. (Refcount 문제 외에 어떤 케이스를 보셨나요?)
[아키텍처] TCA(The Composable Architecture)를 도입했을 때, 단방향 데이터 흐름이 앱의 성능(특히 렌더링 부하)에 미치는 영향은 무엇이었나요?
- 성능 및 암호화 (규진 님 필살기) 이력서의 가장 강력한 무기인 ‘경량화’에 대한 꼬리 질문입니다.
[알고리즘] 기존 RSA에서 ECC(Elliptic Curve Cryptography)로 전환했을 때, 실제 CPU 연산 횟수(명령어 수)가 어느 정도로 감소했는지 수치적으로 설명할 수 있나요?
[하드웨어] Apple Silicon의 Unified Memory Architecture(UMA) 환경에서 CPU와 GPU가 데이터를 공유할 때, Zero-copy를 달성하기 위해 코드를 어떻게 작성해야 하나요?
[최적화] 앱의 Launch Time(t1)을 측정했을 때, 정적 라이브러리가 많아지면 생기는 병목 지점(dyld 과정)을 구체적으로 짚어보세요.
- 상황 가정 및 답정너 질문 (태도 검증) 후기에서 악명 높았던 “특정 상황 가정”에 대한 대응 질문입니다.
[기술 소신] “우리는 안정성을 위해 최신 기술인 Swift 6나 TCA 대신, 구식 Objective-C와 MVC 패턴을 고수하기로 했습니다. 본인의 기술적 성장 방향과 배치되는데 어떻게 하실 건가요?”
[문제 해결] “특정 고객사 기기에서만 암호화 모듈이 0.5초 더 느리게 작동한다는 리포트가 왔습니다. 코드는 동일한데 하드웨어 사양도 같습니다. 무엇부터 의심하고 확인하시겠습니까?”
[협업/규칙] “코드 컨벤션이 전혀 없는 팀에 합류했습니다. 본인이 규칙을 만들려 하는데 팀장님이 ‘기능 구현이 우선’이라며 거부한다면, 어떤 디테일을 포기하고 어떤 것을 챙기겠습니까?”
[이직/인성] “이전 회사에서 3만 라인이나 전환하는 큰 성과를 냈는데, 왜 그 성과를 뒤로하고 우리 회사(팬더티비 관련)에 지원했나요? 우리 플랫폼의 성격에 대해 거부감은 없나요?”
iOS rhdqn
컴퓨터 시스템에서 CPU, RAM, 저장 장치의 역할과 이들이 어떻게 상호 작용하는지 설명해주세요.
- 사용자가 앱을 실행하면 스토리지에 잇던 데이터가 램으로 올라온다
- cpu는 램에 올라온 데이터 중 지금 당장 필요한 명령어를 가져와서 해독한다.
- 해석된 명령에 따라 cpu가 계산을 한다
- 처리된 결과는 램에 다시 기록됨
저장 장치에서 앱 바이너리 로드
- 운영체제가 사용자의 앱 실행 요청을 감지
- 저장 스토리지에서 해당 앱의 바이너리 위치를 찾는다
- os는 램에 이 앱이 올라올 수 잇는 빈 공간을 확보한다.
- 저장장치에 있는 바이너리 데이터를 일정한 크기로 나누어 램으로 복사한다.
- 당장 실행에 필요한 부분만 먼저올림
- 앱이 사용하는 외부라이브러리, 다이나믹 라이브러리와 실제 메모리 주소를 연결한다.
- cpu의 프로그램 카운터가 램에 로드된 앱의 엔트리 포인터를 가리키며 한줄씩 실행된다.
- 따라서 앱 로딩이 너무 느리다면 바이너리에 포함된 라이브러리가 너무 많아서 로딩과 주소 연결이 오래걸리는것
RAM에 코드와 초기 데이터 적재
- cpu가 실행하는 코드는 메모리 주소 어디에 있는 값을 읽어와이지만 실제 램 주소가 아님
- os는 가상 주소와 실제 램의 주소를 짝지어놓은 페이지 테이블을 가지고 잇음
- 랜덤 액세스 메모리라서 파편화가 발생할 수 잇음
- iOS 시스템 라이브러리는 모든 앱이 공통으로 사용함 램의 특정 구역에 미리 올려두고 사용중임
- iOS는 메모리가 부족할 때 스왑하지 않고 그냥 램에서 밀어버림
RAM이 부족하면 iOS 시스템은 어떤 동작을 할까
- 먼저 시스템은 저장장치로부터 다시 읽어올 수 잇는 데이터부터 메모리에서 날려버림(실행파일, 이미지, 프레임워크등)
- 그래도 부족하면 dirty page(heap)을 압축함
- 그래도 부족하면 메모리를 가장 마니 쓰는 앱부터 강제 종료
- didReceiveMemoryWarning을 통해 많이 쓰는 힙을 비워주는 작업을 하면 조금이나마 줄일수잇음
CPU 속도, RAM 용량, 저장 장치 속도 중 어떤 것이 앱의 ‘체감 속도’에 가장 큰 영향을 미칠 수 있을까요? 이유와 함께 설명해주세요.
- 1위 저장장치 속도 : 일단 램으로 적재를 해야하는데 그 시간이 오래걸리면 체감이 오래걸림
- 2위 램 : 그렇게 체감이 될까?
- 3위 스크롤하거나 데이터를 처리할 때속도임
그럼 램이 늘어나면 스레드의 크기도 늘어남?
- 스레드의 크기는 메인 1메가 백그라운드 512kb로 고정 따라서 개수가 늘어남
iOS의 A-시리즈 칩셋에서 CPU와 GPU가 메모리를 공유하는 Unified Memory Architecture가 앱 개발에 미치는 영향은 무엇인가요?
- 일반 pc는 ram과 vram이 물리적으로 분리되어잇음 따라서 복사해야됨
- iOS는 동일한 메모리 공간을 바라봐서 복사할 필요가없음
- cpu가 놀고있을때 ram을 다른용도로 gpu가 놀고 잇을때 vram을 다른 용도로 못쓰지만 동적으로 효율적으로 나눠씀
- iOS에서는 가속이라는 개념이 있는데 예를들어 이미지 처리 가속같은거다 그게 가능한 이유다.
- 자원공유로 인한 대역폭 경쟁과 용량한계가 단점임 따라서 pc같이 고성능이고 무거운 작업을 하는 곳에서는 비효율적임
CG vs CA
- cg는 cpu가 램에 비트맵을 직접 계산해서 넣는 것 이 비트맵을 gpu가 같은 주소에서 읽어옴(벡터연산, 텍스트 렌더링에 강점)
- draw안에서 복잡한 cg는 하면안됨, 무거움
- ca는 cpu가 속성을 변경하면 gpu가 그것을 조합해서 비트맵을 만듬
- cpu부하가 매우적고 부드러움, offscreen rendering, shouldRasterize=true를 사용하여 잘 안변하는 레이어는 비트맵 캐싱
- 복잡한 이미지 합성이나 정교한 픽셀조작은 Core Graphics를 백그라운드에서 연산한다.
- 단순한 이동이나 투명도 변화같은 ui 인터랙션은 ca에 맡긴다.
CPU와 메모리 간의 데이터 교환은 어떻게 이루어지나요? 데이터 교환 속도를 높이기 위해 컴퓨터 시스템에는 어떤 장치들이 사용되나요? 버스(Bus)란 무엇이며, 어떤 종류가 있나요? CPU와 RAM 외에 버스를 통해 연결되는 다른 장치들은 무엇이 있을까요? 캐시 메모리의 개념과 역할에 대해 설명해주세요. 캐시 히트(Cache Hit)와 캐시 미스(Cache Miss)는 무엇이며, 성능에 어떤 영향을 미치나요? 캐시의 지역성(Locality) 원리에 대해 설명해주세요. 시간적 지역성과 공간적 지역성의 구체적인 예를 코드로 설명해주실 수 있나요? (예: 반복문, 배열 순회) 지역성 원리를 잘 활용하지 못하게 작성된 코드는 어떤 성능 문제를 일으킬 수 있을까요?
CPU 아키텍처의 종류(예: ARM, x86)와 각 특징
- x86은 덤프트럭 arm은 테슬라
iOS 기기는 주로 어떤 아키텍처
- ARM
- RISC
- 저발열과 ‘팬리스(Fanless)’ 설계
- Apple은 CPU뿐만 아니라 GPU(그래픽), NPU(인공지능 연산), RAM 등을 하나의 칩 안에 모두 집어넣는 SoC
iOS 시뮬레이터
- 그것을 실행하는 Mac의 아키텍처
- 실제 기기의 하드웨어를 흉내 내는 것이 아니라, iOS의 소프트웨어 인터페이스(API)만 흉내 냅니다. 하드웨어 자원은 Mac의 CPU와 RAM을 직접 끌어다 씀
- 실제 기기에서는 성능 부족으로 끊길 수 있습
- 아이폰 카메라 같은 하드웨어 접근 불가
- 라이브러리 호환성
시뮬레이터 vs 에뮬레이터
- 시뮬레이터는 하드웨어는 냅두고 소프트웨어만 흉내 ios
- 하드웨어까지 복제하는게 에뮬레이터 안드로이드
iOS 아키텍처 및 AP(Application Processor)
- AP의 정체성 (SoC): CPU + GPU + RAM + NPU(AI) + ISP(카메라)가 하나로 합쳐진 단일 칩 시스템.
- 하이브리드 구조: 고성능 코어(P)와 저전력 코어(E)를 상황에 따라 분배하여 배터리 수명 극대화.
- 뉴럴 엔진 (Neural Engine): FaceID, 사진 보정, 온디바이스 AI 연산 전담 회로.
- 최적화: 하드웨어(A 시리즈 칩)와 소프트웨어(iOS)를 동시에 설계하여 하드웨어 사양 대비 최고의 퍼포먼스 구현.
iOS 백그라운드 메모리 종료(Jetsam)
- Freezing(동결) 정책: 앱이 백그라운드로 가면 실행을 일시 중단하고 상태를 RAM에 유지함. 다른 앱을 위해 메모리가 필요해지면 이 동결된 앱부터 종료함.
- Jetsam 데몬 (Low Memory Killer): iOS의 메모리 관리 전담 프로세스. 가용 메모리가 부족해지면 메모리 점유율이 높은 앱부터 즉시 강제 종료(Kill)하여 시스템 안정성을 확보함.
- Memory Footprint: 앱이 차지하는 메모리 용량이 클수록 Jetsam의 1순위 타겟이 됨.
- 시스템 최적화: iOS는 현재 사용 중인 앱(Foreground)에 자원을 몰아주기 위해 백그라운드 자원을 공격적으로 회수함.
- didReceiveMemoryWarning: 시스템이 메모리 부족 신호를 보낼 때, 앱 내 캐시나 무거운 리소스를 스스로 해제하여 점유율을 낮춰야 생존 확률이 높음.
- 하드웨어 제약: 아이폰은 안드로이드 대비 상대적으로 RAM 용량이 적어, 고사양 앱 실행 시 백그라운드 앱이 종료되는 ‘앱 리프레시’ 현상이 더 빈번할 수 있음.
각 단계
- Normal / 제약 없음
- Warning / didReceiveMemoryWarning, 시스템 임시 캐시 정리
- Urgent / 메모리 압축, 우선순위 낮은 데이터 강제 삭제
- Critical / Jetsam에 의한 앱 강제 종료
gcd
queue
- GCD는 개발자가 스레드를 직접 관리하지 않고, 대기열(Queue)에 작업을 던지면 OS가 CPU 상태에 맞춰 일꾼(스레드)을 배정하는 시스템
- Serial (직렬): 한 번에 하나씩만 실행. (순서 보장, 데이터 보호용)
- Concurrent (병렬): 여러 작업을 동시에 실행. (속도 우선, 백그라운드 작업용)
- Sync (동기): 작업이 끝날 때까지 현재 스레드가 멈춰서 기다림.
- Async (비동기): 작업을 던져놓고 현재 스레드는 바로 다음 일을 함.
CPU의 ‘동시 처리’ 원리
- 동시성(Concurrency): 코어가 하나여도 아주 빠르게 파바바바바박 작업을 전환(Context Switching)해서 동시에 하는 것처럼 보이게 함.
- 병렬성(Parallelism): 멀티 코어를 활용해 실제로 여러 작업을 물리적으로 동시에 처리함.
- OS의 역할: 개발자가 큐에 작업을 넣으면, OS가 하드웨어 상황에 맞춰 병렬로 돌릴지, 동시성으로 돌릴지 알아서 판단함.
조합 (언제 무엇을 쓰나)
- Main → Global.async (비동기): * 용도: 네트워크 요청, 이미지 처리 등 무거운 작업.
- 효과: 메인 스레드가 멈추지 않아 화면이 부드럽게 유지됨.
- Global → Main.async (비동기): * 용도: 백그라운드 작업 완료 후 결과를 화면에 표시.
- 효과: UI 업데이트는 반드시 메인 스레드에서 해야 한다는 원칙 준수.
- Custom Serial.async (비동기): * 용도: DB 저장, 로그 기록.
- 효과: 여러 곳에서 동시에 접근해도 데이터가 꼬이지 않게 보호.
- Main.sync (동기): * 경고: 절대 금지. 서로 기다리다 앱이 멈추는 데드락(Deadlock) 발생.
시스템이 주는 DispatchQueue.global()도 병렬인데, 왜 굳이 내 걸 따로 만들까
- 그룹화 및 식별: label을 붙여두면 나중에 앱이 멈추거나 디버깅할 때 어떤 작업 뭉치에서 문제가 생겼는지 찾기 훨씬 쉬움
- 우선순위 제어: 내가 만든 큐에 특정 QoS(Quality of Service)를 설정해서, 시스템 전체의 병렬 작업들 중 내 작업의 중요도를 세밀하게 조절할 수 있습니다.
- Barrier(장벽) 활용: 병렬 큐의 가장 강력한 기능 중 하나인 Barrier를 쓰기 위해서입니다.
- 병렬 큐는 다 좋은데, 여러 명의 일꾼이 동시에 데이터를 수정하면 데이터가 깨질 위험이 있습니다. - 이때 Barrier를 사용하면 “평소에는 병렬로 일하다가, 특정 작업 시에만 잠깐 직렬(Serial)처럼 동작”하게 만들 수 있습니다.
- 읽기(Read): 여러 명이 동시에 읽어도 안전하므로 Concurrent하게 처리.
- 쓰기(Write): 쓸 때는 아무도 읽거나 쓰면 안 되므로 Barrier를 쳐서 나 혼자만 작업.
- 글로벌 큐는 전역 공유 자원입니다.
- 만약 내가 글로벌 큐에 배리어(장벽)를 쳐버리면, 내 앱뿐만 아니라 시스템의 다른 기능들이나 다른 라이브러리들이 글로벌 큐에 던져놓은 작업들까지 전부 멈춰버려야 합니다.
DispatchGroup
- 여러 비동기 작업(예: API 3개 호출)이 모두 끝나는 시점을 알고 싶을 때 사용 (group.notify).
- 그룹 생성: let group = DispatchGroup()
- 입장: 작업 시작 전 group.enter()
- 퇴장: 작업 종료 후 group.leave()
- 알림: 모두 끝났을 때 group.notify(queue: .main) { … }
- API 3개가 각각 언제 끝날지 몰라도(병렬), 가장 마지막 작업이 끝나는 순간을 정확히 잡아내어 UI를 업데이트할 수 있습니다.
DispatchWorkItem
- 작업을 변수에 담아두었다가 원할 때 실행하거나, 실행 전 취소(cancel)하고 싶을 때 사용.
QoS (Quality of Service): 글로벌 큐의 우선순위를 정함.
- userInteractive: 즉각 반응 (가장 높음)
- userInitiated: 사용자가 시킨 일 (빠름)
- utility: 데이터 다운로드 등 (보통)
- background: 사용자 모르게 하는 일 (낮음)
Semaphore
- 공유 자원에 접근할 수 있는 스레드 개수를 제한하고 싶을 때 사용.
- wait(): 차단기를 내립니다. (카운트 -1)
- 카운트가 0보다 크면 통과, 카운트가 0이면 자리가 날 때까지(다른 스레드가 signal을 보낼 때까지) 대기.
- signal(): 차단기를 올립니다. (카운트 +1)
- 작업이 끝났으니 다음 사람 들어오라고 신호를 보냅니다.
- 동시에 처리할 수 있는 작업의 개수를 제한해야 할 때 씁니다.
- 서버 부하를 막기 위해 이미지 다운로드는 동시에 최대 3개까지만 해!
- 비동기 작업을 동기 작업처럼 기다리기
- 작업 시작 전 wait(), 작업 완료 블록 안에서 signal()을 호출하면, 비동기 작업이 끝날 때까지 현재 코드가 멈춰 있게 됩니다.
iOS의 런루프(RunLoop)와 스레드의 관계
- 런루프의 정체: “무한루프 일꾼”
- 스레드 안에 상주하며 “할 일(이벤트)이 올 때까지 기다렸다가, 오면 처리해주는 엔진”입니다.
- 구조: 객체이기도 하지만 본질은 거대한 while(true) 함수입니다.
- 주요 임무: 마우스/키보드 이벤트 감지, 타이머 관리, 그리고 가장 중요한 UI 렌더링(메인스레드에서만)을 담당합니다.
- 메인 스레드: UI 전담 일꾼. 반드시 런루프가 돌아야 앱이 응답합니다.
- 백그라운드 스레드: 특정 작업만 하고 사라지는 일꾼. 런루프 없이 ‘직진’만 합니다.
- 백그라운드가 일을 끝내고 메인 스레드의 큐(Queue)에 포스트잇(작업)을 붙여두면, 메인 런루프가 다음 바퀴에 그걸 발견해서 처리합니다.
ARC
- 런타임에 실시간으로 감시하는 엔진이 아니라, 컴파일 시점에 동작합니다.
- 코드 분석: 컴파일러가 코드를 훑으며 객체가 생성되는 시점과 더 이상 사용되지 않는 지점을 분석합니다.
- 코드 삽입: 사람이 수동으로 넣어야 했던 retain(카운트 +1)과 release(카운트 -1) 코드를 적절한 위치에 자동으로 끼워 넣습니다.
- 즉각 해제: 앱이 실행될 때, 이 삽입된 코드들에 의해 참조 카운트가 0이 되는 순간, 메모리에서 해당 객체가 즉시 소멸합니다.
- 순환 참조(Retain Cycle)에 매우 취약함
- 실시간이다보니 런타임 오버헤드가 꾸준히 발생함
- 해제될 객체를 강제로 백그라운드 큐로 보내버리는 것(변수 캡쳐)
캡쳐의 원리
- 컴파일러가 dispatch_async 블록 내부에 사용된 외부 변수를 발견하면, 그 변수를 블록이 소유하도록 retain 코드를 자동으로 삽입
- dispatch_async를 호출할 때 전달하는 ^{ … } 블록은 단순한 코드 덩어리가 아니라, 하나의 객체처럼 취급
- 발견: 컴파일러가 블록 내부에서 temp라는 외부 지역 변수를 쓰고 있는 것을 확인합니다.
- 복사 및 참조: 블록이 생성될 때, 블록 객체 내부의 멤버 변수로 temp를 복사해둡니다.
- 강한 참조 (Strong Reference): 이때 ARC는 이 블록이 temp를 안전하게 쓸 수 있도록 retain 신호를 보냅니다. 즉, 참조 카운트가 1 올라갑니다.
셀프캡쳐링
- 스위프트에서 클래스의 인스턴스가 클로저를 속성(Property)으로 가지고 있고, 그 클로저 내부에서 self를 참조하면 다음과 같은 상황이 벌어집니다.
- 객체 → 클로저: 객체가 클로저를 메모리에 들고 있습니다. (강한 참조)
- 클로저 → 객체: 클로저가 실행될 때 self를 사용해야 하므로, self를 캡처합니다. 이때 기본값은 강한 참조입니다.
- 이 상태가 되면 두 객체는 서로를 붙잡고 절대 놔주지 않습니다
순환참조
- 순환 참조는 나와 상대방이 서로를 ‘강하게 소유’할 때만 발생합니다.
- 안전한 경우: * DispatchQueue나 일회성 함수 블록 (상대가 나를 소유하지 않음).
- 구조체(Struct)에 클로저를 넘길 때 (구조체는 참조 타입이 아니어서 나를 소유할 수 없음).
- 위험한 경우: 내가 소유한 클래스(Manager, Delegate 등)의 변수에 클로저를 담고, 그 클로저가 다시 나(self)를 캡처할 때.
클로저
- 클로저(Closure)의 정체: “래핑된 객체”
- 클로저는 단순히 코드 덩어리가 아니라 함수 포인터 + 컨텍스트(캡처된 데이터 바구니)를 가진 힙 영역의 객체입니다.
- 동작: 클로저를 변수에 담으면 힙에 ‘컨텍스트 바구니’가 생기고, 캡처된 변수(self 등)의 RC를 올림.
- 해제: 클로저 자체가 release되어 소멸될 때, 비로소 컨텍스트 바구니 안의 변수들도 release됨.
- 최적화: 탈출하지 않는(non-escaping) 클로저는 힙이 아닌 스택에 할당되어 RC 연산 없이 빠르게 동작함.
클래스 안 구조체 안 클래스
- 중첩 구조의 메모리 법칙: “본질을 따라감”
- Class in Struct: 구조체는 값 복사되지만, 내부 클래스는 주소값만 복사되므로 ARC가 RC를 관리함.
- Struct in Class: 구조체는 클래스 몸체의 일부가 되어 힙에 통째로 거주. 클래스가 죽을 때 같이 소멸함 (RC 관리 없음).
weak vs unowned vs __block
- weak: 객체 해제 시 자동으로 nil이 됨. 사이드 테이블(Side Table)을 통해 유령 상태를 관리하므로 안전하지만 미세하게 느림.
- unowned: nil로 안 바뀜. 성능은 빠르나 객체 소멸 후 접근 시 크래시. 생명주기가 완벽히 종속적일 때만 사용.
- __block (Obj-C): 변수를 힙으로 옮겨 블록과 공유하게 만듦. Swift는 모든 클로저 캡처가 기본적으로 이 방식임.
샌드박스
- 앱이 허용된 구역(디렉토리) 밖의 데이터에 접근하거나 시스템 설정을 건드리지 못하게 격리하는 보안 메커니즘
- Documents: 유저가 생성한 콘텐츠나 설정 파일 저장 (iCloud 백업 대상).
- Library: 앱 상태 관리 파일, 캐시(Caches), 설정값(Preferences) 저장.
- tmp: 임시 파일 저장 (앱이 실행되지 않을 때 시스템이 삭제할 수 있음).
앱 간 데이터 공유 방법
- App Groups (가장 일반적인 방법) 같은 개발자가 만든 여러 앱(예: 본체 앱과 위젯, 혹은 시리즈 앱들) 사이에서 데이터를 공유
- Deep Link : 특정 앱을 실행시키면서 짧은 텍스트(데이터)를 전달할 때 사용합니다. 예시: kakaolink://… 또는 fb://…
- 이름이 같으면 충돌(가로채기)이 발생합니다.
- 똑같은 URL Scheme을 가진 앱이 여러 개 있을 때, 어떤 앱을 실행할지 명확한 규칙이 없음
- UIActivityViewController (공유 시트)
- Keychain Sharing / 로그인 정보나 인증 토큰 같은 민감한 정보를 공유할 때 사용합니다.
- Universal Links /웹 주소(HTTPS)를 통해 앱을 실행하고 데이터를 넘기는 현대적인 방식입니다. URL Scheme보다 훨씬 안전
ㅌ
Deep Link
- URL 스킴 하이재킹 (Hijacking) 방지
- URL 스킴을 통해 들어오는 파라미터는 외부에서 유입되는 신뢰할 수 없는 데이터
- 민감한 데이터 노출 금지
- 앱이 백그라운드에 있다가 URL 스킴으로 깨어날 때, 현재 앱의 상태가 해당 요청을 처리하기에 적절한지 확인
- 로그인도 안 된 상태인데 todaypic://my-profile 요청을 받고 마이페이지로 이동
REST API
- Representational State Transfer http를 최대한 활용하기 위한 아키텍처 스타일
- 클라이언트와 서버의 역할을 명확히 분리
- 서버는 클라이언트의 상태를 저장하지않음. 요청에 모든 정보가 잇어야됨
- 캐싱
- 리소스는 uri로 식별되고 http method를 통해 조작되며 메시지는 스스로를 설명해야됨
http 상태코드
1**
- 정보제공: 요청을 받았고 작업을 계속 하겠다
2**
- 성공
- 200 : ok
- 201 : created
3**
- redirection: 요청완료를 위해 추가 동작이 필요
- 301 : Moved permanently
- 304 : not modified
4xx
- error: 요청에 잘못된 문법이 있거나 요청을 수행할 수 없음
- 400: bad request
- 401: unauthorized
- 404: not found
5xx
- server error: 서버가 유효한 요청을 수행하는데 실패
- 500: internal server error
- 503: service unavailable
4xx vs 5xx 차이
- 가장 큰차이는 누구의 잘못인가와 재시도 성공가능성이 있는가
- 4**는 클라이언트 책임
- 따라서 똑같은 요청을 그대로 보내도 결과는 변하지 않음
- 사용자 피드백: 아이디 비번 다시, 접근권한없음같이 안내
- 400은 요청 로직을 수정
- 401은 토큰 재발급 혹은 로그아웃 처리
- 5**은 서버책임
- 요청은 문제없으나 서버에 버그가 잇거나 과부하상태
- 재시도
- 잠시후에 시도해주세요
서버로부터 받은 JSON 데이터를 Swift 객체로 변환하는 과정
- 디코딩이라고 함
- codable 프로토콜을 사용하는것이 표준, encodable, decodable을 합친것
- 기본적으로 json키와 swift모델의 변수 이름이 같아야됨
- JSONDecoder를 사용해 변환함
디코딩 실패
- JSON의 키 값이 Swift 관례(CamelCase)와 다를때
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- CodingKeys 프로토콜을 이용해서 열거형으로 매핑 시켜준다.
- 키값이 없을때
- 타입이 다를때
- 필수값인데 null이 있을때
이미지
- png
- 무손실압축
- 투명도 지원
- 원본과 동일
- 용량이 큼
- 주변 픽셀 값을 보고 현재 픽셀값을 예측함, 실제 값대신 예측값과 실제값의 차이를 기록
- 자주 나오는 데이터에는 짧은 이름표 드물게나오는거는 긴이름표 그걸로 대치
- jpg
- 손실압축
- 투명도 없음
- 많이 작아짐
- 이미지를 rgb로 하는게 아니라 밝기와 색상차이로 나누고 색상차이 정보를 많이 버림
- 8*8 블록으로 나누고 미세한 디테일이나 노이즈는 0으로 버림
메모리 정렬
- cpu는 데이터를 읽을 때 4 or 8바이트 단위로 읽음
- 데이터 값이 8의 배수면 한번의 메모리 접근으로 끝남
- 여러 바이트에 걸쳐잇다면 두번 읽어서 합치고 자르는 연산 필요
- 크기가 큰 타입 순서대로 선언해야됨 그리고 바이트 배치도 8바이트 단위로 해야됨
앱 메모리 최적화
- 이미지 최적화
- 이미지 메모리 크기는 파일용량이 아니라 픽셀 가로 * 세로 * 4바이트
- 다운샘플링
- 데이터 캐싱
- 딕셔너리 대신 NSChace를 사용
- 자주쓰지 않는 데이터는 디스크에 저장
- 참조관리
- 강한 순환 참조
- 타이머와 같은 대용량 리소스를 명시적으로 삭제
- lazy 로딩
싱글톤
- 자원공유가 쉬움
- 데이터 추적 어려움
- 테스트 어려움
- 클래스의 생성자로 의존성을 명시해야하는데 스을쩍 갖다쓰면 모름
- 멀티스레드에서 race condition 발생 가능
- 앱 전체에서 하나의 상태를 공유해야하고 무거울때
부모 자식 상속관계의 클래스에서 생성자 소멸자 호출
- 기초부터 만들고 그 위에 자식을 만든다. (스택구조)
- 부모 클래스 생성자 호출됨
- 자식 클래스 생성자 호출됨
- 자식 클래스 소멸자 호출됨
- 부모 클래스 소멸자 호출됨
- 원칙은 이런데
- swift
- 호출 시작은 자식 클래스 생성자부터
- 근데 부모를 명시적으로 호출해야됨
- 부모 생성 전에 self 사용금지함
- cpp
- 부모 생성부터 무조건
JSON
- JavaScript Object Notation
- 거의 모든 곳의 표준
- 중괄호 안에 키밸류 형태로 저장하는것
- 배열 값이면 대괄호 안에 나열
- xml은 태그가 잇어 텍스트가 무거움
- 딕셔너리와 유사하여 파싱 로직 매우 간단
- JSON은 사람과 컴퓨터 모두 읽기 편한 “텍스트 데이터 양식”
- API에서 쓰는 이유는 “가볍고, 빠르고, 어떤 언어와도 잘 맞기 때문”
브라우저 입력
- 주소창에 입력
- 어떤 IP 주소인지 모름 DNS 서버에 물어봄
- 네이버 서버에게 줄 HTTP 요청 메시지 생성
- 보안을 위해 SSL 암호화
- 논리적으로 준비 소켓 객체생성하고 포트 번호할당
- 3way 시작
- 서버야 보내도되냐
- ㅇㅋㅇㅋ
- ㅇㅋ보낸당
- https니깐 목적지 포트를 443으로 지정
- L7에서 만든 데이터를 세그먼트로 쪼갬
- 세그먼트에 출발지 ip와 도착지 ip를 붙여 패킷을 만듬
- 게이트웨이(공유기)로 가기위해 물리 주소인 MAC을 붙여 프레임만듬
- 모든 데이터를 비트로 전환해서 보냄
클린아키텍쳐
- 관심사의 분리와 의존성 규칙
- 코드가 어디를 바라보고있는가
- 비즈니스로직을 외부 환경(ui나 다른 프레임워크, 네트워크)에 의존하지 않게 하는것
- 의존성은 항상 안쪽(도메인)을 향해야 합니다.
- 비즈니스로직은 데이터를 가져와라는 명령만할뿐 어떻게 가져오는지는 몰라야됨 알라모로 가져오든 urlsession으로 가져오든
- 도메인은 엔티티와 유즈케이스로 구현함 비즈니스로직을 재사용하고 테스트에 용이하게 만듬
- Presentation Layer 뷰
- Domain 유즈케이스
- Data 실제 구현
- 에러처리는 모든 곳 Data에서는 기술적인 팩트만 래핑
- 도메인에서는 그 에러에 대한 대응을 매핑
- 뷰는 그 결과를 뿌려줌
스위프트 Int / cpp int
- Swift 현재 앱이 실행되고 있는 기기의 아키텍쳐 (8바이트, 64비트)
- C++은 숫자로 못박아두지 않음 그러나 4바이트로 구현
- CPU 레지스터 크기를 따라가지만, 하위호환성 때문임
- CPU 내부에서 레지스터의 크기랑 램과의 데이터 버스의 폭
Q1. Objective-C는 런타임이 동적이고, Swift는 정적 타이핑 언어입니다. Swift의 Struct(값 타입)나 Enum을 Objective-C 쪽에서 사용해야 할 때는 어떻게 처리했나요?
- 스위프트의 Enum의 원시값을 Int형으로
Q2. nullability 처리는 어떻게 하셨나요? Objective-C의 nil과 Swift의 Optional 차이로 인해 런타임 크래시가 발생한 경험은 없었나요?
의도: Objective-C 헤더에 NS_ASSUME_NONNULL_BEGIN 등의 매크로 처리를 꼼꼼히 했는지, 강제 언래핑(!)을 남발하지 않았는지 확인합니다.
- 비동기 (GCD, Swift Concurrency, Combine) - “동시성 제어” 이력서에 비동기 기술이 3가지(GCD, Combine, Concurrency)나 적혀 있습니다. 이는 “왜 굳이 섞어 썼는가?”에 대한 공격 포인트가 될 수 있습니다.
Q3. GCD, Combine, Swift Concurrency가 모두 있습니다. 프로젝트 내에서 각각의 사용 기준(Rule)이 있었나요?
예시 답변 전략: “레거시 로직은 GCD, 이벤트 스트림 처리는 Combine, 신규 비동기 로직은 가독성을 위해 Concurrency를 썼습니다”와 같은 명확한 기준이 필요합니다.
Q4. 기존의 GCD 기반 completionHandler 패턴을 Swift Concurrency(async/await)로 전환할 때 UnsafeContinuation과 CheckedContinuation 중 무엇을 사용했고, 그 이유는 무엇인가요?
의도: 마이그레이션의 구체적인 구현 방법을 아는지 묻는 킬러 질문입니다. (보통 안전성을 위해 Checked를 씁니다.)
Q5. Combine은 주로 어디에 사용하셨나요? 단순히 비동기 처리용이었나요, 아니면 MVVM의 데이터 바인딩 용도였나요?
의도: Combine을 RxSwift처럼 반응형 프로그래밍(Reactive Programming)을 위해 썼는지, 단순 API 호출용으로 썼는지 활용 범위를 확인합니다.
- UI & 프레임워크 (UIKit, SwiftUI, WebKit) - “하이브리드 뷰” UIKit과 SwiftUI, 그리고 웹뷰(WebKit)가 섞여 있다면 데이터 흐름과 라이프사이클이 질문의 핵심입니다.
Q6. 기존 AutoLayout(UIKit) 기반 화면에 SwiftUI 뷰를 붙일 때 UIHostingController를 사용했을 텐데, 이때 데이터 동기화(State 관리)는 어떻게 하셨나요?
의도: UIKit의 데이터 변경이 SwiftUI 뷰에 즉시 반영되도록 ObservableObject 등을 잘 연결했는지 묻습니다.
Q7. WebKit을 쓰셨는데, Javascript와 네이티브(Swift) 간의 통신(Bridge)은 어떻게 구현했나요? WKScriptMessageHandler를 사용할 때 메모리 누수(Retain Cycle) 이슈는 없었나요?
의도: 웹뷰 개발의 고질적인 문제인 메모리 릭(Leak)을 해결할 줄 아는지 묻습니다. (ScriptMessageHandler가 webView를 강하게 참조하는 문제 등)
- 아키텍처 (MVVM) - “구조적 분리” 전환 프로젝트에서 MVVM은 코드를 분리하는 핵심 역할을 합니다.
Q8. Objective-C에서 Swift로 전환하면서 MVC에서 MVVM으로 구조를 변경하셨다고 했습니다. ViewModel의 Input/Output 정의는 어떤 방식을 사용하셨나요? (예: Combine, Closure, Delegate)
의도: MVVM의 핵심인 ‘바인딩’을 기술 스택에 있는 Combine으로 처리했는지 확인하려 할 것입니다.
Q9. URLSession 네트워크 레이어는 ViewModel에 포함되어 있나요, 아니면 별도의 Service/Repository 패턴으로 분리되어 있나요?
의도: MVVM을 쓰면서 비즈니스 로직과 네트워크 로직을 제대로 분리했는지 설계 역량을 봅니다.
💡 면접관이 가장 궁금해할 “한 방” 질문 “Obj-C 코드를 Swift로 100% 전환했다고 하셨는데, 그냥 놔두고 Swift랑 섞어 써도(Bridging) 되잖아요? 굳이 리소스를 들여서 100% 전환을 감행한 기술적/비즈니스적 이유는 무엇이었나요?”
이 질문은 기술 스택 전체를 관통합니다. 단순히 “Swift가 최신이라서요”라고 답하면 감점입니다.
컴파일 타임 안전성 확보 (Obj-C의 런타임 오류 방지)
유지보수 비용 감소 (컨텍스트 스위칭 비용 절감)
인재 채용의 용이성 (요즘 개발자들은 Obj-C 기피) 등의 논리로 방어하셔야 합니다.