
최근 iOS 개발 흐름에서 가장 중요한 변화 중 하나는 AI 기능이 더 이상 서버 API 호출만의 문제가 아니라는 점이다.
예전에는 앱에 AI 기능을 넣는다고 하면 보통 이런 구조를 먼저 떠올렸다.
iOS App
→ Backend API
→ LLM Provider
→ Backend API
→ iOS App
고객센터 답변 추천, 회의록 요약, 쇼핑 추천, 여행 플래너, 메뉴판 설명, 사내 문서 검색 같은 기능은 대부분 서버에서 모델을 호출하고, 앱은 결과만 받아서 보여주는 방식이었다.
이 구조는 여전히 유효하다.
하지만 Apple이 Foundation Models framework, Core AI, App Intents, Evaluations framework를 계속 확장하면서 iOS 개발자는 이제 다른 질문을 해야 한다.
이 AI 기능은 꼭 서버로 보내야 할까?
온디바이스에서 처리할 수 있는 부분은 어디까지일까?
사용자 데이터는 어디에 머물러야 할까?
모델 응답이 실패했을 때 UI는 어떻게 움직여야 할까?
AI 결과를 배포 전에 어떻게 검증할까?
즉, 앞으로 iOS 앱의 AI 기능은 단순히 “모델 API를 붙이는 일”이 아니다.
앱 안에서 어떤 작업을 온디바이스로 처리하고, 어떤 작업을 Private Cloud나 서버 모델로 넘기고, 어떤 결과를 사용자에게 보여줄지 설계하는 일이 된다.
이 글에서는 iOS 개발자가 AI 기능을 설계할 때 봐야 할 구조를 정리해본다.
1. 문제의 배경
AI 기능은 이제 많은 앱에서 기본 기능처럼 들어가기 시작했다.
예를 들어 일반적인 서비스 앱에도 이런 기능이 붙을 수 있다.
- 고객 문의 자동 분류
- 고객센터 답변 초안 생성
- 회의록 요약
- 여행 일정 추천
- 상품 추천 문구 생성
- 메뉴판 설명
- 영수증 내용 정리
- 사진 속 정보 요약
- 앱 안 검색어 자연어 처리
- 사용자 입력 문장 정리
문제는 이런 기능들이 모두 같은 성격이 아니라는 점이다.
예를 들어 “사용자가 입력한 문장을 보기 좋게 다듬는 기능”은 비교적 가볍다.
사용자 입력:
내일 오후에 강남에서 친구랑 밥먹고 카페 갈만한 곳 추천
앱 내부 처리:
문장을 정리하고 조건을 추출한다.
이런 기능은 온디바이스 모델로 처리하기 좋다.
반면 “사내 문서 전체를 검색하고 최신 정책에 근거해 답변하는 기능”은 더 복잡하다.
사용자 질문:
육아휴직 신청은 언제까지 해야 하나요?
필요한 처리:
문서 검색
최신 문서 확인
권한 확인
근거 문단 확인
답변 생성
이런 기능은 서버 검색, 권한 체크, RAG, 로그, 감사 추적이 필요할 수 있다.
즉, AI 기능을 만들 때 가장 먼저 할 일은 모델을 고르는 것이 아니다.
먼저 기능을 분류해야 한다.
1. 온디바이스로 충분한 기능
2. 서버 모델이 필요한 기능
3. 온디바이스와 서버를 섞어야 하는 기능
4. 아예 AI가 직접 답하면 위험한 기능
이 분류가 없으면 앱 구조가 쉽게 꼬인다.
2. 왜 기존 방식으로는 부족한지
기존 방식은 단순했다.
앱에서 입력값을 서버로 보내고, 서버가 AI 모델을 호출한 뒤 결과를 내려준다.
struct AIRequest: Encodable {
let text: String
}
struct AIResponse: Decodable {
let result: String
}
final class AIAPI {
func summarize(text: String) async throws -> AIResponse {
// 서버 API 호출
fatalError("example")
}
}
이 구조는 빠르게 만들기 좋다.
하지만 모든 AI 기능을 서버 호출로만 처리하면 몇 가지 문제가 생긴다.
첫 번째 문제: 민감한 입력이 서버로 이동한다
회의록 요약 기능을 생각해보자.
사용자가 회의록 원문을 붙여넣는다.
오늘 회의에서는 신규 결제 정책, 고객 환불 이슈, 내부 일정, 담당자별 작업이 논의됐다.
이 텍스트에는 내부 정보가 포함될 수 있다.
고객센터 앱이라면 고객 문의 원문, 전화번호, 주소, 주문번호가 들어갈 수 있다.
메뉴판 번역 앱이라면 민감도가 낮을 수 있지만, 사내 문서 검색 앱이라면 민감도가 높다.
AI 기능을 만들 때는 먼저 물어봐야 한다.
이 텍스트를 서버로 보내도 되는가?
모델 제공자에게 전달해도 되는가?
로그에 남아도 되는가?
디버깅용 trace에 저장해도 되는가?
온디바이스 AI가 중요한 이유는 여기서 나온다.
모든 기능을 온디바이스로 처리할 수는 없지만, 최소한 “굳이 서버로 보낼 필요 없는 작업”은 기기 안에서 처리하는 쪽이 유리하다.
두 번째 문제: AI 호출 실패가 UI 실패로 바로 이어진다
AI 기능은 일반 API보다 응답 시간이 길거나 실패 조건이 다양할 수 있다.
- 모델 사용 불가
- 네트워크 실패
- 응답 지연
- 응답 형식 깨짐
- 안전 필터로 응답 거부
- 입력이 너무 길어서 실패
- 서버 비용 제한
만약 앱이 AI 결과에만 의존하면 UI가 쉽게 불안정해진다.
나쁜 구조는 이렇다.
사용자 탭
→ AI 요청
→ 응답 올 때까지 빈 화면
→ 실패하면 아무것도 표시 안 됨
좋은 구조는 이렇다.
사용자 탭
→ 즉시 로딩 상태 표시
→ 가능한 경우 온디바이스로 1차 처리
→ 서버가 필요하면 서버 요청
→ 실패 시 안전한 fallback 표시
→ 사용자가 직접 수정할 수 있게 제공
AI 기능은 “정답을 대신 만들어주는 기능”이 아니라, 사용자 작업을 도와주는 기능으로 설계하는 편이 안전하다.
세 번째 문제: 결과가 매번 조금씩 달라진다
AI 기능은 일반 API처럼 항상 같은 출력을 보장하기 어렵다.
예를 들어 회의록 요약 결과가 매번 조금씩 다를 수 있다.
결과 A:
고객센터 AI 기능의 QA 일정과 iOS 수정 작업이 논의되었습니다.
결과 B:
신규 고객센터 AI 출시 일정, QA 계획, iOS 키보드 이슈 수정이 주요 안건이었습니다.
둘 다 맞을 수 있다.
그래서 AI 기능은 “문장 전체가 같은지”보다 “필수 구조가 지켜졌는지”를 봐야 한다.
- 요약이 비어 있지 않은가
- 액션 아이템이 포함되었는가
- 담당자가 있으면 보존되었는가
- 금지된 개인정보가 반복 노출되지 않았는가
- 사용자가 수정 가능한 형태인가
이 때문에 Apple이 Evaluations framework 같은 흐름을 강조하는 것도 자연스럽다.
AI 기능은 단위 테스트만으로 충분하지 않다.
3. 개발자가 실제로 마주치는 문제
iOS 앱에 AI 기능을 붙이면 개발자는 꽤 현실적인 문제를 만난다.
ViewModel이 너무 많은 일을 하게 된다
AI 기능을 급하게 붙이면 ViewModel이 금방 비대해진다.
@MainActor
final class MeetingSummaryViewModel {
var transcript: String = ""
var summary: String = ""
var isLoading = false
var errorMessage: String?
func summarize() async {
isLoading = true
// 입력 검증
// 프롬프트 생성
// API 호출
// JSON 파싱
// 에러 매핑
// 로그 기록
// UI 상태 반영
isLoading = false
}
}
처음에는 편하다.
하지만 시간이 지나면 문제가 된다.
- 프롬프트가 ViewModel 안에 박힌다.
- 모델 호출 로직이 UI 상태와 섞인다.
- 테스트가 어려워진다.
- 온디바이스 모델과 서버 모델 전환이 어렵다.
- 실패 정책이 화면마다 달라진다.
AI 기능은 반드시 계층을 나눠야 한다.
View
→ ViewModel
→ UseCase
→ AI Service
→ Model Provider
ViewModel은 UI 상태만 관리하고, 모델 선택과 프롬프트 구성은 별도 계층으로 빼는 편이 좋다.
온디바이스와 서버 모델을 바꾸기 어렵다
처음에는 서버 모델만 쓰다가 나중에 온디바이스 모델을 붙이고 싶을 수 있다.
반대로 온디바이스 모델만 쓰다가 특정 작업은 서버 모델로 넘기고 싶을 수도 있다.
이때 앱 코드가 특정 모델 호출에 강하게 묶여 있으면 전환이 어렵다.
나쁜 구조는 이렇다.
@MainActor
final class SummaryViewModel {
func summarize() async {
let result = try? await OpenAIClient().summarize(transcript)
self.summary = result ?? ""
}
}
이 구조에서는 모델 제공자, 네트워크, 프롬프트, UI 상태가 모두 섞인다.
좋은 구조는 추상화를 둔다.
protocol TextSummarizing {
func summarize(_ input: String) async throws -> SummaryResult
}
struct SummaryResult {
let title: String
let bulletPoints: [String]
let actionItems: [String]
}
그다음 구현체를 나눈다.
struct OnDeviceSummaryService: TextSummarizing {
func summarize(_ input: String) async throws -> SummaryResult {
// Foundation Models 또는 Core AI 기반 온디바이스 처리
fatalError("example")
}
}
struct ServerSummaryService: TextSummarizing {
func summarize(_ input: String) async throws -> SummaryResult {
// 서버 API 또는 외부 LLM provider 호출
fatalError("example")
}
}
ViewModel은 구체적인 모델을 몰라도 된다.
@MainActor
final class MeetingSummaryViewModel {
var transcript = ""
var result: SummaryResult?
var isLoading = false
var errorMessage: String?
private let summarizer: TextSummarizing
init(summarizer: TextSummarizing) {
self.summarizer = summarizer
}
func summarize() async {
guard !transcript.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
errorMessage = "요약할 내용을 입력해주세요."
return
}
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
result = try await summarizer.summarize(transcript)
} catch {
errorMessage = "요약을 만들지 못했습니다. 잠시 후 다시 시도해주세요."
}
}
}
이렇게 해두면 나중에 모델을 바꾸기 쉽다.
개발 환경:
- MockSummaryService
오프라인 모드:
- OnDeviceSummaryService
고품질 모드:
- ServerSummaryService
민감 데이터 모드:
- OnDeviceSummaryService 우선
AI 결과를 그대로 화면에 보여주면 위험하다
AI가 만든 결과를 그대로 사용자에게 보여주는 구조는 위험할 수 있다.
예를 들어 고객센터 답변 추천 기능에서 AI가 이렇게 답했다고 하자.
내일 바로 배송될 예정입니다.
실제로 배송 예정일을 확인하지 않았다면 문제가 된다.
그래서 AI 결과는 보통 후처리가 필요하다.
- 금지 문구 검사
- 개인정보 패턴 제거
- 응답 형식 검증
- 빈 응답 처리
- 너무 긴 응답 줄이기
- 사용자가 수정할 수 있는 UI 제공
예를 들어 고객센터 답변 추천 결과는 바로 전송하지 않는 편이 좋다.
AI 추천 답변 생성
→ 상담사가 확인
→ 상담사가 수정
→ 최종 전송
회의록 요약도 마찬가지다.
AI 요약 생성
→ 사용자가 수정
→ 복사 또는 저장
AI 기능은 자동 실행보다 “검토 가능한 초안”으로 시작하는 것이 안전하다.
4. 실무 설계 방식
iOS 앱에 AI 기능을 붙일 때는 아래 구조를 추천한다.
Feature/
├── Presentation/
│ ├── MeetingSummaryView.swift
│ └── MeetingSummaryViewModel.swift
├── Domain/
│ ├── SummaryResult.swift
│ ├── TextSummarizing.swift
│ └── GenerateSummaryUseCase.swift
└── Data/
├── OnDeviceSummaryService.swift
├── ServerSummaryService.swift
└── MockSummaryService.swift
역할은 이렇게 나눈다.
Presentation:
- 사용자 입력
- 로딩 상태
- 에러 메시지
- 결과 표시
Domain:
- 기능의 입력/출력 모델
- UseCase
- 모델 추상화 protocol
Data:
- 온디바이스 모델 호출
- 서버 모델 호출
- Mock 구현
이 구조의 장점은 명확하다.
- UI와 모델 호출이 분리된다.
- 온디바이스/서버 모델 전환이 쉽다.
- 테스트가 쉬워진다.
- 프롬프트 변경 영향 범위를 줄일 수 있다.
- 실패 정책을 일관되게 관리할 수 있다.
모델 선택 정책을 따로 둔다
AI 기능에서는 “어떤 모델을 쓸지”가 중요하다.
이걸 ViewModel에서 직접 결정하면 안 된다.
별도 policy를 두는 편이 좋다.
enum AIExecutionMode {
case onDevice
case server
case automatic
}
struct AIExecutionPolicy {
let mode: AIExecutionMode
let allowsServerFallback: Bool
let containsSensitiveInput: Bool
}
UseCase에서 이 정책을 기준으로 모델을 고른다.
struct GenerateSummaryUseCase {
private let onDeviceService: TextSummarizing
private let serverService: TextSummarizing
func execute(
input: String,
policy: AIExecutionPolicy
) async throws -> SummaryResult {
switch policy.mode {
case .onDevice:
return try await onDeviceService.summarize(input)
case .server:
return try await serverService.summarize(input)
case .automatic:
if policy.containsSensitiveInput {
return try await onDeviceService.summarize(input)
}
do {
return try await onDeviceService.summarize(input)
} catch {
guard policy.allowsServerFallback else {
throw error
}
return try await serverService.summarize(input)
}
}
}
}
이 예시는 단순하지만 중요한 기준을 보여준다.
민감한 입력:
- 온디바이스 우선
고품질이 필요한 작업:
- 서버 또는 Private Cloud 고려
오프라인에서도 동작해야 하는 작업:
- 온디바이스 필수
결과가 업무 정책에 영향을 주는 작업:
- AI 결과를 초안으로만 제공
5. 코드 또는 설정 파일 예시
이번 예시는 “여행 플래너 앱의 일정 요약 기능”으로 잡아보자.
사용자가 여행 조건을 입력하면 앱이 간단한 일정 초안을 만든다.
목적지: 제주
기간: 2박 3일
스타일: 가족 여행
예산: 80만 원
AI는 다음 구조로 결과를 반환해야 한다.
struct TravelPlanSummary {
let title: String
let overview: String
let days: [DayPlan]
let cautions: [String]
}
struct DayPlan {
let day: Int
let theme: String
let places: [String]
}
Domain 계층
protocol TravelPlanGenerating {
func generate(from input: TravelPlanInput) async throws -> TravelPlanSummary
}
struct TravelPlanInput {
let destination: String
let nights: Int
let days: Int
let style: String
let budget: Int
}
struct TravelPlanSummary {
let title: String
let overview: String
let days: [DayPlan]
let cautions: [String]
}
struct DayPlan {
let day: Int
let theme: String
let places: [String]
}
여기서 중요한 점은 AI 결과를 문자열 하나로 받지 않는 것이다.
나쁜 방식은 이렇다.
let result: String
좋은 방식은 구조화된 결과를 받는 것이다.
let result: TravelPlanSummary
구조화된 결과가 있으면 UI가 안정된다.
- 제목 영역
- 요약 영역
- 일차별 카드
- 주의사항 영역
각 UI 영역이 명확해진다.
ViewModel
@MainActor
final class TravelPlannerViewModel {
var destination = ""
var nights = 2
var days = 3
var style = "가족 여행"
var budget = 800_000
var plan: TravelPlanSummary?
var isGenerating = false
var errorMessage: String?
private let generator: TravelPlanGenerating
init(generator: TravelPlanGenerating) {
self.generator = generator
}
var canGenerate: Bool {
!destination.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
&& days > 0
&& !isGenerating
}
func generate() async {
guard canGenerate else { return }
isGenerating = true
errorMessage = nil
defer { isGenerating = false }
let input = TravelPlanInput(
destination: destination,
nights: nights,
days: days,
style: style,
budget: budget
)
do {
plan = try await generator.generate(from: input)
} catch {
errorMessage = "일정을 생성하지 못했습니다. 조건을 조금 단순하게 바꿔 다시 시도해주세요."
}
}
}
ViewModel은 AI 모델이 무엇인지 모른다.
온디바이스 모델이든, 서버 모델이든, Mock이든 상관없다.
ViewModel은 오직 UI 상태만 관리한다.
SwiftUI View
struct TravelPlannerView: View {
@State private var viewModel: TravelPlannerViewModel
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
TextField("목적지", text: $viewModel.destination)
Stepper("기간: \(viewModel.days)일", value: $viewModel.days, in: 1...10)
TextField("여행 스타일", text: $viewModel.style)
Button {
Task {
await viewModel.generate()
}
} label: {
if viewModel.isGenerating {
ProgressView()
} else {
Text("일정 생성")
}
}
.disabled(!viewModel.canGenerate)
if let errorMessage = viewModel.errorMessage {
Text(errorMessage)
.foregroundStyle(.red)
}
if let plan = viewModel.plan {
TravelPlanResultView(plan: plan)
}
}
.padding()
}
}
}
AI 기능에서 UI는 반드시 “기다리는 상태”와 “실패한 상태”를 자연스럽게 보여줘야 한다.
- 생성 중
- 실패
- 빈 결과
- 재시도 가능
- 사용자가 수정 가능
이 상태들이 없으면 AI 기능은 금방 불안정하게 느껴진다.
OnDevice 구현체 예시
아래 코드는 특정 SDK 버전의 정확한 API 사용법이라기보다, Foundation Models 같은 온디바이스 모델 계층을 앱 구조 안에 어떻게 감쌀지 보여주는 개념 예시다.
struct OnDeviceTravelPlanGenerator: TravelPlanGenerating {
func generate(from input: TravelPlanInput) async throws -> TravelPlanSummary {
let prompt = """
다음 조건에 맞는 여행 일정 초안을 만들어줘.
목적지: \(input.destination)
기간: \(input.nights)박 \(input.days)일
스타일: \(input.style)
예산: \(input.budget)원
결과는 제목, 요약, 일차별 장소, 주의사항으로 나눠줘.
"""
// Foundation Models 또는 Core AI 기반 온디바이스 모델 호출 계층
// 실제 구현에서는 SDK 버전과 모델 availability를 확인해야 한다.
return TravelPlanSummary(
title: "\(input.destination) \(input.days)일 여행 초안",
overview: "\(input.style) 스타일에 맞춘 일정입니다.",
days: [
DayPlan(day: 1, theme: "도착과 가벼운 산책", places: ["공항", "숙소", "근처 맛집"]),
DayPlan(day: 2, theme: "대표 관광지", places: ["해변", "시장", "카페"]),
DayPlan(day: 3, theme: "복귀 전 여유 일정", places: ["브런치", "기념품 shop"])
],
cautions: [
"실제 영업시간과 이동 시간은 출발 전 확인하세요.",
"날씨에 따라 실내 대체 일정을 준비하세요."
]
)
}
}
실제 앱에서는 여기서 모델 availability, 입력 길이, 언어, 실패 처리, 결과 파싱, 평가 기준까지 함께 봐야 한다.
Server 구현체 예시
struct ServerTravelPlanGenerator: TravelPlanGenerating {
private let api: TravelPlanAPI
func generate(from input: TravelPlanInput) async throws -> TravelPlanSummary {
try await api.generateTravelPlan(input)
}
}
서버 모델은 다음 상황에서 유리할 수 있다.
- 더 큰 컨텍스트가 필요하다.
- 최신 외부 데이터가 필요하다.
- 사용자 계정/권한 기반 검색이 필요하다.
- 품질 평가와 로깅이 필요하다.
- 여러 플랫폼에서 같은 결과가 필요하다.
온디바이스 모델은 다음 상황에서 유리하다.
- 빠른 응답이 중요하다.
- 오프라인 동작이 필요하다.
- 입력이 민감하다.
- 서버 비용을 줄이고 싶다.
- 앱 안에서 가벼운 문장 정리나 분류가 필요하다.
6. 운영 시 주의할 점
iOS 앱에 AI 기능을 넣을 때는 몇 가지 원칙이 필요하다.
온디바이스라고 무조건 안전한 것은 아니다
온디바이스 AI는 사용자 데이터가 기기 밖으로 나가지 않는다는 장점이 있다.
하지만 그것만으로 모든 문제가 해결되지는 않는다.
앱 내부에서 다음 문제가 생길 수 있다.
- 민감한 입력을 로컬 로그에 남김
- AI 결과를 그대로 화면에 노출
- 모델 파일 크기로 앱 용량 증가
- 오래된 기기에서 성능 저하
- 배터리 사용량 증가
- 긴 입력에서 응답 지연
- 결과 품질이 서버 모델보다 낮을 수 있음
따라서 온디바이스 AI도 운영 기준이 필요하다.
- 원문 입력 로그 금지
- 결과 후처리
- 실패 시 fallback
- 지원 기기 체크
- 응답 시간 모니터링
- 사용자가 수정 가능한 UI 제공
모델 availability를 UI 흐름에 반영한다
AI 기능은 모든 기기에서 항상 같은 방식으로 동작하지 않을 수 있다.
기기 성능, OS 버전, Apple Intelligence 지원 여부, 네트워크 상태, 서버 설정에 따라 달라질 수 있다.
따라서 UI는 기능 사용 가능 여부를 고려해야 한다.
enum AIFeatureAvailability {
case available
case unavailable(reason: String)
case requiresNetwork
case requiresSupportedDevice
}
ViewModel에서 이 상태를 관리한다.
@MainActor
final class AIFeatureViewModel {
var availability: AIFeatureAvailability = .available
func checkAvailability() {
// 실제 구현에서는 OS 버전, 모델 지원 여부, 네트워크 상태 등을 확인한다.
availability = .available
}
}
사용자에게는 단순하고 친절하게 보여주는 것이 좋다.
이 기기에서는 AI 요약을 사용할 수 없습니다.
최신 OS와 지원 기기에서 사용할 수 있습니다.
또는 서버 fallback이 가능하다면 이렇게 제공할 수 있다.
온디바이스 요약을 사용할 수 없어 서버 요약으로 전환합니다.
단, 민감한 데이터라면 서버 fallback을 자동으로 하면 안 된다.
AI 결과는 초안으로 제공하는 것이 안전하다
고객센터 답변, 여행 일정, 회의록 요약, 상품 추천 문구는 대부분 “최종 정답”이 아니라 “초안”으로 제공하는 것이 안전하다.
나쁜 UX:
AI가 생성한 답변을 바로 전송
좋은 UX:
AI가 답변 초안 생성
→ 사용자가 확인
→ 사용자가 수정
→ 사용자가 직접 전송
특히 다음 기능은 자동 실행을 조심해야 한다.
- 고객에게 메시지 전송
- 결제 또는 환불
- 예약 변경
- 개인정보 수정
- 계정 삭제
- 사내 문서 외부 공유
AI는 사용자를 돕는 보조자여야지, 중요한 작업을 조용히 대신 실행하면 안 된다.
테스트는 문장 전체가 아니라 구조와 안전 조건을 본다
AI 결과는 매번 조금씩 달라질 수 있다.
따라서 테스트는 문장 전체를 고정하면 쉽게 깨진다.
나쁜 테스트는 이런 식이다.
XCTAssertEqual(
result.overview,
"제주 가족 여행을 위한 2박 3일 일정입니다."
)
더 나은 테스트는 구조를 본다.
XCTAssertFalse(result.title.isEmpty)
XCTAssertEqual(result.days.count, 3)
XCTAssertFalse(result.cautions.isEmpty)
금지 조건도 확인할 수 있다.
let forbiddenPhrases = [
"무조건 가능합니다",
"확정입니다",
"보장합니다"
]
for phrase in forbiddenPhrases {
XCTAssertFalse(result.overview.contains(phrase))
}
AI 기능 테스트는 “정확히 같은 문장”보다 “사용 가능한 구조와 안전 조건”을 검증해야 한다.
7. 팀 단위로 적용하는 방법
팀에서 iOS AI 기능을 만들 때는 처음부터 크게 시작하지 않는 것이 좋다.
현실적인 순서는 다음과 같다.
1단계: 온디바이스 후보 기능을 고른다
처음부터 핵심 업무 기능을 AI에 맡기지 말고, 위험이 낮고 효과가 분명한 기능부터 시작한다.
좋은 시작점:
- 문장 정리
- 입력값 분류
- 회의록 초안 요약
- 여행 조건 구조화
- 메뉴판 설명
- 상품 설명 요약
- 검색어 추천
조심할 기능:
- 결제
- 환불
- 계정 변경
- 법률/의료 조언
- 고객에게 자동 전송
- 사내 문서 외부 공유
처음에는 “사용자가 직접 확인하는 초안 기능”부터 시작하는 편이 좋다.
2단계: AI 서비스 protocol을 먼저 만든다
구체 모델을 바로 붙이기 전에 protocol부터 만든다.
protocol AITextGenerating {
func generate(prompt: String) async throws -> String
}
그다음 구현체를 나눈다.
struct OnDeviceTextGenerator: AITextGenerating {
func generate(prompt: String) async throws -> String {
fatalError("on-device implementation")
}
}
struct ServerTextGenerator: AITextGenerating {
func generate(prompt: String) async throws -> String {
fatalError("server implementation")
}
}
struct MockTextGenerator: AITextGenerating {
func generate(prompt: String) async throws -> String {
"mock result"
}
}
이 구조가 있으면 UI 개발, 테스트, 실제 모델 연동을 분리할 수 있다.
3단계: 프롬프트를 코드 안에 흩뿌리지 않는다
프롬프트가 ViewModel 곳곳에 있으면 유지보수가 어려워진다.
나쁜 방식은 이렇다.
let prompt = "이 내용을 요약해줘: \(text)"
좋은 방식은 PromptBuilder를 둔다.
struct MeetingSummaryPromptBuilder {
func build(transcript: String) -> String {
"""
다음 회의록을 요약해줘.
조건:
- 핵심 내용 3개
- 액션 아이템
- 담당자가 있으면 유지
- 없는 내용은 만들지 않기
회의록:
\(transcript)
"""
}
}
프롬프트도 코드처럼 관리해야 한다.
- 버전 관리
- 리뷰
- 테스트
- 실패 케이스 반영
4단계: AI 기능에도 QA 기준을 만든다
AI 기능은 일반 기능보다 QA 기준이 더 중요하다.
예를 들어 회의록 요약 기능의 QA 기준은 이렇게 만들 수 있다.
# Meeting Summary AI QA Policy
## Success Criteria
- 요약이 비어 있지 않다.
- 핵심 안건이 포함된다.
- 액션 아이템이 분리된다.
- 담당자가 있으면 유지된다.
- 없는 내용을 새로 만들지 않는다.
- 회의록 원문 전체를 로그로 남기지 않는다.
## Failure Criteria
- 원문에 없는 결론을 만든다.
- 담당자를 잘못 지정한다.
- 액션 아이템을 누락한다.
- 개인정보를 반복 노출한다.
- 응답이 너무 길어 UI를 깨뜨린다.
AI 기능은 “대충 그럴듯한 결과”가 아니라 “서비스에서 쓸 수 있는 결과”를 기준으로 봐야 한다.
8. 마무리
iOS 앱에 AI를 붙이는 방식은 바뀌고 있다.
예전에는 서버에서 모델을 호출하고 앱은 결과만 보여주는 구조가 자연스러웠다.
하지만 이제는 Foundation Models, Core AI, App Intents, Evaluations 같은 흐름 때문에 iOS 앱 안에서 더 많은 AI 판단과 처리가 가능해지고 있다.
중요한 것은 “무조건 온디바이스가 정답”이라는 이야기가 아니다.
핵심은 작업을 나누는 것이다.
온디바이스가 좋은 작업:
- 민감한 입력
- 빠른 응답
- 오프라인 지원
- 가벼운 분류와 정리
- 사용자 보조 기능
서버가 좋은 작업:
- 최신 데이터 필요
- 큰 컨텍스트 필요
- 복잡한 RAG
- 권한 기반 검색
- 운영 trace와 평가 필요
혼합이 좋은 작업:
- 온디바이스 1차 처리
- 서버 고품질 보완
- 사용자가 선택하는 fallback
앞으로 iOS 개발자는 AI 모델을 단순 API로만 보면 안 된다.
UI 상태, 개인정보, 모델 availability, fallback, 평가, 테스트, 사용자 검토 흐름까지 함께 설계해야 한다.
좋은 AI 앱은 “AI가 답을 잘하는 앱”이 아니다.
사용자 데이터가 안전하고, 실패해도 UI가 무너지지 않고, 결과를 사용자가 검토할 수 있으며, 모델이 바뀌어도 앱 구조가 흔들리지 않는 앱이다.
iOS 개발자가 지금 봐야 할 핵심은 이것이다.
AI 기능을 붙이는 것이 아니라,
AI가 들어와도 유지보수 가능한 앱 구조를 만드는 것
Foundation Models와 Core AI의 의미도 여기에 있다.
AI가 서버 밖으로 나오고 있다.
이제 iOS 앱 자체가 AI 실행 환경이 될 수 있다.
그만큼 앱 개발자는 더 많은 설계 책임을 갖게 된다.
모델 호출은 시작일 뿐이다.
진짜 실무는 그다음부터다.
## 참고 자료
- Apple Developer: WWDC26 Apple Intelligence Guide
- Apple Developer Documentation: Foundation Models
- Apple Newsroom: Apple aids app development with new intelligence frameworks and advanced tools
- Apple Developer: Bring an LLM provider to the Foundation Models framework
- Apple Developer: What’s New in iOS'IT' 카테고리의 다른 글
| AI Agent Runtime의 다음 진화: Stateless Core + Stateful Application 아키텍처 (1) | 2026.07.18 |
|---|---|
| AI 에이전트가 어제 한 일을 잊지 않게 만드는 법: Durable Memory와 Run Ledger 설계 (1) | 2026.07.18 |
| 최근 AI 새 버전들이 진짜로 밀고 있는 기술: 모델보다 중요한 Agent Runtime 설계 (1) | 2026.07.18 |
| AI 코딩 에이전트 성능을 확 올리는 실전 스킬: Context Pack 만들기 (0) | 2026.07.18 |
| GPT-5.6 발표로 보는 AI 에이전트 개발 워크플로 설계법 (0) | 2026.07.18 |