← 모든 글

iOS 27의 App Intents: 백그라운드, 동기화, Spotlight

App Intents는 iOS 16에서 Shortcuts, Siri, Spotlight를 위한 Apple의 타입 기반 구조화 동작 API로 출시됐습니다. iOS 17은 이를 App Intents 기반 위젯으로 확장했고, iOS 18은 Apple Intelligence의 동작 표면을 위한 계약으로 삼았으며, iOS 26은 Visual Intelligence와 인터랙티브 스니펫으로 밀어넣었습니다. iOS 27은 이 판돈의 모양을 다시 한 번 바꾸는데, 그 변화는 겉치레가 아니라 메커니즘 차원입니다. 이제 인텐트는 30초 백그라운드 제한을 넘어 실행할 수 있고, 엔티티는 사용자의 여러 기기를 오가도 살아남는 정체성을 지닐 수 있으며, 쿼리는 시스템이 요청하면 자신의 Spotlight 색인을 스스로 복구할 수 있습니다. iOS 27이 더한 것은 단맛이 아니라 역량입니다.1

이전의 모든 릴리스는 누가 여러분의 인텐트를 호출할 수 있는지를 넓혔습니다. iOS 27은 호출된 인텐트가 무엇을 할 수 있는지를 넓힙니다. 수천 개의 레코드를 다루는 동기화 인텐트는 예전에는 30초 타이머와 경주를 벌이다 졌지만, 이제는 시스템에 더 긴 활주로를 요청하고 작업하는 동안 진행 상황을 보고합니다. iPhone에서는 한 가지를 의미하고 Mac에서는 다른 것을 의미하던 엔티티가 이제는 양쪽 모두에서 동일한 객체로 해석됩니다. 이 글은 Apple 문서를 기준으로 iOS 27의 표면을 짚어가며, 이 클러스터의 나머지 글과 같은 틀을 따릅니다. 이미 App Intents를 출시한 앱이 각각의 새 역량을 얻기 위해 무엇을 더해야 하는가입니다.

TL;DR

  • LongRunningIntent는 인텐트의 백그라운드 실행 시간을 시스템의 30초 제한 너머로 연장합니다. 작업을 performBackgroundTask(options:operation:)로 감싸고 LongRunningTaskOptions를 전달합니다. 이 프로토콜은 ProgressReportingIntent를 정제(refine)하므로, 진행 상황 보고는 선택이 아니라 필수입니다. Live Activities가 그 진행 상황을 자동으로 렌더링합니다.234
  • SyncableEntityAppEntity가 사용자의 여러 기기에서 일관된 식별자를 지닌다고 선언합니다. 덕분에 시스템은 iPhone, Mac, Watch에서 동일한 객체를 가리킬 수 있습니다(Siri는 이를 사용해 한 기기에서 다른 기기로 대화를 넘깁니다).5
  • IndexedEntityQueryEntityQuery에 Spotlight 재색인 지원을 더합니다. 시스템이 앱의 색인에서 문제를 발견하면, 영향을 받은 엔티티를 다시 기증(re-donate)하도록 쿼리에 요청할 수 있습니다.6
  • AppUnionValueAppUnionValueCasesProviding(@UnionValue 매크로가 생성)은 하나의 파라미터가 여러 개의 서로 다른 엔티티 타입을 받으면서도 제대로 된 선택기 UI와 파라미터 요약을 갖추도록 해줍니다.78
  • OwnershipProvidingEntity, EntityOwnership, EntityCollection은 소유권을 인지하는 확인과 대량 처리 효율을 다루고, RunSystemShortcutIntentIntentExecutionTargets는 위젯이 실행하는 시스템 동작과 어느 프로세스가 인텐트를 실행하는지를 다룹니다.910111213

30초의 벽: LongRunningIntent

백그라운드 실행 제한은 App Intent가 할 수 있는 일에 조용히 드리워진 천장이었습니다. 시스템이 백그라운드에서 인텐트를 수행할 때(사용자가 Siri에게 동기화를 요청하고는 휴대폰을 잠가 주머니에 넣는 경우), 전통적으로 완료까지 대략 30초를 부여합니다.2 물 한 잔을 기록하는 일에는 그것으로도 넉넉합니다. 하지만 라이브러리를 동기화하거나, 온디바이스 추론을 돌리거나, 큰 파일을 처리하는 일에서 30초는 단두대입니다. 시스템이 쓰기 도중에 작업을 죽여버리고, 사용자는 반쯤 끝난 결과를 받게 됩니다.

