Engineering
App Intent, 어디까지 쓸 수 있을까
김지영Tori(토리) / iOS 개발팀여기어때
2025년 12월 17일
원문에서 보기 ↗App Intent와 Apple Intelligence 매핑의 현재 한계
안녕하세요. 여기어때 iOS개발팀 토리 입니다.
iOS 18 이후 Apple Intelligence가 공개되면서, 우리는 애플 기본 제공 앱과 서드파티 앱을 통합적으로 제어할 수 있을 거라고 기대합니다.
예를 들면 이런 생각을 해보게 됩니다.
“이제 시리한테 여기어때 앱에 있는 정보를 가지고 적절한 명령을 하면, 이 정보를 활용해서 애플 기본 제공 앱과 연동할 수 있겠지?”
이 글에서는 이런 기대가 실제로 어느 정도까지 가능한지 확인하기 위해, App Intents가 무엇인지 , 현재 어떤 수준까지 적용해 볼 수 있는지를 여기어때 앱의 “예약내역 → 캘린더 저장” 시나리오를 예시로 해서 정리해보겠습니다.
1. App Intents는 앱의 행동을 노출하는 인터페이스
Apple 공식 문서 기준으로, App Intents는 이렇게 정의합니다.
App Intents 프레임워크는 앱의 액션과 콘텐츠를 시스템 전반(Siri, Spotlight, 위젯, 컨트롤 등)에 깊게 통합할 수 있게 해주는 기능을 제공한다.
즉, 앱이 할 수 있는 동작(예: 쿠폰 적용, 예약 보기, 특정 예약 열기)을 Intent(동사) + Entity(명사) 로 정의해서 시스템(Siri, Shortcuts, Spotlight, Apple Intelligence 등)이 그 동작을 직접 호출할 수 있게 하는 인터페이스 라고 볼 수 있습니다.
App Intetnt를 구현할때 생각해야 할 포인트는 두 가지입니다.
(1) App Intent에서 핵심은 행동 정의와 데이터 구조화
App Intent가 하는 일은 ‘어떤 행동을 할 것인지’ 정의 하는 것입니다. 그래서 어떤 행동을 정의하느냐에 따라 앱에서 준비해야 할 것이 다릅니다.
예를 들어 시리에게 “여기어때 예약내역 상세 화면을 열어줘”정도만 명령할 것이라면, 해당 Intent에서는 앱을 열고 라우터를 통해 예약내역 화면으로 이동만 해 주면 됩니다. 이때는 그 화면이 WebView인지 네이티브 화면인지는 중요하지 않습니다.
struct ShowReservationHistoryIntent: AppIntent { ... }
위 Intent 안에서앱 라우터를 호출해 예약내역 화면으로 이동하게 만들면 되고, 그 화면을 WebView로 구현하든, 네이티브로 구현하든 App Intent 관점에서는 차이가 없습니다.
문제가 되는 건 다음과 같은 요청입니다.
- “여기어때 예약 내역을 캘린더에 저장해줘”
- “여기어때 부산 예약내역의 체크인 날짜를 말해줘”
이건 단순 화면 오픈이 아니라, 예약 데이터를 가공해야 하는 케이스입니다.
만약 예약화면이 WebView로만 되어있고, 별도의 예약내역 API없이 매번 DOM 파싱/스크래핑을 해야 예약 정보를 알 수 있다면, 현실적으로 이런 요청을 App Intent에서 활용하기는 어렵습니다.
반대로, 서버에 JSON API가 있고 앱이 그걸 Reservation 모델로 가지고 있다면 화면이 WebView여도 상관 없이, App Intent에서 이 모델을 기반으로 필터링/요약/캘린더 연동 등을 처리할 수 있습니다.
정리하면, 핵심은 App Intent로 어떤 행동을 정의할지, 그 행동에 필요한 데이터가 네이티브에서 구조화된 형태로 준비돼있는지 입니다.
(2)App Intent는 앱 실행 중이 아니어도 동작한다.
자연스럽게 이런 궁금증이 생깁니다.
시리에게 언제든지 “여기어때 예약 내역을 캘린더에 저장해줘”라고 명령 하려면, 앱이 항상 예약 데이터를 메모리에 들고 있어야 하나?
결론을 먼저 말하자면, 그럴 필요는 없습니다.
시스템(iOS)은 필요할 때 App Intents Extension이나 앱 프로세스를 백그라운드에서 깨운 뒤 , 해당 Intent의 perform()을 실행합니다.
이때 중요한 건 “앱이 실행중인가?” 가 아니라 “데이터를 어떻게든 다시 가져올 수 있는 경로가 준비 돼 있느냐”입니다.
즉, 언제든지 다시 가져올 수 있는 API/서비스가 있어야 합니다.
perform()이 실행되는 시점에 네트워크(API) 호출 혹은 로컬 DB/캐시 조회로 그때그때 필요한 예약데이터를 다시 가져오면 , 앱이 꺼져 있다가도 필요한 데이터만 다시 조립해서 행동을 수행할 수 있습니다.
2. App Intents를 구현해봅시다.
“시리야, 여기어때 예약 내역을 캘린더에 저장해줘.”
이 명령을 App Intents로 처리하려면, 여기어때 앱 코드에 아래에서 소개하는 요소들을 준비해야 합니다.
(1) 네이티브 예약 모델 + API
먼저 예약 내역을 표현하는 모델이 필요합니다.
struct Reservation {
let id: String
let city: String
let hotelName: String
let checkInDate: Date
let checkOutDate: Date
}
- 서버에서 예약내역을 JSON 등으로 내려주는 API
ReservationService.fetchAllReservations()→[Reservation]
같은 구조가 준비되어야 합니다. 여기서는 더미데이터를 활용하겠습니다.
(2) Intent정의
이제 핵심 Intent를 하나 정의해 봅시다.
import Foundation
import AppIntents
struct SaveUpcomingReservationsToCalendarIntent: AppIntent {
static var title: LocalizedStringResource = "여기어때 숙소 예약내역들을 캘린더에 저장"
static var description = IntentDescription(
"앞으로 일정 기간 동안의 여기어때 예약 일정을 캘린더에 자동으로 추가합니다."
)
func perform() async throws -> some IntentResult & ProvidesDialog {
// container 가져옴 (저장 프로퍼티 아님)
let container = ServiceContainer.shared
// 0. 캘린더 권한 확인/요청
do {
try await container.calendarService.requestAccessIfNeeded()
} catch {
return .result(
dialog: IntentDialog(stringLiteral: "캘린더 접근 권한이 없어 일정을 저장할 수 없습니다.")
)
}
// 1. 예약내역 조회
let reservations = try await container
.reservationService
.fetchUpcomingReservations()
if reservations.isEmpty {
return .result(
dialog: IntentDialog(stringLiteral: "예약 내역이 없습니다.")
)
}
// 2. 캘린더에 추가 (reservation는 배열이므로 반복문 안에서 사용)
for reservation in reservations {
do {
try container.calendarService.addReservationToCalendar(reservation)
} catch {
// 개별 실패는 로그만 남기고 계속 진행하도록 처리 가능
}
}
// 3. Siri에 보여줄/읽어줄 문구
let dialogText = "예약 \(reservations.count)건을 캘린더에 저장했습니다."
return .result(
dialog: IntentDialog(stringLiteral: dialogText)
)
}
}
이 Intent의 역할은 “여기어때 예약들을 조회해서, 각각을 캘린더에 이벤트로 저장하는 AppIntent” 입니다.
구성을 조금 더 봐봅시다.
a. 메타데이터(title, description)
- Shortcuts 앱 / Siri에서 액션 설명으로 사용
b. 실행 로직(perform)
ServiceContainer.shared에서 서비스 가져오기reservationService.fetchUpcomingReservations()로 예약 리스트 조회- 비어 있으면 “예약 내역이 없습니다.” 반환
- 있으면
calendarService.addReservationToCalendar(_:)로 반복 저장 - 마지막에 “예약 X건을 캘린더에 저장했습니다.” 라는 메시지를 dialog로 반환하여 Siri가 이 문장을 읽어줌
(3) 의존성 컨테이너: ServiceContainer
App Intent가 네트워크나 EventKit에 직접 붙지 않도록, 의존성 컨테이너를 하나 두고 서비스만 바라보도록 합니다.
DEBUG 빌드에서는 목 객체MockReservationService를, 릴리즈 빌드에서는 실제 API 서비스 APIReservationService를 바라보도록 했습니다.
final class ServiceContainer {
static let shared = ServiceContainer()
let reservationService: ReservationService
let calendarService: CalendarService
private init() {
#if DEBUG
self.reservationService = MockReservationService()
#else
self.reservationService = APIReservationService()
#endif
self.calendarService = EventKitCalendarService()
}
}
(4) 캘린더 서비스: EventKitCalendarService
EventKitCalendarService는 CalendarService 프로토콜의 실제 구현으로,
예약 정보를 iOS 기본 캘린더(EventKit)에 이벤트로 저장하는 역할을 합니다.
final class EventKitCalendarService: CalendarService {
// ...
}
App Intent는 이 서비스를 통해서만 캘린더를 제어합니다.
EventKitCalendarService 내부에는 두 개의 메서드
- 캘린더 권한 요청/확인:
requestAccessIfNeeded() - 예약 데이터 기반으로 이벤트 생성:
addReservationToCalendar(_)
를 둡니다.
requestAccessIfNeeded()는 Intent 실행 초기에 한 번 호출한 이후 addReservationToCalendar(_)에서는 권한이 있다고 가정하고 저장하는 식으로 역할을 분리할 수 있습니다.
MockReservationService는 실제 API 없이도 Intent 흐름을 검증하기 위한 목 구현입니다. “지금으로부터 3~5일, 10~12일 뒤”의 더미 예약 두 건을 내려줍니다.
final class MockReservationService: ReservationService {
func fetchUpcomingReservations() async throws -> [Reservation] {
let calendar = Calendar.current
let now = Date()
let checkIn1 = calendar.date(byAdding: .day, value: 3, to: now)!
let checkOut1 = calendar.date(byAdding: .day, value: 5, to: now)!
let checkIn2 = calendar.date(byAdding: .day, value: 10, to: now)!
let checkOut2 = calendar.date(byAdding: .day, value: 12, to: now)!
let r1 = Reservation(
id: "dummy-1",
hotelName: "부산 해운대 테스트 호텔",
city: "부산",
checkInDate: checkIn1,
checkOutDate: checkOut1
)
let r2 = Reservation(
id: "dummy-2",
hotelName: "서울 강남 테스트 호텔",
city: "서울",
checkInDate: checkIn2,
checkOutDate: checkOut2
)
return [r1, r2]
}
}
실제 API가 준비되면, APIReservationService 구현으로 교체하면 됩니다.
(5) App Intents Extension
App Intent 코드만 만들었다고 해서 시리나 단축어가 그 존재를 바로 알 수는 없습니다.
그래서 시스템 입장에서 보면 “이 앱 안에 어떤 Intent들이 있는지, 어떤 Shortcuts 액션을 노출해야 하는지”를 알려줄 진입점(entry point)가 필요합니다.
그 역할을 하는 게 바로 App Intents Extension입니다. Xcode에서 App Intents Extension 타깃을 하나 만들면 아래와 같은 엔트리 타입이 생성됩니다.
import AppIntents
@main
struct YGAppIntentsExtension: AppIntentsExtension {
}
이 한 줄로 “이 번들은 App Intents Extension이다”라고 시스템에 선언하고, iOS가 이 번들을 로드할 때 내부에 포함된 SaveUpcomingReservationsToCalendarIntent 같은 AppIntent들을 스캔해서 시리 / 단축어 / Spotlight에서 쓸 수 있는 액션 목록으로 인덱싱합니다.
3. App Intent와 Apple Intelligence의 현재 한계
이제 시리에게 우리가 만든 App Intent를 사용할 수 있는지 테스트해봅시다.
“시리야, 여기어때 예약내역 캘린더에 저장해줘”
라고 명령하면
“이름을 뭐라고 할까요?”
“일정을 언제로 설정 할까요?”
질문이 되돌아옵니다.

