SwiftData를 사용해보자! 1탄

Explanation

내친김에 바로 작성하는 다음 포스팅!
이번 포스팅은 바로 간단하게 바로 사용해보는 SwiftData 1탄 입니다!

SwiftData는 Swift 코드로 직접 데이터를 모델링하고 다양한 모델 작업을 수행할 수 있는 프레임워크랍니다.
어제 글에서 SwiftUI가 최신? 프레임워크라 아직 구현되지 않은 많은 것들이 있다고 했었는데,
SwiftData는 레알 최신 기술이랍니다.

WWDC23에서 공개되었으니 아직 1년도 되지 않았네요?!

iOS 17.0+, macOS 14.0+ 에서 사용할 수 있기 때문에, 아마도 iOS나 macOS 애플리케이션을 개발하는 현업?에서는 아직 많이 사용하지는 못하려나 싶네요?. 잘 모르지만 아마 장기적으로 SQLite나 CoreData를 대체할 수 있는 프레임워크로 개발되지 않을까 싶어요.

(사실 저는, SQLite도 CoreData도 사용해 본 적이 없어서 잘 모릅니다..)

하지만 저는 가볍게 진행하는 개인 프로젝트이기 때문에 고민없이 바로 SwiftData를 사용하였답니다.

(SwiftData가 상대적으로 쉬워 보여서 선택한 건 안비밀)

그렇게 오늘은 짧게나마, 가볍게 사용해본 SwiftData에 대해서 적어보려 합니다!

앞서 이야기 했듯, 아직 공부를 한지 몇개월 되지 않아서 글에 잘못된 부분이 있을 수 있습니다!!
그리고 작성된 모든 코드는 https://github.com/falsy/blog-post-example/tree/master/macOS-project/SwiftDataEx에서 확인하실 수 있습니다.

1. Model 정의하기

저는 예시로 간단하게 Post와 Comment라는 두개의 모델을 만들었어요.

모델에 사용된 매크로들은 천천히 하나씩 이야기하기로 하고 일단 바로 ModelContainer를 추가해서 사용해볼게요.

2. Model Container 추가

여기서 보면 Post와 Comment 라는 두개의 모델을 추가했는데, ModelContainer에는 Post만 등록하였죠?(Comment 모델도 같이 등록한다고 해서 딱히 오류가 나거나 하는 건 아니에요.) SwiftData는 어느 정도 이 모델간의 관계를 자동으로 인식하는 거 같아요. 그래서 Post만 등록해도 Comment도 자동으로 인식을 한답니다.

‘isStoredInMemoryOnly’ 속성은 이름 그대로 실제 스토리지에 저장할지 아니면 인메모리 상에서만 저장할 지 설정하는 거에요. 일단 개발 단계에서는 인메모리만 사용하는 편이 좋은 거 같아요.

확실하지는 않은데, 제가 짧게 찾아본 바로는 SwiftData에 코드로 생성한 모델과 데이터를 전체를 깔끔하게 싹 지워주는 방법을 아직 딱하고 제공하고 있는 거 같진 않았어요..
그래서 초기화가 필요하면 별도에 초기화할 수 있는 반복문 코드를 만들어서 지우는 거 같아요.

3. SwiftData CRUD

짜잔, 엄청 간단하죠?!
아까 ContentView에서 등록한 ModelContainer를 이렇게 ‘@Environment(\.modelContext) private var modelContext’를 사용해서 insert, delete, save 기능을 수행할 수 있답니다.

그리고 모델을 읽는 방법은 ‘@Query private var posts: [Post]’ 이렇게 ‘Query’ 매크로를 사용해서 뚝딱하고 불러올 수 있답니다.

Query에 다양한 기능들은 아래에서 조금 더 알아볼게요.

4. 모델 매크로 알아보기

일단 모델에서 사용한 매크로를 중심으로 조금씩 이야기 해볼게요.

‘@Attribute’ 이름 그대로 속성 매크로네요. ‘.unique’ 값을 주면 저 프로퍼티의 값이 저 모델에 고유함음 보장해줘요. 음.. 그러니까, 지금 위 코드는 id라는 프로퍼티에 UUID()를 생성해서 id 값이 어느정도 유니크함이 보장되지만, 만약에.

이렇게 title 프로퍼티가 .unique 로 설정되어 있고 동일한 title 값의 Post가 추가(insert) 된다면 새로운 Post가 생기지 않고, 기존의 동일한 title 값이 있는 Post 데이터가 변경이 된답니다. DBMS를 사용해보셨다면 UPSERT를 생각하시면 이해가 편할 거 같아요.

만약 위 코드처럼 id 프로퍼티와 title 프로퍼티 두개가 .unique로 등록되어 있다면, 새로 추가하려고 하는 모델이 두 프로퍼티 중에 하나의 값만 같아도 모두 업데이트를 수행합니다.

