Keepiluv
커플 소통 앱 · 2025.11.22. ~ ing · 9인 팀 (iOS 2)
YAPP 27기 대상 App Store 출시
Swift SwiftUI TCA MFA Universal Link FCM Cloudflare Workers Pulse
프로젝트 맥락
커플이 초대 링크로 처음 들어온 뒤 연결을 완료하고, 이후 알림으로 다시 돌아오는 흐름까지 끊기지 않게 만드는 데 집중했습니다.
SwiftUI 기반 프로젝트고, 상태 관리와 모듈 경계를 직접 검증하기 위해 TCA와 MFA를 함께 적용했습니다.
구현
초대 링크 진입부터 연결까지
  • 문제 초대 링크를 받았을 때 앱 설치 여부에 따라 진입 방식이 달랐고,
    설치 이후에도 다음 단계로 자연스럽게 이어지지 않을 수 있었습니다.
  • 해결 AASA를 적용해 Universal Link 기반 초대 흐름을 만들었고,
    앱이 설치되지 않은 경우에는 랜딩페이지를 거쳐 다시 앱으로
    이어지도록 정리했습니다.
  • 결과 앱 밖에서 시작된 초대가 커플 연결과 이후 입력 흐름까지 끊기지 않도록 만들었습니다.
연결 대기 + 상태 기반 온보딩
  • 문제 상대방 연결을 기다리는 구간과 이미 입력한 정보를 다시 받는 구간이
    온보딩 과정에서 불편할 수 있었습니다.
  • 해결 연결 완료는 polling으로 바로 반영했고, 온보딩은 상태값을 기준으로
    완료된 입력은 건너뛰도록 구성했습니다.
  • 결과 처음 들어온 사용자가 중간에 멈추지 않도록 했고,
    중복 입력도 줄였습니다.
푸시 알림 재진입 흐름
  • 문제 알림 종류가 늘어나면서 알림을 눌렀을 때 어떤 화면으로 보내야 하는지
    규칙이 복잡해졌습니다.
  • 해결 twix://notification/[type] 형태의 딥링크를 enum으로 매핑하고, Coordinator에서 화면 전환을 처리했습니다.
  • 결과 알림으로 다시 들어왔을 때도 끊기지 않는 흐름을 만들었습니다.
구조와 협업 보조 경험
  • 구조 TCA와 MFA를 함께 적용해 상태 관리와 모듈 경계를 직접 검증했습니다.
  • 협업 Cloudflare Workers로 Figma → Slack, Linear ↔ GitHub 자동화를 구성해 반복 작업을 줄였습니다.
  • 디버깅 Pulse로 서버 연동 과정의 요청과 응답을 빠르게 확인했습니다.
판단
카카오톡 안에서는 Universal Link가 동작하지 않았다

단순 테스트에서는 문제가 없었습니다. 문제는 실제 사용 환경이었습니다. 커플이 초대 링크를 전달할 때 카카오톡이나 Instagram 같은 외부 서비스를 쓰면 링크가 WebView로 열립니다. 이 환경에서는 AASA가 읽히지 않아 Universal Link가 동작하지 않았고, 결국 앱으로 진입할 수 없었습니다.

그래서 구조를 바꿨습니다. 랜딩페이지를 따로 두고, 거기서 Custom App Scheme으로 앱을 여는 방식으로 우회했습니다. 처음 설계했던 Universal Link 중심 구조를 그대로 밀기보다, 실제 사용 흐름에서 더 잘 동작하는 방식으로 바꾸는 게 맞다고 판단했습니다.

Reed
독서 기록 앱 · 2025.06.11. ~ 2026.03.29. · 9인 팀 (iOS 2) · 서비스 종료
YAPP 26기 최우수상 App Store 출시
UIKit Combine Tuist Clean Architecture OCR APNs / FCM URLSession
프로젝트 맥락
출시와 운영 과정에서 실제로 드러난 문제를 계속 손본 프로젝트였습니다.
Clean Architecture는 협업을 위해 선택했고, Tuist는 Xcode 기본 모듈화의 한계를 겪은 뒤 전환했습니다.
구현
App Store 심사 거절 대응
  • 문제 로그인 없이 앱에 진입할 수 없는 구조가 HIG 위반으로
    심사에서 거절됐습니다.
  • 해결 서버팀과 함께 인증이 꼭 필요한 API만 분리하고, iOS에서는 게스트/멤버 접근 모드를 나눠 인증이 필요한 경우에만 로그인을 유도했습니다.
  • 결과 심사 거절 사유를 해소하고 App Store에 출시했습니다.
OCR 후처리
  • 문제 OCR 인식 텍스트 품질이 낮아 그대로 쓰기 어려웠습니다.
  • 해결 NLTokenizer, NLTagger, UITextChecker를 조합해
    후처리 모듈을 만들고, confidence 기준으로 텍스트를 다듬었습니다.
  • 결과 OCR 인식 텍스트 품질을 개선했습니다.
