Projections¶
The Consistency Manager supports projections for models. This allows using the same modelIdentifier for instances of different classes. For instance, let’s say you have two projections of the same base type - PersonModel and FullProfileModel. Both models represent the same data (and share fields) so we want to use the same ID for both. However, this doesn’t come for free. In general, it’s not advised to use projections if you can avoid it and instead use composition (see the Alternatives section of this page).
Let’s say that our models look something like this:
struct PersonModel {
let id: String
let name: String
let pictureURL: String
}
struct FullProfileModel {
let id: String
let name: String
let pictureURL: String
let age: Int
let username: String
}
If we want these models to sometimes have the same ID, we need to implement an additional method (mergeModel(model: ConsistencyManagerModel)) which merges one model into the other. For instance, for PersonModel, this would be:
extension PersonModel {
func mergeModel(model: ConsistencyManagerModel) -> ConsistencyManagerModel {
if let model = model as? PersonModel {
// If the class is the same, we can just return the other model (that's the model with the fresh data)
return model
} else if let model = model as? FullProfileModel {
// Return a new PersonModel with all the updated fields from the new model
return PersonModel(id: id, name: model.name, pictureURL: model.pictureURL)
} else {
assertionFailure("We cannot merge models of this type")
// Best we can do is not apply any updates
return self
}
}
}
This method should always return the same class as Self. It should take all the new data from the new model and merge it with all the current data.
Implementation Difficulties¶
There are a couple of things which makes implementing this method tricky.
First, it needs to be recursive. It not only needs to merge the current model, but merge all of the submodels too. This can’t be done automatically, but it’s not always clear how to merge subtrees.
Second, merging arrays of different classes is difficult. If the array has new members, it may not be possible to create members of a class if it’s missing required fields. For this reason, it’s recommended not to use different projections in arrays.
In general, it’s advised to use this feature sparingly if at all and to ensure that you know what you’re doing.
Alternatives¶
Instead of using projections, you can use composition. Larger models can contain smaller models. This means whenever a smaller model updates, the larger model will receive this update. For instance, for the models above, we could rewrite them as:
struct PersonModel {
let id: String
let name: String
let pictureURL: String
var modelIdentifier: String { return "PersonModel:\(id)" }
}
struct FullProfileModel {
let id: String
let person: PersonModel
let age: Int
let username: String
var modelIdentifier: String { return "FullProfileModel:\(id)" }
}
Here, each model defines a different ID and one model contains the other. This is a much simpler way to write your models while achieving consistency as well as the ability to choose large or small models for each use case.
Additional features¶
The Consistency Manager also allows models to use the same class for different projections. This is useful if you want to define a model with multiple optional fields and not always set them all. This is rare, and again, use with caution as it can be hard to tell if a field has been deleted or has not yet been set. See ConsistencyManagerModel.swift for more information.