배경
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 지원 여부 확인