ITZone

Maintaining model consistency in Swift

When designing model layers of any application or system, establishing a consistent model for each state and the data we process is essential, to make our logic predictable. . However, ensuring that each state is stored in a single place is easier said than done – and it’s common to end up with bugs and errors due to inconsistent model data, especially when those models are transferred. through and transform in many different places. Those errors are definitely happening outside the models, in this article, let’s see how we can improve the consistency within each of our models – and how we can set them up. A stronger platform for our codebase.

Deriving dependent states

the overall model layer for any particular system is often described as a hierarchy, where the higher level data sections depend on some kind of ground state. For a simple example, suppose we are working on a contact management application and we have a Contact model that contains each person’s contact information – such as their names and email addresses:

At first glance, the code above might look like any standard data model, but there’s really a significant risk to it being inconsistent. Since we have three separate properties for firstName , lastName and fullName , we must always remember to update fullName whenever we make changes to firstName , lastName or our data will be inconsistent. . Instead of deploying fullName as a separate stored property, instead, turn it into a calculated property:

That way, we no longer have to worry about our model becoming inconsistent, because the fullName of a contact will now be recalculated every time it accesses the current firstName and lastName . However, always recalculating the dependent state each time it is accessed is not always practical – especially if the state depends on a large set of elements or if the required calculation involves a lot of a little bit more than combining a few basic values. Like we saw in “Utilizing value semantics in Swift,” in those situations, maintaining a separate stored property may be the best approach – but if we prevent the asset. be modified externally and automatically updated whenever its base state is changed, we can still ensure that the model containing it remains consistent. Here, we can use an observer property to do that for the Leaderboard model, which contains high scores for the top players of the game, as well as the current average of those players:

The above patterns not only improve our model’s consistency but also make it easier to understand and use

Consistent collections

While maintaining a 1: 1 relationship between two state parts can be challenging enough, things are even more difficult when we have to ensure that many collections remain consistent with each other. Going back to the previous contact management application, suppose we were building a ContactList class – which would store a set of contacts and allow those contacts to be organized into groups. and marked as favorites:

Similar to the previous example, we were asked to manually sync the contact names, the above model also makes each of its call sites accountable for homogeneous data. For example, when deleting a contact, we also have to remember to delete its ID from our favorite set of IDs – and when renaming a group, we always have to update its key in groups dictionary. Both of the following functions do not do that, and although they may look perfectly valid, both of them make the ContactList they change inconsistent:

An early idea of ​​how to avoid the above types of conflict could be to use the private (set) we used on our Leaderboard model earlier and prevent our collections from being modified outside the category. ContactList :

However, in this case, we really need to be able to change our collections somehow – so the approach above will require us to copy some of their basic API collections. ta, to be able to make changes like adding and removing contacts:

The above code can work as long as we only have to change our collections in very simple ways and as long as we do not add any new pieces of data to our model, but it is not A flexible solution. Requiring a completely new API to be created for each change, in general, is not a great design – so let’s see if we can find a more dynamic approach and use it. be in the future. If we think about it, keeping our ContactList data synchronized really only requires us to be able to react to any changes to the property that are also used as key elements (ids in the field). co Contact name in case of Contact.group ) and to be able to make updates whenever an element is deleted (so that we can ensure that no contact is removed yet still in the favoriteIDs ). Let’s add to both of those capabilities by implementing a lightweight wrapper around the Dictionary. Our wrapper, called Storage , will use the key paths mechanism to keep our keys in sync – and will also allow us to attach a closure of the keyRemovalHandler to receive notifications whenever the key is deleted:

Our initializer and keyRemovalHandler are marked as fileprivate to prevent our new Storage Type instances from being created outside the file that the ContactList is defined to enhance our model’s consistency. To make Storage work like a real Swift collection, we have 2 options. We can make it fully compliant with the Collection Collection protocol or if we just need to repeat it, we can make it compliant with Sequence – by passing the makeIterator () call to its basic dictionary. :

With the foregoing, we can write loops on our collections and use APIs like forEach , map and filter on them – just like when using Dictionary directly. Next, to allow the Storage to be changed, we will add a subscript implementation to ensure that the key element is updated in case its key attribute is changed and also calls keyRemovalHandler when the key is deleted:

Also, our wrapper collection is complete and we are ready to update the ContactList to use it – by storing our contacts and groups with our new type and by using keyremovalHandler to Make sure our favorite IDs are still in sync with the contact’s collection:

With this new implementation, we can still change our collections by adding and deleting values, just like when using Dictionary directly – only now we ensure that our data remains consistent. , completely automated. Now that we have the custom collection, we can go one step further and make it easier to use – by adding a convenient API to add and remove values ​​without having to worry about which key to use. :

Using the above APIs and previous subscript , we are now free to decide how to add or remove values ​​in each case without affecting the consistency of the model in any way.

Although writing a custom collection is not always appropriate, whenever we want to add new behaviors to one of the data structures provided by the standard library, create lightweight wrappers that are tailored to A very specific domain can be a great approach.

Conclusion

In many ways, to make a code base really powerful, we have to start by making its core data models as predictable and consistent as possible – because those models often play a role. The game is the basis of the rest of the code base that we build.

Hope the article will be useful to you

Reference: https://www.swiftbysundell.com/articles/maintaining-model-consistency-in-swift/

Share the news now