Introducing SwiftData the successor of CoreData
Tags: swift, xcode, database, swiftui
Published: 2023-06-14 08:47:31

[Here](https://blog.leonifrancesco.com) you can find this articles and many more about iOS and macOS Development.

SwiftData is a framework for data modelling and management.
It's built on top of CoreData's  persistence layer, but with an API completely redesigned and reimagined for Swift.
SwiftData provides also support for undo and redo automatically. 
And if your app have shared container enabled, SwiftData automatically makes data directly accessible by widgets using the same APIs.
Now, let's see how it works.
## Models
First we need to create the models that we want to persist.
To create a model you need to mark the `class` with the Macro `@Model`.
SwiftData includes all non-computed properties of a class as long as they use compatible types. So far, SwiftData supports types such as `Bool`, `Int` and `String`, as well as `structures`, `enumerations`, and other value types that conform to the `Codable` protocol.
So, let's create the `Folder` and  `Note` models.

```swift
@Model
final class Folder {
 
  @Attribute(.unique)
  var id: String = UUID().uuidString
  var name: String
  var creationDate: Date
  var notes: [Note] = []
 
  init(name: String) {
	self.name = name
	self.creationDate = .now
  }
}
 
@Model
final class Note: Decodable {
 
  @Attribute(.unique)
  var id: String = UUID().uuidString
  var text: String
 
  init(text: String) {
	self.text = name
  }
```
1. Create a model class that contains all the properties you wish to persist
2. Mark the classes with the Macro `@Model`
3. Optionally, add Macros (`@Attribute`, `@Relationship`, `@Transient`) to the property that need them

Creating models is as simple as that.

> **WARNING**: As for now, if you declare an array of objects is likely you will get an error like `Ambiguous use of 'getValue(for:)'`. This is a bug in Xcode 15.0 beta that I think will be fixed on final version of Xcode. For now, just remove the `@Model` from the `Note` class and add `@Transient()` to `notes` property.

## Model attributes
Properties in the `@Model` can have [Macros](/articles/swift-macros) attached to them that will defined their behaviour.
Currently there are 3 types of macros: [`@Attribute`](https://developer.apple.com/documentation/swiftdata/attribute%28_:renamingidentifier:hashmodifier:%29), [`@Relationship`](https://developer.apple.com/documentation/swiftdata/relationship%28_:_:renamingidentifier:inverse:hashmodifier:%29), [`@Transient`](https://developer.apple.com/documentation/swiftdata/transient).
### @Attribute
This Macro alters how SwiftData handles the persistence of a particular model property.
This is commonly used to mark a property as primary key, as we did for `Folder.id`.
```swift
@Attribute(.unique) 
var id: String
```
But there are many other options:
* `encrypt`:  Stores the property’s value in an encrypted form.
* `externalStorage`: Stores the property’s value as binary data adjacent to the model storage.
* `preserveValueOnDeletion`: Preserves the property’s value in the persistent history when the context deletes the model.
* `spotlight`: Indexes the property’s value so it can appear in Spotlight search results.
* `transformable`: Transforms the property’s value between an in-memory form and a persisted form.
* `transient`: Enables to the context to disregard the property when saving the model.
* `unique`: Ensures the property’s value is unique across all models of the same type.
### @Relationship
By default if you declare a property whose type is also a model, SwiftData manages the relationship between these models automatically.
But if you want to customise its behaviour, you can use the `@Relationship` Macro.
```swift
@Relationship(.cascade) 
var notes: [Note] = []
```
This way if the `Folder` is deleted, all of its notes are deleted too.
This Macro defines the behaviour that in CoreData was named **Delete Rule**.

> **NOTE**: Both `@Attribute` and `@Relationship` support the renaming of the  argument in case you want to preserve the original name of the property.

### @Transient
By default SwiftData persist all non-computed properties inside the model, but if you don't need to persist a specific property, you can add the `@Transient` Macro to that property.
```swift
@Transient 
var someTempProperty: String
```
## The model container
Before using these models, we need to tell SwiftData which models to persist.
To do this there is the `.modelContainer` modifier. To use it you simply pass all the models you want to persist.
```swift
@main
struct NoteBookApp: App {
 
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: [Folder.self]) // <-
    }
}
```
Here we pass only the `Folder` class because SwiftData knows that it has to persist also `Note` since there is a relationship between the two classes.
The `.modelContainer` modifer allows you to specify some options.
* `inMemory`: Whether the container should store data only in memory.
* `isAutosaveEnabled`: If enabled you don't need to save changes.
* `isUndoEnabled`: Allows you to undo or redo changes to the context.
* `onSetup`: A callback that will be invoked when the creation of the container has succeeded or failed.

> **NOTE**: If you model contains a relationship to another model, you can omit the destination model.

## Saving models
Now, we are ready to use SwiftData.
Let's see how to save models.
First we need to get the context, to do this we use the `@Environment(\.modelContext)` wrapper.
```swift
@Environment(\.modelContext) private var context
```
Next, once we have the context, we create the object we want to save and we insert it into the context.
```swift
let folder = Folder(name: "Trips")
context.insert(folder)
```
If we have enabled the autosaving while creating the container we don't have to do anything else.
Otherwise we need to save the context.
```swift
do {
   if context.hasChanges {
      context.save()
   }
} catch {
   print(error)
}
```
## Fetching models
To fetch existing models, SwiftData provides the `@Query` wrapper.
This wrapper allows you to sort, filter and order the result of the query.
```swift
@Query(filter: #Predicate { $0.name != "" },
       sort: \.name,
       order: .forward) 
var folders: [Folder]
```
Here we query by:
* Filter folders that have non-empty name
* Sort them by the `name` property
* Order them ascending
 
Or, we can use a `FetchDescriptor`.
```swift
extension FetchDescriptor {

  static var byName: FetchDescriptor<Folder> {
    var descriptor = FetchDescriptor<Folder>(
	    predicate: #Predicate { $0.id != "" },
		sortBy: [SortDescriptor(\.name)]
	)
    descriptor.fetchLimit = 50
    descriptor.includePendingChanges = true
    return descriptor
  }
}
 
@Query(.byName) 
var items: [Folder]
```
This way we can reuse the `FetchDescriptor` for as many queries as we need.
## Updating models
To update a model we just need to change the value of the property we want and if autosave is enabled that's all.
```swift
folder.name = "New name"
```
Otherwise we need to save the context.
## Deleting models
To delete a model is just as simple as to create one.
We get the instance of the object to delete and we pass it to the context.
```swift
context.delete(model)
```
## Conclusion
SwiftData really simplifies the usage of CoreData, making it less prone to errors.
Of course this is still in Beta so there will be changes and improvements, but so far I really enjoyed playing with it.
Hope this will help developers speed up their workflow to persist data and reduce crashes.
Thank you for reading!
What are you thoughts about this? Tweet me [@franceleonidev](https://twitter.com/intent/tweet?original_referer=http%3A%2F%2Fblog.leonifrancesco.com%2F&ref_src=twsrc%5Etfw%7Ctwcamp%5Ebuttonembed%7Ctwterm%5Eshare%7Ctwgr%5E7C%20SwiftyLion&url=https%3A%2F%2Fblog.leonifrancesco.com%2Farticles%2Fintroducing-swift-data-the-successor-of-coredata&via=franceleonidev) and share your opinion.