iOS 27은 LongRunningIntent를 도입합니다. 인텐트가 채택해 시스템에 연장된 백그라운드 시간을 요청하는 프로토콜입니다.2 Apple은 문서에서 사용 사례를 직접 명시합니다. 파일 작업, 데이터 동기화, 머신러닝 추론, 그리고 충분히 큰 데이터셋에 대한 데이터 처리입니다. 선언부는 코드 한 줄을 쓰기도 전에 가장 중요한 제약을 알려줍니다.

protocol LongRunningIntent : ProgressReportingIntent

LongRunningIntentProgressReportingIntent를 정제합니다.2 진행 상황을 보고하지 않고서는 이 장시간 실행 프로토콜을 채택할 수 없으며, 이는 설계상 의도된 것입니다. 연장된 실행 시간은 시스템이 조건부로 부여하는 특권이고, 그 조건은 지금 얼마나 진행됐는지를 계속 알려주는 것입니다. 보고를 멈추면 시스템은 연장을 철회하고 작업을 일찍 끝낼 수 있습니다.3

작업은 performBackgroundTask(options:operation:) 안에 들어갑니다.

@discardableResult
func performBackgroundTask<T>(
    options: LongRunningTaskOptions = [],
    operation: @escaping () async throws -> T
) async throws -> T

이 메서드는 인텐트의 perform() 본문에서 호출하고, 비용이 큰 코드를 operation 클로저에 넣습니다. 메서드는 제한을 부과하는 플랫폼에서 표준 30초 제한 너머로 실행 시간을 자동으로 연장합니다. 별도의 백그라운드 작업을 시작하거나 UIBackgroundTaskIdentifier를 직접 관리하지 않습니다.3 라이브러리 동기화 인텐트는 이렇게 생겼습니다.

import AppIntents

struct SyncLibraryIntent: LongRunningIntent {
    static var title: LocalizedStringResource = "Sync Library"

    func perform() async throws -> some IntentResult {
        try await performBackgroundTask(options: []) {
            let records = try await server.fetchPendingRecords()
            for (offset, record) in records.enumerated() {
                try await store.apply(record)
                progress.completedUnitCount = Int64(offset + 1)
                progress.totalUnitCount = Int64(records.count)
            }
            return ()
        }
        return .result()
    }
}

튜토리얼이 건너뛰는 두 가지는 설명할 만합니다.

progress 프로퍼티는 텔레메트리가 아니라 계약입니다. Apple은 분명히 말합니다. 작업이 실행되는 동안 ProgressReportingIntent 적합성이 제공하는 Progress를 정기적으로 갱신하라고, 그렇게 하지 않으면 시스템이 실행 시간 연장을 취소하고 작업을 조기에 끝낼 수 있다고요.3 일반 인텐트에서 진행 상황 보고는 있으면 좋은 것입니다. LongRunningIntent에서는 연장을 살려두는 심장박동입니다.

LongRunningTaskOptions는 리소스 요구사항을 선언합니다. 이 옵션 값(기본값이 []OptionSet 스타일 구조체)은 작업에 필요한 추가 리소스를 시스템에 알리고, 시스템은 이를 부여할 실행 시간을 정할 때 반영합니다.4 빈 집합이 일반적인 경우입니다. 기본 프로필 이상이 필요한 작업에서야 명시적 옵션에 손을 댑니다.

30초를 넘겨 살아남는다는 것 너머의 보상은 이렇습니다. Live Activities가 진행 상황을 공짜로 렌더링합니다. 문서는 Live Activities가 performBackgroundTask에서 자동으로 받은 정보를 사용해 인텐트 작업의 진행 상황을 표시하며, 여러분의 코드가 보고한 값에서 제목과 부제목, 진행 막대를 그려낸다고 명시합니다.3 음성으로 시작한 긴 동기화는, 여러분이 Live Activity 뷰를 한 줄도 만들지 않았는데도 잠금 화면에 실시간 진행 막대로 나타납니다. 인텐트가 보고하면, 시스템이 렌더링합니다.

Watch on Apple Developer ↗
Apple은 LongRunningIntent를 통해 사진 업로드 인텐트가 30초 제한을 넘어 살아남는 모습을 시연하며, Live Activity에 정지 버튼을 두어 사람이 언제든 취소할 수 있게 합니다.

