[iOS] SwiftData는 모델 클래스의 상속을 지원하지 않는다

Sehee·2025년 2월 5일
0

iOS 개발하기

목록 보기
16/16
post-thumbnail

시작하며,

새로운 앱을 개발하면서, SwiftData를 활용하여 로컬에 데이터를 저장하려고 한다
테이블을 만들기 위해 데이터베이스를 설계하다보니, 상속 구조가 가장 깔끔하게 데이터를 관리할 수 있을 것 같았다
SwiftData에서 상속 구조를 사용할 수 있는지 먼저 찾아보았는데, 결론은 "아직 지원하지 않는다" 였다

관련해서 이야기하기 전, SwiftData에 대해 먼저 알아보자


SwiftData

공식 문서를 참고하여 설명하자면,
애플이 WWDC 2023에서 공개한 SwiftData는 순수한 Swift 코드를 사용하여 앱의 데이터를 모델링 및 유지할 수 있는 경량화된 표현적 API를 제공한다
이를 통해 개발자는 추가 파일이나 도구 없이도 데이터를 선언적으로 모델링하고, SwiftUI와 매끄럽게 통합하여 앱을 개발할 수 있다

SwiftData로 테이블 생성하기

SwiftData로 테이블을 생성할 때에는 @Model이 포함된 일반 Swift 타입을 사용하여 생성할 수 있다
#Unique 등의 명확한 선언을 사용하여 제약 조건을 설정할 수도 있다

@Model
class Book {
	@Attribute(.unique) var title: String
	var author: String?
	var genres: [Genre]
}

SwiftUI와의 통합

SwiftUI 뷰에서 @Query를 사용하면 데이터를 가져올 수 있다
SwiftData와 SwiftUI가 연동되어 기본 데이터를 변경할 경우 뷰에 라이브로 업데이트되므로, 수동으로 새로고침을 해줄 필요가 없어졌다

@Query var books: [Book]
var body: some View {
	List(books) { book in
		NavigationLink(book.title, destination: BookView(book))
	}
}

이외의 내용은 공식 문서를 참고하길 바란다
Swift Data | Apple Developer


SwiftData와 상속?

예를 들면, 전자책과 웹소설의 정보를 저장하는 테이블을 만들고자 한다
이 경우, 전자책과 웹소설이 가지는 공통 속성 (제목, 작가 등)이 있을 것이고, 전자책과 웹소설 각각이 가지는 개별 속성이 있을 것이다

이때 아래 세 가지 구현 방법을 생각해볼 수 있었다

  1. 하나의 테이블에서 타입을 나누기
  2. 전자책과 웹소설 테이블을 각각 따로 만들기
  3. 공통 속성을 가진 부모 테이블을 상속하는 전자책과 웹소설 테이블을 만들기

3번째 상속 구조로 구현하는 경우, 여러 장점이 있었다

  1. 공통 속성을 부모 클래스에서 관리하면 코드의 중복을 줄일 수 있음
  2. 전자책과 웹소설 각각의 속성을 확장하기 편리함
  3. 객체 지향 프로그래밍 관점에서 깔끔하고, 타입별로 명확한 구조가 만들어짐

그래서 SwiftData에서 상속 구조를 만드는 법을 알아보았다
아무리 찾아도 관련 글이 안나오고, 공식 문서에서도 못 찾아서 결국 GPT에게 물어봤다

아직 SwiftData는 모델 클래스의 상속을 지원하지 않는다...
특히 SwiftData에서 부모-자식 관계를 저장/조회하는 것이 어려워 추천하지 않는다고 한다

그럼에도 상속 구조와 비슷하게 구현하는 방법이 있긴 하다


프로토콜

프로토콜을 활용해 공통 속성을 공유하는 방식을 사용할 수 있다
이렇게 하면 모델 간의 코드 중복을 줄이면서도, SwiftData의 기능을 그대로 활용할 수 있다고 한다 (GPT 왈)

설명하는 것보다 아래 예시 코드를 봐보는 것을 추천한다

공통 속성은 프로토콜로 정의

protocol BookProtocol {
    var title: String { get set }
    var author: String? { get set }
    var platform: String? { get set }
    var genres: [Genre] { get set }
    var isWatched: Bool { get set }
}

SwiftData 모델에서 프로토콜 채택

// 웹소설 연재 상태 enum
enum WebNovelStatus: String, Codable {
    case ongoing = "연재중"
    case completed = "완결"
}


// 전자책 - SwiftData 모델
@Model
final class EBook: BookProtocol {
    var title: String
    var author: String?
    var platform: String?
    var genres: [Genre]
    var isWatched: Bool
    var price: Int?
    var publishDate: Date

    init(title: String, 
         author: String? = nil, 
         platform: String? = nil, 
         genres: [Genre] = [], 
         isWatched: Bool = false, 
         price: Int? = nil, 
         publishDate: Date) {
        self.title = title
        self.author = author
        self.platform = platform
        self.genres = genres
        self.isWatched = isWatched
        self.price = price
        self.publishDate = publishDate
    }
}

// 웹소설 - SwiftData 모델
@Model
final class WebNovel: BookProtocol {
    var title: String
    var author: String?
    var platform: String?
    var genres: [Genre]
    var isWatched: Bool
    var totalChapters: Int
    var status: WebNovelStatus

    init(title: String, 
         author: String? = nil, 
         platform: String? = nil, 
         genres: [Genre] = [], 
         isWatched: Bool = false, 
         totalChapters: Int, 
         status: WebNovelStatus) {
        self.title = title
        self.author = author
        self.platform = platform
        self.genres = genres
        self.isWatched = isWatched
        self.totalChapters = totalChapters
        self.status = status
    }
}

프로토콜을 사용하여 공통 데이터 처리하기

이제 BookProtocol을 준수하는 모든 모델을 타입에 관계없이 처리할 수 있다
예를 들어, 전자책과 웹소설 데이터를 한 번에 필터링하고 싶다면 다음처럼 활용할 수 있다

// SwiftData에서 모든 BookProtocol을 준수하는 데이터 가져오기
func fetchAllBooks<T: BookProtocol>(_ type: T.Type) -> [T] {
    let books = try? ModelContext.fetch(T.self)
    return books ?? []
}

// 사용 예시
let allEBooks = fetchAllBooks(EBook.self)
let allWebNovels = fetchAllBooks(WebNovel.self)

마치며,

그런데, 다 찾아놓고 생각해보니 Swift에서 상속 구조를 쓰는 게 맞는지에 대한 의문이 들었다
없는 데엔 이유가 있지 않을까

profile
디자인하는 개발자

0개의 댓글