이런 질문들이 되돌아오는 이유는 우리가 한 질문이 기존 Siri의 “기본 캘린더 이벤트 생성” 플로우로 실행되었기 때문입니다.
Siri는 이 문장을 “앱의 App Intent를 호출할 기회”로 보지 않고 “그냥 캘린더 이벤트를 만들라는 말”로 해석해, 내장 캘린더 기능을 실행한 것입니다.
즉, 우리가 만든 SaveUpcomingReservationsToCalendarIntent 는 전혀 호출되지 않은 상태입니다.
이 부분은 Apple 내부 정책/모델 상태에 가깝기 때문에, 정확한 이유를 알기는 어렵고, 개발자 입장에서 할 수 있는 건 추측 정도입니다.
아래와 같이 3가지 정도 추측을 해 볼 수 있습니다.
- 기기/OS에서 Apple Intelligence가 아직 충분히 활성화되지 않았다.
- 활성화는 되어도, 한국어 + 현재 버전/조건에서 커스텀 App Intent를 자연어로 “자동 매핑”해주는 단계까지는 오지 않았다.
- “예약/캘린더/저장” 같은 키워드에 대해서는 여전히 기본 캘린더 도메인을 우선으로 처리하는 정책이 적용되고 있을 수 있다.
그렇다면 질문을 이렇게 바꿔볼 수 있습니다.
“Apple Intelligence/Siri 자연어 매핑은 우리가 컨트롤하기 어려운 영역이라면, 개발자가 일관적으로 제어할 수 있는 경로는 어디까지일까?”
4. 우리가 지금 할 수 있는 현실적인 전략
현재 App Intent 실행 경로 중에서, 우리가 제어할 수 있는 것은 Shortcuts를 통한 방식입니다. 즉, App Intent를 Shortcuts 액션으로 노출하고, 사용자가 단축어를 통해 실행하도록 만드는 기존 패턴을 그대로 사용하는 것입니다.
(1) App Intent + Shortcuts로 기능을 먼저 안정화
SaveUpcomingReservationsToCalendarIntent를 잘 설계하고- Shortcuts 앱에서 액션등록
- 단축어 이름을 예를 들어
예약내역 숙소 예약 내역들을 캘린더에 저장으로 만들어 등록 해두고 - Siri에게 “여기어때 숙소 예약 예약내역들을 캘린더 저장해줘” 라고 호출