세션 345에서 Apple은 30초 창 안에서 계속 죽어버리던 사진 업로드라는 실제 실패 사례를 두고 LongRunningIntent를 시연하며, 시스템이 백그라운드 작업의 수명 주기를 관리하면서 진행 상황과 취소 컨트롤을 Live Activity로 드러내는 모습을 보여줍니다.14

일관된 정체성: SyncableEntity

AppEntity에는 id가 있습니다. 단일 기기에서 그 식별자는 앱 안에서만 고유하면 됩니다. 문제는 사용자가 기기를 둘 이상 소유하는 순간 시작되는데, Apple 생태계에서는 그것이 기본값입니다. 사용자가 iPhone에서 Siri와 이야기 나눈 “Project Atlas”는, Mac에서 그 대화를 다시 이어받을 때 알아볼 수 있게 동일한 “Project Atlas”여야 합니다. iPhone의 로컬 식별자가 Mac의 것과 다르다면, 시스템에는 서로 무관한 두 객체가 있을 뿐 둘을 연결할 방법이 없습니다.

SyncableEntity가 iOS 27의 답입니다.5

protocol SyncableEntity : AppEntity

이를 채택하면 여러분의 엔티티 식별자가 기기 간에 동일하다고 선언하는 것입니다. 이 프로토콜의 존재는, 시스템이 한 기기에서 다른 기기로 여러분의 엔티티를 일관되게 가리킬 수 있다고 알려줍니다. Apple은 구체적인 보상을 제시합니다. Siri는 이 역량을 사용해 한 기기에서 다른 기기로 대화를 옮깁니다.5

채택 비용은 전적으로 식별자가 어디서 오는지에 달려 있습니다. 엔티티가 이미 안정적인 기기 간 식별자(서버가 발급한 UUID, iCloud 레코드 이름)를 쓰고 있다면, 다른 변경 없이 SyncableEntity를 채택하면 됩니다. 여러분이 이미 저장하고 있는 값이 곧 시스템이 필요로 하는 값이기 때문입니다.5

import AppIntents

struct ProjectEntity: SyncableEntity {
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Project"
    static var defaultQuery = ProjectQuery()

    // A UUID issued by the backend and identical on every device.
    var id: UUID

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }

    @Property(title: "Name") var name: String
}

함정은 각 기기에서 새로운 로컬 식별자를 찍어내는 앱(자동 증가 행 ID, 설치별 UUID)입니다. 그런 식별자는 로컬에서는 고유하지만 기기 간에는 무의미합니다. 그 경우에 대한 Apple의 지침은 이렇습니다. 프로토콜을 채택하되 실제로 안정적인 값에서 정체성을 끌어내, 시스템이 닻을 내릴 견고한 무언가를 갖게 하라는 것입니다.5 동기화는 여러 팀이 저마다의 iCloud 배관으로 일일이 손수 풀어오던 어려운 문제였습니다. SyncableEntity는 기기 간 정체성 선언을, Siri와 시스템의 나머지가 그것을 바탕으로 행동할 수 있는 프레임워크 안으로 옮겨놓습니다.

스스로 치유하는 검색: IndexedEntityQuery

iOS 16은 IndexedEntity 인스턴스를 Spotlight에 기증해 개별 엔티티가 검색 가능하도록 해줬습니다. 빈틈은 복구였습니다. 색인은 어긋나고, 손상되고, 마이그레이션 후 뒤처지는데, iOS 27 이전까지 시스템의 유일한 수단은 앱의 CSSearchableIndex 델리게이트나 CSImportExtension에 기대는 것뿐이었습니다.

IndexedEntityQuery는 시스템이 여러분의 쿼리에게 재색인을 요청할 수 있게 하여 이 빈틈을 메웁니다.6

protocol IndexedEntityQuery : EntityQuery where Self.Entity : IndexedEntity

where 절은 전제 조건입니다. 쿼리의 엔티티는 IndexedEntity를 준수해야 하는데, 재색인은 애초에 Spotlight에 기증하는 엔티티에 대해서만 의미가 있기 때문입니다.6 시스템이 앱의 색인에서 문제를 만나면, 쿼리 타입이 이를 채택한 경우 이 프로토콜의 메서드를 호출합니다. 채택하지 않았다면, Spotlight는 계속해서 여러분의 CSSearchableIndex 객체(또는 엔티티를 그 타입에 연관시켜 기증했다면 CSImportExtension)에게 그 일을 해달라고 요청합니다.6 여러분은 요청받은 엔티티를 가져와 선호하는 검색 색인을 통해 다시 기증하도록 메서드를 구현합니다.

