Apple Intelligence Foundation Models를 iOS 18 타겟 앱에서 사용하는 방법 — @available 우회 패턴

배경

Foundation Models 프레임워크는 iOS 26 전용입니다. 그런데 앱의 minimum deployment target이 iOS 18이라면 @available(iOS 26, *) 분기가 필수입니다. 여기서 한 가지 함정이 있습니다.

문제: @Generable 타입을 stored property로 가질 수 없음

@Generable 매크로로 생성된 타입(NoticeboardParseResult 등)은 @available(iOS 26, *)가 붙어 있습니다. 이를 class/struct의 stored property로 선언하면 iOS 18 타겟에서 링크 에러가 발생합니다.

// ❌ 컴파일 에러 — iOS 18에서 LanguageModelSession 심볼 없음
actor NoticeboardAssistantManager {
    private var session: LanguageModelSession?  // @available 없이 선언 불가
}

해결: Any?로 타입 지우기

// ✅ 실제 코드 (NoticeboardAssistantManager.swift)
actor NoticeboardAssistantManager {
    static let shared = NoticeboardAssistantManager()

    // Any?로 저장 — stored property에 @available 없이 iOS 26 타입 보관
    private var _session: Any?

    @available(iOS 26, *)
    private var session: LanguageModelSession? {
        get { _session as? LanguageModelSession }
        set { _session = newValue }
    }

    var isAIAvailable: Bool {
        if #available(iOS 26, *) {
            return SystemLanguageModel.default.availability == .available
        }
        return false
    }
}

@Generable 구조체 정의

// iOS 26 전용 — @available 필수
@available(iOS 26, *)
@Generable
struct NoticeboardParseResult: Equatable {
    @Guide(description: "알림장에서 추출한 항목 목록")
    var items: [NoticeboardExtractedItem]

    @Guide(description: "알림장 전체 내용을 1~2문장으로 요약")
    var summary: String
}

@available(iOS 26, *)
@Generable
struct NoticeboardExtractedItem: Equatable {
    @Guide(description: "항목 제목")
    var title: String

    @Guide(description: "분류: homework / material / notice / schedule")
    var category: String

    @Guide(description: "마감일 yyyy-MM-dd, 없으면 빈 문자열")
    var dueDate: String
}

세션 초기화와 사용

@available(iOS 26, *)
func parseNoticeboardText(_ text: String) async throws -> AIParseSuccess {
    // lazy 초기화 — 최초 호출 시 생성
    if session == nil {
        session = LanguageModelSession {
            "당신은 초등학교 알림장 분석 전문가입니다. OCR로 추출된 텍스트에서 항목을 추출해 구조화된 데이터로 변환합니다. 한국어로 응답하세요."
        }
    }

    let response = try await session!.respond(
        to: prompt,
        generating: NoticeboardParseResult.self
    )

    // iOS 26 전용 타입 → 버전 독립 앱 모델로 변환
    return AIParseSuccess(
        items: response.content.items.map { ParsedNoticeItem(from: $0) },
        summary: response.content.summary
    )
}

iOS 26 베타 주의사항

// 베타에서 내부 safety 모델 누락 시 DecodingError 발생
// 세션 폐기 후 재전파
do {
    let response = try await session!.respond(to: prompt, generating: NoticeboardParseResult.self)
    return convertToAppModel(response.content)
} catch {
    session = nil  // 세션 리셋
    throw error    // 상위로 재전파
}

교훈

  • Any? stored property 패턴으로 iOS 26 전용 타입을 안전하게 보관
  • actor 사용으로 LanguageModelSession 스레드 안전성 확보
  • iOS 26 전용 타입은 앱 공통 모델로 변환 후 TCA Action에 전달
  • SystemLanguageModel.default.availability 체크로 AI 지원 여부 확인

글쓴이

admin

https://ivent-admin.storyqbe.top/ 를 운영하고 있는 강프로입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다