DI Container + 네트워크 레이어 직접 구현
  • 배경 외부 라이브러리를 바로 도입하기보다, 프로젝트 구조에 맞는 방식으로 직접 제어해보고 싶었습니다.
  • 구현 Spring의 @Autowired에서 아이디어를 얻어 의존성을 자동으로 resolve하는 DI Container를 만들었습니다. 네트워크 레이어는 URLSession 기반으로 일반 API와 OAuth 흐름을 나눠 구성했습니다.
  • 한계 의존성 순서가 어긋나거나 상호 의존 관계가 생기면 크래시의 위험이
    있었습니다. 편의성과 안정성이 같지 않다는 걸 직접 확인했습니다.
APNs / FCM 연동
  • 구현 APNs·FCM 환경을 구성하고, 앱에서 푸시 알림을 수신할 수 있도록 기본 연동을 구현했습니다.
판단
Apple의 요구를 받아들이기까지

심사 거절을 받고 Apple에 기획 의도를 설명하며 재검토를 요청했습니다. 도서 검색 데이터가 외부 서비스에 의존하는 구조라 회원에게만 제공하는 게 합리적이라고 판단했고,
그 근거도 전달했습니다. 하지만 Apple은 HIG 기준을 이유로 변경 없이는 승인하지 않겠다고 했습니다.

결국 받아들이기로 했습니다. 출시를 위해서는 선택지가 없었고, 서버 팀과 함께 구조를 바꿨습니다. 기획 의도를 끝까지 밀고 가는 것보다, 출시 가능한 구조로 바꾸는 게 맞다고 판단했습니다.

이중 ScrollView 구조 화면을 재작성하다

독서 기록 등록 화면을 처음엔 세로 ScrollView 안에 가로 ScrollView를 넣는 구조로 설계했습니다. 단계별로 컨텐츠 높이가 달라 StackView가 높이를 계산하지 못했고,
작은 디바이스에서는 UI가 잘리거나 페이징이 동작하지 않았습니다.

구조를 반대로 바꿨습니다. 바깥을 가로 페이징 ScrollView로 두고, 내부를 페이지별 세로 ScrollView로 다시 설계했습니다. 페이지마다 높이를 독립적으로 가져가면서
문제가 해결됐고, 처음 설계를 고수하기보다 구조를 다시 짜는 편이 더 빠른 선택이었습니다.

Heim
AI 기반 음성 일기 앱 · 2024.10.28. ~ 2024.12.05. · 4인 iOS 팀
부스트캠프 웹·모바일 9기
UIKit CoreML Gemini API Structured Concurrency AVAudioPlayer Keychain
프로젝트 맥락
음성 데이터를 처리해 일기와 감정을 정리하는 앱이었고, 응답 시간이 길다는 문제가 가장 크게 보였습니다.
기존 구조가 Swift Concurrency 기반이었고, 순차 실행 때문에 응답 시간이 길어져 Structured Concurrency로 전환했습니다.
구현
응답 시간 단축
15.5s → 3.9s
Structured Concurrency 기반 병렬 처리
평균 응답 시간 약 4배 개선
  • 문제 음성 텍스트 추출, 감정 분석, 생성형 AI 요청이 순차적으로 실행되면서
    응답 시간이 최악 기준 15초를 넘었습니다.
  • 해결 의존 관계가 없는 작업을 분리해 Structured Concurrency 기반
    병렬 처리로 바꿨습니다. 프롬프트 출력 길이도 제한해 API 응답 시간
    자체를 줄였습니다.
  • 결과 평균 응답 시간을 15.5초에서 3.9초로 줄였고, 사용자가 기다리는 시간을 크게 줄였습니다.
프롬프트 인젝션 방어
  • 발견 보안 전공 배경으로 인젝션 가능성을 의심했고, 데모 전 테스트에서 악의적 입력을 직접 넣어보며 취약점을 확인했습니다.
  • 해결 입력 길이 제한과 래핑을 적용하고, 시스템 프롬프트와 사용자 입력을 분리하는 정책을 반영했습니다.
  • 결과 악성 입력이 프롬프트 명령으로 해석되지 않도록 막았습니다.
오디오 시각화 구현
  • 구현 PCM 데이터를 기반으로 재생 중인 오디오 크기를 시각화하는 VisualizerView를 구현했습니다.
  • 의도 단순 재생 여부보다 사용자가 듣고 있는 구간을 더 직관적으로
    느낄 수 있도록 만들고 싶었습니다.
판단의 순간
Spotify API 지원이 갑자기 종료됐다

감정에 맞는 음악 추천 기능을 Spotify API 기준으로 설계하고 구현하던 중, 사용하던 엔드포인트가 갑자기 동작하지 않았습니다. 확인해보니 Spotify가 해당 API 지원을 종료한 상태였습니다.

대안을 찾아봤지만, 우리 앱이 원했던 건 단순 플레이리스트 추천이 아니라 미세한 감정값에 따라 적절한 음원을 고르는 기능이었습니다. 그 수준의 데이터를 안정적으로 제공하는 대안은 찾기 어려웠고, 억지로 비슷한 기능을 남기기보다 해당 기능을 제거하는 편이 맞다고 판단했습니다.

이 경험 이후에는 외부 API를 고를 때 기능 자체만이 아니라, 대체 가능성과 백업 플랜까지 같이 보게 됐습니다.