import AppIntents
import CoreSpotlight

struct PhotoQuery: IndexedEntityQuery {
    func entities(for identifiers: [Photo.ID]) async throws -> [Photo] {
        try await library.photos(matching: identifiers)
    }

    func suggestedEntities() async throws -> [Photo] {
        try await library.recentPhotos(limit: 20)
    }

    // Called by the system during reindexing. Fetch the requested
    // entities and donate them again to Spotlight.
    func entities(matching string: String) async throws -> [Photo] {
        try await library.photos(matchingText: string)
    }
}

가치는 운영에 있습니다. IndexedEntityQuery를 제대로 구현한 앱은 Spotlight의 복구 루프에 참여합니다. 시스템이 색인이 잘못됐음을 알아채면 앱이 요청에 따라 새 데이터를 공급하고, 사용자는 다음 전체 재기증 때까지 검색 결과를 슬그머니 잃지 않습니다. 이 클러스터의 App Intents 기초 글은 항목을 검색 가능하게 만들기 위한 순수한 IndexedEntity 노출을 다뤘습니다. IndexedEntityQuery는 그 위에 얹히는 유지보수 계층입니다.

하나의 파라미터, 여러 타입: AppUnionValue

현실의 많은 인텐트는 정당하게 여러 타입 중 하나인 파라미터를 받습니다. “이것을 공유”에서 “이것”이 사진일 수도, 문서일 수도, 링크일 수도 있는 것처럼요. iOS 27 이전의 우회책은 보기 흉했습니다. 타입별로 인텐트를 따로 두거나, 문자열 판별자에 더해 선택기 UI가 말끔히 렌더링할 수 없는 선택적 파라미터를 두는 식이었죠.

iOS 27은 타입이 지정된 합집합 파라미터를 위해 AppUnionValue를 추가합니다.7

protocol AppUnionValue : TypeDisplayRepresentable

이 프로토콜을 준수하는 합집합 값은 풍부한 메타데이터와 함께 Shortcuts 파라미터로 동작하므로, 시스템은 멤버 타입들에 걸쳐 적절한 선택기와 합리적인 파라미터 요약을 제시할 수 있습니다.7 적합성을 손으로 작성하지는 않습니다. @UnionValue 매크로가 그것을 생성하고, 같은 매크로가 AppUnionValueCasesProviding을 준수하는 중첩 Cases 열거형도 생성합니다.78

protocol AppUnionValueCasesProviding : AppEnum

AppUnionValueCasesProviding은 매크로가 내보내는 Cases 열거형이 자동으로 준수합니다.8 이 열거형은 케이스 열거형을 합집합 값 타입으로 다시 이어주고, AppEnum 적합성을 통해 메타데이터를 상속받는데, 이것이 선택기에서 각 케이스에 표시 이름을 부여하는 요소입니다.8 실제로는 합집합을 작성하고 거기에 주석을 답니다.

import AppIntents

@UnionValue
enum ShareTarget {
    case photo(PhotoEntity)
    case document(DocumentEntity)
    case link(URL)
}

struct ShareIntent: AppIntent {
    static var title: LocalizedStringResource = "Share Item"

    @Parameter(title: "Item")
    var target: ShareTarget

    func perform() async throws -> some IntentResult {
        // Switch over the concrete case and act accordingly.
        return .result()
    }
}

@UnionValue 매크로가 AppUnionValueAppUnionValueCasesProviding 적합성을 처리합니다. 기본값을 넘어선 맞춤 메타데이터가 필요하다면 확장에서 프로토콜 요구사항을 구현하면 됩니다.7 하나의 파라미터, 세 개의 유효한 타입, 그리고 셋 모두를 보여줄 줄 아는 선택기입니다.

소유권과 효율

서로 다른 두 가지 iOS 27 관심사가 이 절을 함께 쓰는 까닭은, 둘 다 시스템이 여러분의 데이터에 경솔하게 손대는 것을 막아주기 때문입니다. 소유권을 인지하는 확인, 그리고 대량 작업의 효율입니다.

파괴적 동작 확인하기: OwnershipProvidingEntity

