-
Notifications
You must be signed in to change notification settings - Fork 2
NSManagedObject Mapping
To provide relevant examples, here are two entities to work with
class PlayerEntity: NSManagedObject {
@NSManaged var name: String?
@NSManaged var score: Int32
@NSManaged var avatar: Data?
@NSManaged var games: NSSet?
}
class GameEntity: NSManagedObject {
@NSManaged var duration: Double
@NSManaged var player: PlayerEntity?
}
And their associated DatabaseModel
s
extension PlayerEntity: DatabaseEntity {}
extension GameEntity: DatabaseEntity {}
struct Player: DatabaseModel {
let name = Field(\.name)
let score = Field(\.score)
let avatar = UIImage(\.avatar, as: UIImage.self)
let games = Children(\.games, as: Game.self)
// ...
}
struct Game: DatabaseModel {
let duration = Field(\.duration)
let player = Parent(\.player, as: Player.self)
// ...
}
To read a current value in the database model, you can call its current(:)
function. This allows to read a field, a parent value or a child value when exposed by the related model.
let player: Player
let game: Game
player.current(\.name) // String?
player.current(\.score) // Double
player.current(\.avatar) // UIImage?
player.current(\.games) // [Game]
game.current(\.player) // Player?
game.current(\.player, \.name) // String?
The library offers map
functions using those current
functions.
var players: [Players]
// Fields
players.mapCurrent(\.name) // [String?]
players.compactMapCurrent(\.name) // [String]
// Children
players.flatMapCurrent(\.games) // [Games]
You can also subscribe to a value rather than reading it.
let player: Player
func setupSubscriptions() {
player.publisher(for: \.name)
.assign(to: name.label.text, on self)
.store(in: &subscriptions)
player.publisher(for: \.avatar)
.assign(to: avatarView.image, on: self)
.store(in: &subscriptions)
player.publisher(for: \.games, sortedBy: .ascending(\.duration))
.assign(to: avatarView.image, on: self)
.store(in: &subscriptions)
}
To set the Player
value, you can either use the assign(:to:)
function or the publisher assign(to:on:)
when using a publisher that emits the value to assign.
player.assign(newName, to: \.name)
Just(newName)
.assign(to: \.name, on: player)
.store(in: &subscriptions)
Before assigning a value, you can validate it with the validate(:for:)
function or the publisher tryValidate(for:on)
.
try player.validate(newName, for: \.name)
Just(newName)
.tryValidate(for: \.name, on: player)
.sink(receiveCompletion: { completion in // can finish with a failure
//...
}, receiveValue: { _ in })
.store(in: &subscriptions)
Optionally, it's possible to use validateAndAssign(to:)
and to chain both tryValidate(for:on:)
and assign(to:on:receiveCompletion:)
publishers.
try player.validateAndAssign(newName, to: \.name)
Just(newName)
.tryValidate(for: \.name, on: player)
.assign(to: \.name, on: player) { completion in
if case let .failure(error) = completion {
// display a relevant error message to the user
}
.store(in: &subscriptions)
You can learn more about the validation in the Validation section.
To map a relationship between two entities, you can use:
-
Children
to map a 1:N relationship (from the parent point of view) -
OrderedChildren
to map an ordered 1:N relationship (from the parent point of view) -
Parent
to map a N:1 relationship (from the child point of view) -
Sibling
to map a 1:1 relationship
For example, if the player entity has a 1:N relationship with a GameEntity
entity mapped with a Game
model, you can declare
let games = Children(\.games, as: Game.self)
It's then possible to use the publisher player.publisher(for: \.games)
which will emit a Game
array, or to call relevant modifications CRUD functions.
// Subscribe to the player games, sorted by their date then by their duration
player.publisher(for: \.games, sortedBy: .ascending(\.date), .descending(\.duration))
// Add a `Game` model to the player `games` relationship
let game = Game(...)
try player.add(game, to: \.games)
Once a DatabaseModel
is declared to map an entity, you can subscribe to its fetched results updates with the static updatePublisher(sortingBy:)
function. The subscription will hold a NSFetchedResultsController
and the emitted values will be an array of DatabaseModel
.
Player.updatePublisher(sortedBy: \.ascending(.name)) // emits [Player]
When working with a diffable data source, this can be really useful, especially as a DatabaseModel
is hashable.
Wiki valid for version 1.0.0