우리가 컨트롤할 수 있는 영역은 여기까지라고 볼 수 있습니다. 이 플로우는 지금도 구현 가능하고, 우리가 통제할 수 있는 흐름입니다.
(2) Apple Intelligence가 매핑 품질을 올려줄 때를 대비해 구조를 준비하기
iOS / Intelligence가 업데이트되면서 App Intent를 자연어로 자동 매핑하는 기능이 강화되면, 우리가 지금처럼 Intent를 잘 정의해 둔 앱은 별도 작업 없이도 “추천/자동 실행 대상”이 될 수 있습니다.
그래서 지금 우리가 할 수 있는 최선은:
- App Intent를 잘 설계해서 시스템에 “행동 인터페이스”를 열어두고
- Shortcuts 기반으로 기능/UX를 먼저 안정화하고
- 추후 Apple Intelligence 매핑이 충분히 열렸을 때 자연어 한 줄이 얼마나 잘 붙는지 보고
phrases,title,description등을 튜닝하는 것 입니다.
5. 정리
지금 우리가 했던 App Intent 작업을 바로 Apple Intelligence가 자유롭게 활용하기에는 아직 이른 상태로 보입니다.
“왜 지금 자연어 한 줄에 자동 매핑이 안 되지?” 라는 질문의 답은 대부분 플랫폼·버전·정책 문제에 가깝고, 현재 시점에서 개발자가 직접 제어할 수 있는 영역은 한정적입니다.
Apple Intelligence는 App Intents 연동 방식이 일부 문서와 샘플 코드로 공개돼 있지만, 제가 직접 확인한 범위에서는 Shortcuts를 거치지 않고
Apple Intelligence가 서드파티 App Intents를 직접 활용하는 공개 사례는 아직 찾기 어려웠습니다.
지금 시점에서 Apple Intelligence가 우리 앱을 대신 움직이게 하고 싶다면, 결국 App Intent를 통해서만 액션을 노출하는 것이 정석적인 루트지만, 이 액션들이 자연어 한 줄에서 곧바로 자동 매핑되어 호출되기를 기대하기에는
아직 플랫폼 성숙도가 충분치 않은 상황이라고 보는 것이 현실적입니다.
그래서 현 시점에서 현실적으로 할 수 있는 일은 다음 두 가지라고 정리할 수 있습니다.
- App Intent를 잘 설계해서, 시스템에 “행동 인터페이스”를 열어 두기
- Shortcuts 기반 플로우를 안정적으로 만들기
이렇게 개발된 환경에서, iOS / Apple Intelligence가 업데이트되면서 자연어 매핑 품질이 개선되고, 한국어 환경에서도 커스텀 App Intent가 더 잘 붙는 시점이 오면, 그때 phrases, title, description 등을 단계적으로 튜닝해 나가는 것이 지금 우리가 준비할 수 있는 가장 현실적인 전략이라고 생각합니다.