앱이 엔티티를 인텐트로 넘기고 결과로 돌려줄 때, Apple Intelligence, Siri, 맞춤 단축어는 앱 간에 그 엔티티에 대해 동작할 수 있습니다. 파괴적이거나 민감한 동작(엔티티 삭제, 공유 엔티티 수정)에 대해서는 올바른 맥락을 담은 확인을 받고 싶을 것입니다. OwnershipProvidingEntity가 이를 제공합니다.9

protocol OwnershipProvidingEntity : AppEntity

엔티티를 여기에 준수시키면, 인텐트가 공유되거나 공개적으로 접근 가능한 엔티티에 동작할 때 시스템이 대화창에 적절한 맥락을 담아 확인을 요청합니다.9 소유권 상태 자체는 EntityOwnership 값으로, 단일 상태를 지정하거나 OptionSet으로 여러 상태를 결합하는 플래그 기반 구조체입니다.10

import AppIntents

struct AlbumEntity: OwnershipProvidingEntity {
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Album"
    static var defaultQuery = AlbumQuery()

    var id: UUID
    var isSharedWithFamily: Bool
    var isPublished: Bool

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }

    @Property(title: "Name") var name: String

    // Reflect how the user has shared this album so the system can
    // calibrate its confirmation dialog.
    var ownership: EntityOwnership {
        var state: EntityOwnership = []
        if isSharedWithFamily || isPublished {
            state = .shared
        }
        return state
    }
}

이 메커니즘은 공유 콘텐츠가 있는 앱에서 가장 중요합니다. 게시했거나 가족과 공유한 사진 앨범은 비공개 앨범보다 더 조심스러운 확인을 만들어야 하며, OwnershipProvidingEntity는 엔티티가 어느 쪽인지를 시스템에 알려주는 수단입니다.9

메모리 부담 없는 대량 처리: EntityCollection

엔티티를 해석하는 일은 공짜가 아닙니다. 인텐트가 수백 개의 엔티티를 파라미터로 받을 때, 파라미터 해석 과정에서 모든 식별자를 완전한 인스턴스로 강제로 해석시키면 좋지 않은 순간에 상당한 시간과 메모리를 잡아먹을 수 있습니다. EntityCollection이 해법입니다.11

struct EntityCollection<Entity> where Entity : AppEntity

이 컬렉션은 처음에 각 엔티티의 식별자만 저장하고, 필요하면 나중에 완전한 인스턴스를 가져올 수 있는 선택지를 제공합니다.11 식별자를 많이 들고 있을 때는 변수 타입으로 쓰고, 인텐트가 큰 집합에 대해 동작할 때는 파라미터 타입으로 씁니다.

import AppIntents

struct DisableNotificationsIntent: AppIntent {
    static var title: LocalizedStringResource = "Disable Notifications"

    // Hundreds of conversations resolve lazily, not all at once.
    @Parameter(title: "Conversations")
    var conversations: EntityCollection<ConversationEntity>

    func perform() async throws -> some IntentResult {
        return .result()
    }
}

수백 개의 엔티티를 담은 파라미터에서 식별자별 해석을 건너뛰면, 사용자가 동작이 시작되기를 기다리는 바로 그 순간에 시간과 메모리를 아낍니다.11

인텐트가 실행되는 곳: RunSystemShortcutIntentIntentExecutionTargets

두 개의 작은 iOS 27 추가 기능이 이 표면을 마무리합니다. RunSystemShortcutIntent는 위젯 전용 인텐트로, 위젯 버튼에서 다른 앱을 실행하거나 App Shortcut, 맞춤 단축어, 시스템 동작을 수행하기 위한 것입니다.12

struct RunSystemShortcutIntent

이것은 오직 시스템 단축어 이니셜라이저로 Button을 초기화하고 그 버튼을 위젯에 배치하는 데만 사용합니다. 그 맥락 밖에서는 아무 쓸모가 없습니다.12 사용자가 위젯을 구성할 때 버튼의 동작을 선택하고, 인텐트는 구성 UI에 필요한 메타데이터를 시스템에 공급합니다. 이것이 여러분의 위젯에 단축어의 동작, 파라미터, 구현에 대한 접근 권한을 넘겨주지는 않습니다. 선택된 단축어가 입력을 요청해야 한다면, 시스템은 그것을 수행하기 위해 Shortcuts 앱을 열 수 있습니다.12

