ARCHIVES / ALL
72URLSession 취소를 실패 알림으로 보여주지 않기
Swift concurrency에서 URLSession 요청을 취소했는데 에러 토스트가 뜨는 문제를 CancellationError와 URLError.cancelled 기준으로 정리한다.
NavigationStack path가 커질 때 Route enum으로 버티기
SwiftUI NavigationStack에서 여러 모델 타입을 path에 직접 넣다가 화면 전환이 흩어진 경험과 Route enum으로 목적지를 모으는 기준을 정리한다.
SwiftUI @State 초기값이 바인딩을 따라 바뀌지 않을 때
SwiftUI View init에서 @State를 Binding 값으로 초기화했다가 부모 값 변경을 놓친 경험과 draft state, onChange, id 재생성 기준을 정리한다.
SwiftUI searchable 붙이고 검색 상태가 지저분해졌을 때
SwiftUI searchable에서 입력 중인 searchText, 실제 API query, 검색 결과 상태를 한 값에 묶었다가 꼬인 경험과 상태를 나눠 정리한 패턴.
UICollectionViewCompositionalLayout 가로 스크롤 섹션에서 삐끗한 것들
UICollectionViewCompositionalLayout에서 orthogonalScrollingBehavior로 가로 스크롤 섹션을 만들 때 paging, group width, estimated size, section inset에서 자주 헷갈린 지점을 정리한다.
iOS 17에서만 죽는 SwiftData 크래시 — unmanaged @Model의 함정
ModelContext 밖에서 init한 @Model의 합성 getter가 iOS 17 런타임에서 SIGABRT — 운반은 순수 struct로 바꿨다.
SwiftUI TextField 숫자 입력이 자꾸 되돌아갈 때
SwiftUI TextField에 Int를 바로 묶었다가 빈 입력과 검증 타이밍에서 막혔다면 String 입력 상태를 따로 두는 방법이 편하다.
@Bindable vs @Binding, 둘이 뭐가 다른지 한 번 정리
iOS 17에서 @Bindable이 추가되면서 @Binding이랑 헷갈리기 시작했다. 언제 뭘 써야 하는지 한 번에 정리.
SwiftUI #Preview가 자꾸 죽을 때 점검할 패턴들
Environment·SwiftData 컨테이너만 챙겨도 Preview crash 대부분이 사라진다.
SwiftUI .task vs .onAppear, 어느 쪽이 안전한가
화면이 사라질 때 비동기 작업이 따라서 취소되느냐의 문제. .task와 .onAppear { Task { } }가 같아 보여도 동작이 갈리는 지점을 정리한다.
@FocusState로 키보드 다루기 — 자주 헷갈리는 자리들
TextField 포커스 이동·키보드 dismiss·초기 포커스 — 매번 갸우뚱하는 자리를 한 번에 정리.
Swift ClosedRange<Date> 한 줄 때문에 한 달간 헤맨 이야기
Swift ClosedRange<Date>의 precondition trap을 따라가다 보니, 진짜 범인은 캘린더 데이터 매핑에서 가드 변수를 안 쓴 한 줄이었다. 데이터·표현 두 레이어로 막은 회고.
Coordinator 패턴에서 부모 없는 외부 진입이 만드는 delegate nil 함정
푸시나 딥링크로 상세 화면을 직접 열 때 부모 Coordinator가 없어 delegate nil로 종료 흐름이 멈춘 문제와 fallback 처리 기록.
실기기에서만 깨진 콜드스타트 푸시 race condition 디버깅 기록
시뮬레이터에서는 멀쩡했지만 실기기 콜드스타트 푸시에서만 상세 화면이 열리지 않던 문제를 네 단계로 추적한 기록.
SwiftUI List에서 onAppear로 페이지네이션 짜다가...
SwiftUI List row의 onAppear에서 곧바로 페이지네이션 API를 호출했다가 동시·중복 호출이 폭주했고 마지막 row 판별 + isLoading guard + cursor 세 개로 정리한 기록
NavigationStack에서 화면이 두 번 쌓이는 세 가지 케이스
SwiftUI NavigationStack에서 같은 화면이 두 번씩 쌓이는 세 가지 케이스와 어떻게 정리했는지 기록
SwiftUI 모달 세 개째부터는 Bool 말고 enum 라우팅으로
SwiftUI에서 modal 여러 개를 각각 @State Bool로 관리하다가 깨진 경험과 Identifiable enum 한 개로 묶어 푼 패턴을 정리한 글
SwiftUI onChange 안에서 state 다시 set하면 무한 루프 돈다
SwiftUI onChange 안에서 감시 중인 state를 다시 set하면 어떻게 무한 호출이 도는지 어떤 식으로 끊었는지 정리한 글
UIKit 모달 여러 개가 동시에 present 될 때 큐로 줄 세운 기록
UIKit에서 여러 publisher가 동시에 modal present를 트리거할 때 생기는 충돌을 priority queue, RunLoop 디바운스, dismiss completion으로 직렬화한 사례.
Coordinator re-attach 때 생기는 deinit race window 막기
UIKit Coordinator를 재부착하는 순간 old coordinator deinit과 외부 push/deeplink 이벤트가 겹치며 생기는 race window를 막는 방법.
iOS 푸시 알림 중복 진입을 막는 2층 dedupe 패턴
같은 iOS 푸시가 notification tap, cold launch, pending route 경로로 중복 처리되는 문제를 fingerprint dedupe와 envelope id 소비 추적으로 막은 사례.
RxSwift 코드베이스에서 async/await로 점진 마이그레이션하는 방법
RxSwift 의존이 큰 iOS 프로젝트에서 신규 코드부터 Swift Concurrency로 옮기는 단계별 전략. Repository async 미러, UseCase 공존, 호출자 화면 단위 전환까지.
iOS Coordinator에서 푸시 진입을 단일 라우터로 정리한 기록
UIKit MVVM Coordinator 앱에서 list ViewModel이 푸시를 직접 구독하던 구조를 AppNavigationRouter와 async/await 기반 단일 진입점으로 정리한 사례.
@MainActor를 클래스에 통째로 붙이면 생기는 fetch 함정
UIKit 때문에 클래스 전체에 @MainActor를 붙였다가 async fetch까지 메인 액터에 묶인 사례. Swift Concurrency에서 UI 빌드와 데이터 fetch 격리를 나누는 방법.
Swift Concurrency 프로젝트에서 GCD가 여전히 답인 자리들
Concurrency로 통일 중이지만 GCD를 일부러 남긴 자리들 — 폴링, fire-and-forget, RW lock, 그리고 sync 콜백
MCP 서버 비개발자 동료에게 배포하다 Windows에서 6번 죽었다
macOS에선 한 줄로 끝나는데 Windows만 함정 6개. Claude Desktop MCP 서버 배포 삽질 기록.
UIButton.Configuration 쓸 땐 layer.cornerRadius 건드리지 말자
layer.cornerRadius 줬는데 안 먹는다면 Configuration이 범인이다
xcrun simctl launch에 환경변수 안 먹던 이야기
simctl launch 뒤에 KEY=VAL 적어줬는데 ProcessInfo.environment가 계속 nil이었던 이유, 그리고 SIMCTL_CHILD_ prefix.
@Observable 마이그레이션, 모르고 가면 발 헛디딘다 — 함정 5가지
ObservableObject에서 @Observable로 갈아탈 때 드롭인 교체인 줄 알고 갔다가 view가 안 그려지거나 너무 자주 그려지는, 헛디디기 좋은 다섯 지점 정리.
iOS 26 SwiftUI 네이티브 WebView, 드디어 한 줄이다
WKWebView를 UIViewRepresentable로 감싸던 시절은 안녕. iOS 26 네이티브 WebView / WebPage를 짧게 정리해본다.
Foundation Models, Swift 3줄로 온디바이스 LLM 호출하기
iOS 26 Foundation Models 프레임워크 덕분에 OpenAI API 키도, 비용 청구서도 없이 Swift 3줄로 온디바이스 LLM을 호출할 수 있다. 첫인상을 정리해본다.
@Entry 매크로로 EnvironmentValues 보일러플레이트 날려버리기
EnvironmentValues 하나 추가하려고 struct + getter/setter 쓰던 거, @Entry 한 줄로 끝낸다. iOS 13까지 역호환.
iOS 26 .glassEffect() 실전 가이드 — content엔 절대 바르지 말 것
iOS 26 Liquid Glass는 한 줄짜리 modifier지만, 핵심은 '어디에 바르냐'다. .glassEffect() 사용법과 절대 바르면 안 되는 곳까지 한 번에 정리한다.
Firebase RemoteConfig 빼고 GitHub Actions + 정적 JSON으로 갈아탄 후기
단순 broadcast 용도로만 쓰던 Firebase RemoteConfig를 GitHub Actions + 정적 서버 JSON 구조로 갈아탔다. 트레이드오프, 새 구조, 마이그레이션 시 캐시 stale 이슈까지.
SwiftUI에서 PreferenceKey가 안 먹어서 onGeometryChange로 갈아탄 디버깅
SwiftUI ScrollView에서 스크롤 위치에 따라 네비게이션 타이틀을 점진적으로 노출하려는데 PreferenceKey/GeometryReader 측정값이 갱신되지 않았다. onGeometryChange로 갈아탄 디버깅 회고.
SwiftUI에서 NantesLabel 살리기
SwiftUI Text로는 안 되는 자동 링크 탐지, 줄 수 제한, 더 보기 토글을 UIKit NantesLabel을 UIViewRepresentable로 래핑해서 해결한 패턴과 네 가지 함정 정리.
`.ignoresSafeArea(.bottom)` 한 줄에 액션 버튼이 통째로 사라졌다
한 줄짜리 modifier가 자식 전부에 적용되면서 overlay 액션 버튼까지 home indicator 밑으로 끌어내린 사례.
Bool 한 개에 결정 세 개 묶었을 때
공용 컴포넌트의 옵션 한 개가 사실은 결정 세 개였다. 정책이 갈라지는 순간 쪼개야 한다.
Coordinator 시그니처 안 깨고 UIKit MVVM+C → SwiftUI MVI 갈아끼우기
상세 화면을 UIKit MVVM+C에서 SwiftUI MVI(Store)로 갈아엎으면서 Coordinator 외부 시그니처는 한 글자도 안 바꾼 이유와 방법 정리.
`final class` 상속 막혔을 때 컴포지션 써본 후기
final 막힌 클래스를 만났을 때, 격언만 외우다 놓친 진짜 답.
Swift 프로토콜에 옵셔널 property 넣을 뻔한 이야기
옵셔널 요구사항을 만들고 싶어지면 멈춰라. 십중팔구 프로토콜이 두 개여야 한다.
SwiftUI 펼침/접힘 애니메이션에 대한 나의 생각
UIHostingController 너머의 SwiftUI 애니메이션, 만져봐야 결국 step-jump가 이긴다.
공용 SwiftUI 컴포넌트의 마진, 누구 책임이지?
padding 두 번 먹이면 안쪽으로 두 배 들어간다. 공용 컴포넌트 마진 정책을 정하자.
서버 스펙 안 나왔을 때 클라 먼저 작업하는 법
백엔드 기다리느라 일정 밀리는 게 싫어서 만든 나름의 체크리스트. 나중에 뜯어고칠 비용을 0에 가깝게.
GCD 시리얼 큐로 로그 라이터를 재작성한 이유 — actor + AsyncStream을 버린 결정
actor는 mutual exclusion을 보장하지만 thread identity는 보장하지 않는다. Realm/SwiftData 같은 thread-confined 자원에는 GCD serial queue가 답인 이유.
47개 @Model 중 4개만 보존하기 — default-safe 정책 설계
47개 @Model 중 4개만 보존하고 43개는 삭제하는 정책을 설계하면서 만난 네 가지 후보. 누락이 안전을 깨지 않는 design 원칙: default = 삭제, 정책 = 한 곳, 적용 = 자동화.
SwiftData #Predicate에서 enum 비교가 0건을 반환하는 이유
SwiftData #Predicate가 사용자 정의 enum을 만나면 throw 없이 0건을 반환한다. SQL 번역 실패가 침묵으로 드러나는 함정. raw value 컬럼, Swift filter, PK 인코딩 세 가지 우회법.
@Model 스캐폴드 id + .unique 제약 = 조용한 데이터 손실
SwiftData에서 sentinel default id(.invalidInt = -1)와 @Attribute(.unique)와 try?가 만나면 100건이 1건으로 줄어든다. 에러 없이. 다층 방어로 막는 법.
Swift String.hashValue로 DB PK를 만들면 안 되는 이유
Swift의 String.hashValue는 per-process random seed라서 앱 재시작마다 값이 바뀐다. DB PK로 박으면 lookup이 영구 실패한다. 결정적 hash로 가야 하는 이유와 마이그레이션 처리.
Swift 6에서 매일 만나는 변화 7가지 — strict concurrency부터 typed throws까지
Swift 6에서 매일 실무 코드를 만질 때 부딪히는 변경 7가지 정리. strict concurrency 모드, Sendable 강제, typed throws, existential any 강화, migration mode 등.
AI가 짠 코드 PR 리뷰에서 AI한테 회신 떠넘기기
리뷰는 코드보다 코드의 의도에 대한 대화다. AI에게 회신까지 맡기면 리뷰가 사람-AI 대화가 되어 의도가 사라진다. PR 리뷰에서 AI를 어떻게 써야 할지 정리한 회고.
Swift mutating인데 self를 안 바꾸는 함수 — 컴파일 통과하는 시그니처의 함정
mutating 키워드는 self 변경 의도를 표현하는 계약이다. 컴파일러는 그 의미를 검증하지 않아서, self를 안 바꾸는 함수에도 mutating이 붙어 통과한다. PR 리뷰에서 만난 실제 사례와 builder 패턴 대안.
isPush 플래그가 만드는 Coordinator 흐름 분기 지옥
푸시 진입과 일반 진입을 isPush 플래그로 가르는 순간, dismiss 경로가 두 갈래로 갈리고 cascade finish가 의도와 정반대로 동작한다. 분기 자체가 정책과 어긋난 사례.
UIKit ↔ SwiftUI 브릿징 — UIViewRepresentable vs UIHostingController 언제 뭘 쓸지
UIKit과 SwiftUI를 같은 앱에서 섞어 쓸 때 등장하는 네 가지 다리(UIViewRepresentable, UIViewControllerRepresentable, UIHostingController, UIHostingConfiguration)의 용도, 패턴, 함정 정리.
UICollectionView CellRegistration — 옛날 register/dequeue 그만 쓰자
iOS 14에서 들어온 UICollectionView.CellRegistration. 문자열 reuse ID 안 만들어도 되고, as! 캐스팅도 사라지고, cell 등록과 설정 코드가 한 곳으로 모인다. 단독으로도 쓸 수 있다.
UICollectionViewDiffableDataSource 쓰면서 정리한 메모
UITableView/UICollectionView 데이터 소스의 새 표준. 옛날 cellForItemAt 패턴에서 DiffableDataSource로 옮기면서 정리한 기본 사용법, snapshot 동작, 흔한 사고.
NavigationView에서 NavigationStack으로 옮긴 메모
iOS 16에서 deprecated된 NavigationView를 NavigationStack으로 옮기면서 정리한 메모. 값 기반 push, NavigationPath, 시간 잡아먹은 두 자리.
withAnimation, .animation, .transition 헷갈릴 때 보는 메모
SwiftUI에서 상태 변화에 애니메이션 붙이려고 할 때 withAnimation, .animation modifier, .transition 셋이 어떻게 다른지. 그리고 .transition이 안 먹히는 흔한 케이스.
SwiftUI List 스크롤 끊길 때 점검하는 메모
SwiftUI List/LazyVStack 스크롤이 끊기거나 셀이 깜빡일 때 가장 먼저 보는 곳들. ForEach id, 셀 안 ViewModel 생성, AsyncImage 캐싱, 무거운 body 계산까지.
Realm에서 SwiftData로: 42개 모델을 옮기는 전략과 함정 (Part 6 — View 다중 mount 디버깅)
마이그레이션 후 리스트 화면 진입 시 스켈레톤이 두 번 깜빡이는 회귀를 가설 5개로 못 잡고, ObjectIdentifier 진단 로그 한 줄로 Store가 3번 init되는 걸 5분 만에 잡았다. Publisher 다중 trigger + invalid sentinel 합작이었고, isValid 가드 + attach 책임 단일화 + attachedFolderID latch 3중으로 막은 흐름. 시리즈 6편을 닫으며 측정이 추측을 이긴다는 thesis 정리.
iOS 17 이후 @StateObject는 거의 안 쓰게 됐다
iOS 17부터 @Observable 매크로 들어오면서 @StateObject/@ObservedObject 중에 뭘 쓸지 헷갈리던 게 거의 사라졌다.
Realm에서 SwiftData로: 42개 모델을 옮기는 전략과 함정 (Part 5 — Async/Await 통합)
동기 메서드 안에 숨겨둔 Task { await }는 호출자가 sync 완료를 모르는 fire-and-forget 안티패턴이다. SwiftData 갱신 전 옛 캐시 query가 먼저 도착해 EmptyView 한 프레임이 깜빡인 흐름과 async 시그니처 전파로 정직하게 푼 패턴 정리.
Realm에서 SwiftData로: 42개 모델을 옮기는 전략과 함정 (Part 4 — Combine 통합)
SwiftData에는 Realm Results 같은 자동 Combine publisher가 없다. Just(snapshot) + NotificationCenter merge로 직접 만들고, Just 첫 emit 즉시 흐름·이벤트 누락·잦은 이벤트·스레드 격리 4함정에 가드 거는 패턴.
Realm에서 SwiftData로: 42개 모델을 옮기는 전략과 함정 (Part 3 — DAO 패턴)
SwiftData DAO를 `class` 인스턴스로 만들면 ModelContext 라이프사이클이 흩어진다. `enum` + `static` + 단일 진입점으로 정리한 패턴과, Realm 변환 규칙·hidden state 함정까지.
Realm에서 SwiftData로: 42개 모델을 옮기는 전략과 함정 (Part 2 — Primary Key 함정 3가지)
SwiftData `@Attribute(.unique)` PK를 쓰다 만난 silent failure 3종 — sentinel 기본값, hashValue 시드 랜덤, Predicate enum 비교. 결정적 해시로 막은 이야기.
Realm에서 SwiftData로: 42개 모델을 옮기는 전략과 함정 (Part 1 — 전략편)
iOS 17 최소 지원, 일괄 마이그레이션, 데이터는 다음 sync에서 자연 복원. Realm 의존성을 떼어내는 첫 단추를 정리한다.
Swift 5.7에서 매일 쓰는 개선 7가지
Swift 5.7에 들어온 변경 중 실무에서 매일 쓰게 되는 것들 위주 정리. if let 단축, any/some, 정규식 리터럴, distributed actor, 클로저 추론, 새 String 처리, Generics where 구문까지.
UIStackView isHidden이 누적되는 동작과 안전한 토글 패턴
UIStackView 안의 view에 isHidden을 애니메이션으로 토글할 때 누른 횟수만큼 다시 눌러야 보이는 현상의 원인과, 의도대로 동작시키는 안전한 패턴 정리.
iOS Fastlane을 적용해보자
Fastlane 적용으로 원시인 탈출하기!
GCD에서 Swift Concurrency로 — 콜백 지옥을 async/await로 정리하기
GCD의 DispatchQueue·QoS 기본기와, 같은 코드를 async/await로 옮길 때 무엇이 달라지는지. 콜백 지옥, 에러 핸들링 분리, 데드락 함정까지.
iOS에 FCM 붙이기 — 인증 키부터 토큰 흐름, 그리고 자주 막히는 지점
Firebase Cloud Messaging을 iOS 프로젝트에 도입할 때 인증 키 발급부터 토큰 수신, 페이로드 수신, 자주 막히는 함정까지 한 번에 정리.
RxSwift Hot Observable vs Cold Observable — 다중 구독에서 갈리는 동작
Hot/Cold Observable의 차이는 구독 시점이 아니라 사이드 이펙트의 공유 여부. 실제 코드와 흔한 함정, 그리고 share/publish가 필요한 순간 정리.