위 코드는 간단한 예시인데요, 위 코드를 실행해서 ‘UPSERT 글 추가’ 버튼을 누르면 ‘insert’를 수행하지만, .unique한 id 값을 동일하게 입력하기 때문에 글이 추가되지 않고 계속 업데이트 되는 것을 확인하실 수 있습니다.

그리고 @Attribute 매크로에는 아래와 같은 옵션들이 있답니다.
(저는 .unique 말고는 아직 사용해보지 못했네요.)

allowsCloudEncryption: 속성 값을 암호화된 형식으로 저장합니다.
externalStorage: 모델 저장소에 인접한 이진 데이터로 속성 값을 저장합니다.
preserveValueOnDeletion: 컨텍스트가 소유 모델을 삭제할 때 영구 기록에 속성 값을 유지합니다.
spotlight: Spotlight 검색 결과에 나타날 수 있도록 속성 값을 인덱싱합니다.
unique: 동일한 유형의 모든 모델에서 속성 값이 고유한지 확인합니다.

참고: https://developer.apple.com/documentation/swiftdata/schema/attribute/option

다음으로 ‘@Relationship’ 역시 이름 그대로 모델간의 관계를 정의해주는 매크로랍니다.

위에 사용된 ‘deleteRule’는 삭제규칙? 인데요. ‘.cascade’는 관련된 모델을 모두 삭제하는 규칙이에요.
그러니까 위 코드에서는 Post가 삭제되면 해당 Post의 comments 프로퍼티에 해당하는 Comment 모델들도 삭제 되겠지요??

하지만.. 정확하게 왠지 모르겠는데, ‘.cascade’는 아직 동작하지 않는 거 같아요…

모든 옵션은 아래와 같습니다.
cascade: 관련 모델을 삭제하는 규칙입니다.
deny: 다른 모델에 대한 참조가 하나 이상 포함되어 있기 때문에 모델 삭제를 방지하는 규칙입니다.
noAction: 관련 모델을 변경하지 않는 규칙입니다.
nullify: 삭제된 모델에 대한 관련 모델의 참조를 무효화하는 규칙입니다.

참고: https://developer.apple.com/documentation/swiftdata/schema/relationship/deleterule-swift.enum

다음으로 ‘inverse: \Comment.post’는 Post에서 Comment로 접근하는게 아닌 Comment에서 Post로 접근할 수 있도록 그 관계를 설정해주는 거에요.

마지막으로 ‘@Transient’ 해당 프로퍼티가 스토리지에는 저장되지 않게 해주는 매크로랍니다.
간단하게 예를 들면 아래와 같아요.

Post 모델에 ‘@Transient’ 를 사용했던 ‘isShowComment’ 프로퍼티의 값에 따라 댓글을 보여줄지 숨길지 설정할 수 있는 예시랍니다. ‘댓글 보기 / 숨기기’ 버튼을 누르면 ‘isShowComment’ 프로퍼티의 값이 toggle 되면서 값이 업데이트 되는데요. 이건 ‘@Transient’ 매크로로 정의되어 있기 때문에 앱을 종료했다 다시 실행해도 초기값으로 설정한 false가 유지된답니다.

위 예시는 업데이트가 바로 뷰에 적용되지가 않아서, @State 값에 업데이트 상태값을 추가하고 뷰에 ‘.id()’ 메서드를 체이닝해서 변화에 따라 뷰를 다시 그리도록 해주었습니다!

5. ‘.cascade’

앞서 잠깐 이야기한대로 삭제 규칙에 ‘cascade’가 동작하지 않더라고요?

위 코드에서 Post를 추가하고 해당 Comment를 추가한 후 Post를 삭제하면 여전히 연관된 Comment가 남아있는 것을 확인 할 수 있습니다. 그래서 저는 ‘.cascade’을 선언과 별개로 삭제는 자식도 함께 삭제하도록 코드를 구성했어요.

아직 정확하게 왜 .cascade가 동작하지 않는지는 잘 모르겠어요.

6. ‘@Query’

마지막으로 Query의 filter와 sort에 대해 적어볼게요.
우선 filter!

짜잔, 이렇게 예시처럼 filter 속성을 사용해서 댓글이 있는 글만 출력할 수도 있답니다.
다음으로 sort!

짜잔, 이렇게 예시처럼 sort 속성을 사용해서 Post의 createdAt 프로퍼티의 역순(내림차순, 최신이 위로) 정렬되도록 할 수 있답니다. 그리고 comment는 sorted를 사용해서 동일하게 역순으로 사용해서 정렬해 주었답니다.

아직 더 많은 SwiftData의 기능들이 있겠지만, 아직 직접 사용해본 기능이 많지 않아서 오늘의 포스팅은 여기까지로하고 다음에 2탄에 더 많은 정보로 포스팅을 해보도록 하겠습니다!