IntentExecutionTargets는 인텐트와 엔티티를 Swift 패키지나 프레임워크를 통해 앱, 위젯 익스텐션, App Intents 익스텐션에 걸쳐 공유하는 순간 떠오르는 질문에 답합니다. 어느 프로세스가 인텐트를 실행하는가입니다.13

struct IntentExecutionTargets

기본적으로 시스템은 사용 가능한 아무 대상이나 써서 인텐트나 엔티티 쿼리를 수행합니다.13 IntentExecutionTargets로 이를 제약합니다. Apple의 예시는 브라우저입니다. 북마크 추가는 앱이 보이지 않을 때도 일어날 수 있으니 App Intents 익스텐션으로 충분하지만, 새 탭 열기는 앱이 보일 때만 의미가 있으므로 앱 자신의 프로세스가 필요합니다.13 여러분이 유효한 대상을 선언하면 시스템이 그 제약을 지킵니다.

채택 경로

이미 App Intents를 출시한 앱은 iOS 27 역량을 점진적으로 더합니다. 그중 어느 것도 핵심 모델을 다시 쓰지 않습니다.

  1. 가장 느린 인텐트를 찾으세요. 파일 I/O, 동기화, 온디바이스 추론, 대용량 데이터 처리를 하는 인텐트라면 모두 LongRunningIntent 후보입니다. 프로토콜을 채택하고, 작업을 performBackgroundTask(options:operation:)로 옮기고, 내내 progress를 보고하세요. 30초 너머의 실행 시간과 공짜 Live Activities 진행 표시를 얻습니다.23
  2. 엔티티 식별자를 점검하세요. 이미 기기 간에 안정적이라면(서버 UUID, iCloud 레코드 이름), 해당 엔티티를 SyncableEntity에 준수시키고 출시하세요. 기기별이라면 정체성부터 고친 다음 준수시키세요.5
  3. 엔티티가 IndexedEntity인 쿼리에 IndexedEntityQuery를 더하세요. 순수하게 더하기만 하는 작업입니다. 메서드는 시스템이 재색인을 필요로 할 때만 호출되고, 검색 결과는 색인이 어긋나는 동안에도 올바르게 유지됩니다.6
  4. @UnionValue로 여러 타입 파라미터를 합치세요. 인텐트를 따로 두거나 판별자 문자열로 합집합을 흉내 냈던 곳마다, 매크로가 깔끔한 단일 파라미터를 줍니다.7
  5. 공유 엔티티를 OwnershipProvidingEntity로 표시하고, 큰 집합 파라미터를 EntityCollection으로 바꾸세요. 앞엣것은 확인 안전성을, 뒤엣것은 해석 성능을 개선합니다.911

자주 묻는 질문

LongRunningIntent는 백그라운드에서 얼마나 오래 실행할 수 있나요?

Apple은 끌어올리는 바닥을 문서화할 뿐, 고정된 천장을 명시하지는 않습니다. 시스템은 전통적으로 백그라운드 작업에 완료까지 대략 30초를 주며, LongRunningIntent는(performBackgroundTask(options:operation:)를 통해) 제한을 부과하는 플랫폼에서 그 창을 표준 제한 너머로 자동으로 연장합니다.23 연장은 조건부입니다. ProgressReportingIntent 적합성의 Progress를 계속 갱신해야 하고, 멈추면 시스템이 연장을 취소하고 작업을 일찍 끝낼 수 있습니다.3 진행 상황 보고를 추가 실행 시간의 대가로 여기세요.

LongRunningIntent를 쓰려면 진행 상황을 보고해야 하나요?

네. LongRunningIntentprotocol LongRunningIntent : ProgressReportingIntent로 선언되므로, 이를 채택하면 ProgressReportingIntent 적합성과 그 Progress가 필요합니다.2 컴파일러를 만족시키는 것을 넘어, 정기적인 진행 상황 갱신은 백그라운드 실행 시간 연장을 살려두고, Live Activities가 performBackgroundTask에서 자동으로 렌더링하는 제목, 부제목, 진행 막대에 데이터를 공급합니다.3

SyncableEntity는 런타임에서 실제로 무엇을 바꾸나요?

여러분의 엔티티 식별자가 사용자의 여러 기기에서 동일하다고 선언하여, 시스템이 그 객체를 기기별로 분리된 객체가 아니라 어디서나 하나의 엔티티로 다루게 합니다.5 Apple이 명시하는 구체적 역량은 이렇습니다. Siri는 그 엔티티에 관한 대화를 한 기기에서 다른 기기로 옮길 수 있습니다. 식별자가 이미 기기 간에 안정적이라면 다른 변경 없이 프로토콜을 채택하고, 기기별이라면 먼저 안정적인 값에 정체성을 다시 닻 내립니다.5

시스템은 언제 IndexedEntityQuery를 호출하나요?

앱의 Spotlight 색인에서 문제를 만나고, 여러분의 쿼리 타입이 IndexedEntityQuery를 채택했을 때입니다(그 엔티티가 IndexedEntity를 준수한 상태로).6 시스템은 이 프로토콜의 메서드를 호출해 영향받은 엔티티를 가져와 Spotlight에 다시 기증하게 합니다. 쿼리가 프로토콜을 채택하지 않았다면, Spotlight는 여러분의 CSSearchableIndex 객체에, 혹은 그 타입을 통해 기증했다면 CSImportExtension에 요청하는 것으로 되돌아갑니다.6

왜 단순한 엔티티 배열 대신 EntityCollection을 쓰나요?

EntityCollection<Entity>는 처음에 각 엔티티의 식별자만 저장하고 필요하면 나중에 완전한 인스턴스를 가져옵니다.11 인텐트 파라미터로서, 파라미터 해석 과정에서 시스템이 모든 식별자를 완전한 인스턴스로 강제 해석하는 것을 막는데, 수백 개의 엔티티를 담은 파라미터에서는 이것이 잠재적으로 중요한 순간에 시간과 메모리를 아낍니다.11 단순한 [Entity] 배열은 모든 것을 미리(eager) 해석합니다.

RunSystemShortcutIntent을 위젯 밖에서 쓸 수 있나요?

아니요. 이것은 오직 시스템 단축어 이니셜라이저로 Button을 초기화해 위젯에 배치하기 위해 존재하며, 다른 맥락에서는 아무 기능도 제공하지 않습니다.12 위젯 구성 UI를 위한 메타데이터를 드러내고 사용자가 선택한 동작을 나타낼 뿐, 여러분의 위젯이나 앱에 그 바탕이 되는 단축어의 동작, 파라미터, 구현에 대한 접근 권한을 주지 않습니다.12

전체 Apple 생태계 클러스터: 타입 기반 App Intents, iOS 26 추가 기능, MCP 도구를 상대로 한 라우팅 문제, Foundation Models, 새로운 Foundation Models 도구 호출 제어, 런타임 대 도구 체계 LLM 구분, 세 가지 표면, 단일 진실 공급원 패턴, 앱과 나란히 두는 MCP 서버, Live Activities, watchOS 런타임, SwiftUI 내부 구조, SwiftData 스키마 규율, Liquid Glass 패턴, 멀티플랫폼 배포, 플랫폼 매트릭스, Vision 프레임워크, @Observable 내부 구조, 플랫폼으로서의 접근성을 다룹니다. 허브는 Apple 생태계 시리즈에 있습니다. AI 에이전트와 함께하는 iOS의 더 넓은 맥락은 iOS 에이전트 개발 가이드를 참고하세요.

참고 자료


  1. Apple Developer Documentation: App Intents. AppIntent, AppEntity, 쿼리, 파라미터, 그리고 iOS 27 추가 기능을 다루는 프레임워크 레퍼런스. 

  2. Apple Developer Documentation: LongRunningIntent (iOS 27.0 beta). “장시간 실행 작업을 수행하는 앱 인텐트의 백그라운드 실행 시간을 연장하기 위해 사용하는 인터페이스.” protocol LongRunningIntent : ProgressReportingIntent로 선언됨. 시스템은 전통적으로 백그라운드 작업에 최대 30초를 부여함. 

  3. Apple Developer Documentation: performBackgroundTask(options:operation:) (iOS 27.0 beta). 표준 30초 제한 너머로 연장된 시간을 가지고 백그라운드에서 작업을 실행함. 정기적인 진행 상황 갱신이 필요하며, 그렇지 않으면 시스템이 연장을 취소할 수 있음. Live Activities가 진행 상황을 자동으로 렌더링함. 

  4. Apple Developer Documentation: LongRunningTaskOptions (iOS 27.0 beta). 장시간 실행 작업을 구성하기 위한 옵션. 추가 리소스 요구사항을 선언하며, performBackgroundTask(options:operation:)에 전달됨. 

  5. Apple Developer Documentation: SyncableEntity (iOS 27.0 beta). “엔티티가 기기 간에 일관된 식별자를 가짐을 나타내는 인터페이스.” protocol SyncableEntity : AppEntity로 선언됨. Siri가 이를 사용해 기기 간에 대화를 옮김. 

  6. Apple Developer Documentation: IndexedEntityQuery (iOS 27.0 beta). “엔티티 쿼리에 Spotlight 재색인 지원을 더하는 인터페이스.” protocol IndexedEntityQuery : EntityQuery where Self.Entity : IndexedEntity로 선언됨. 

  7. Apple Developer Documentation: AppUnionValue (iOS 27.0 beta). “합집합 값에 명목적 타입 정체성과 메타데이터를 제공하는 프로토콜.” protocol AppUnionValue : TypeDisplayRepresentable로 선언됨. 적합성은 @UnionValue 매크로가 생성함. 

  8. Apple Developer Documentation: AppUnionValueCasesProviding (iOS 27.0 beta). protocol AppUnionValueCasesProviding : AppEnum으로 선언됨. @UnionValue 매크로가 생성하는 Cases 열거형이 자동으로 준수함. 

  9. Apple Developer Documentation: OwnershipProvidingEntity (iOS 27.0 beta). “앱 엔티티에 대한 소유권과 공유 맥락을 시스템에 제공하는 타입.” protocol OwnershipProvidingEntity : AppEntity로 선언됨. 공유되거나 공개적으로 접근 가능한 엔티티에 대해 확인을 요청함. 

  10. Apple Developer Documentation: EntityOwnership (iOS 27.0 beta). “앱 엔티티의 소유권 및 공유 특성을 나타내는 타입.” struct EntityOwnership로 선언됨. 플래그 기반이며 OptionSet으로 결합 가능. 

  11. Apple Developer Documentation: EntityCollection (iOS 27.0 beta). “대량의 엔티티가 관여하는 작업의 효율을 높이기 위해 사용하는 엔티티 식별자 배열.” struct EntityCollection<Entity> where Entity : AppEntity로 선언됨. 처음에는 식별자를 저장하고 완전한 인스턴스는 지연(lazy) 해석함. 

  12. Apple Developer Documentation: RunSystemShortcutIntent (iOS 27.0 beta). “다른 앱을 열거나 App Shortcut, 맞춤 단축어, 시스템 동작을 수행하기 위해 위젯에서 사용하는 앱 인텐트.” struct RunSystemShortcutIntent로 선언됨. 위젯 Button을 초기화하는 데만 사용 가능. 

  13. Apple Developer Documentation: IntentExecutionTargets (iOS 27.0 beta). “어느 프로세스가 인텐트나 엔티티 쿼리를 수행하는지 기술하는 옵션 집합.” struct IntentExecutionTargets로 선언됨. 실행을 앱, App Intents 익스텐션, 또는 사용 가능한 아무 대상으로 제약함. 

  14. Apple, WWDC26 세션 345, “Discover new capabilities in the App Intents framework.” developer.apple.com/videos/play/wwdc2026/345. Apple은 30초 제한 안에서 계속 실패하던 사진 업로드 인텐트를 LongRunningIntent가 해결하는 모습을 시연하며, 프레임워크가 백그라운드 작업의 수명 주기를 관리하고 진행 상황과 정지 컨트롤을 Live Activity로 드러내는 과정을 보여줍니다. 

관련 게시물

iOS 27의 Foundation Models: 도구 호출 제어

iOS 27은 온디바이스 모델이 도구를 사용하는 방식을 제어하는 GenerationOptions.ToolCallingMode와 함께 기본 제공 Vision 도구인 OCRTool과 BarcodeReaderTool을 추…

10 분 소요

iOS 26의 App Intents 2.0: Visual Intelligence, 인터랙티브 스니펫, 그리고 Deferred Properties

iOS 26은 Visual Intelligence를 위한 IntentValueQuery, 비동기 값을 위한 @DeferredProperty, 인터랙티브 스니펫, 엔티티 뷰 어노테이션으로 App Intents를 확장했…

7 분 소요

The Robots Are Taking Exams in My Search Console

First-party GSC data: 91% of 3.8M impressions fail a human-query filter. Exam questions, pasted errors, and agent sweeps…

10 분 소요