Derived attributes are available since iOS 13 and aim to improve fetch performance in many different scenarios. Although we have great performance with the latest devices it’s good to be prepared for scaling up to fetching a large number of items from your database.
Your memory footprint might look good now but once you start fetching thousands of more objects without optimising the way you fetch it can easily grow and decrease performance. My aim for this blog post is to show you how easily you can optimise by making use of derived attributes in Core Data.
What is a derived attribute?
It’s all in the name: the attribute is derived from other values. For example, you could create a derived attribute to return the total number of articles based on the articles count. In this case, you would use the articles to-many relationship with a @count
aggregate function. It reflects the number of objects in a relationship without having to perform a join.
To visualise the difference you can compare the two statements:
// Get the articles count using a join on our articles relationship.
let articlesCount = category.articles.count
// Get the articles count using a derived attribute.
let articlesCount = category.articlesCount
How to configure derived attributes?
Derived attributes can be configured in the Core Data model configuration by creating a new attribute. In this example, we’re creating an integer attribute named articlesCount
:
Inside the Data Model Inspector, you can mark the attribute as derived and fill in the derivation. In this case, we fill in articles.@count
which basically means that we want to use the articles
relationship using the @count
aggregate function. Saving your model configuration will generate updated entity classes and you’re good to go to use the new attribute.
In case you’re manually generating your entity classes you can add the attribute yourself:
@NSManaged var articlesCount: Int
Note that we’re marking the attribute as optional in our attribute configuration while the property itself is non-optional. This is important as upon saving, the derived attribute is not yet calculated. However, after saving, we always have a derived value.
When is a derived attribute being updated?
Derived attributes are recalculated when you save a context. Unsaved changes are not taken into account which is important to be aware of. A new article would only be added to the category count once it’s saved.
Are there any other examples of caculated attributes?
The @count
aggregate function is just an example of how you can use derived attributes. @sum
is the only other aggregate function that is supported but there are other expressions you can use too.
To-one keypaths
For example, showing the category name for an article using the category.name
derivation:
// Using the relationship
let categoryName = article.category.name
// Using a derived attribute
let categoryName = article.categoryName
To-one keypath with a function
The result of calling a function on a single value. Supported functions include canonical:
, uppercase:
, and lowercase:
. For example, you can use canonical
to create a derived search name for an article title with case and diacritics removed for more efficient comparison during a search.
The derivation value will be canonical:(name)
and results in the following value:
// Setting up the article.
article.name = "A Title With Case and Díäcrîtįcs"
// Using the normal name:
print(article.name) // Prints: "A Title With Case and Díäcrîtįcs"
// Using the derived attribute:
print(article.searchName) // Prints: "a title with case and diacritics"
Using time
The last example of a derived attribute is making use of the now()
to get the current time. This is especially useful in the case where you want to keep track of the modification date. The derived attribute will automatically be recalculated upon a new save event.
In my article NSManagedObject events: handling state in Core Data you might have learned to update the modification date as follows:
override func willSave() {
super.willSave()
setPrimitiveValue(Date(), forKey: #keyPath(Article.lastModifiedDate))
}
This works great and is more than fine. It allows you to alter the moments when an article is actually modified instead of always updating the modification date when something changed.
However, if you want to keep track of any modifications, a derived attribute using a derivation now()
is much simpler to maintain.
Conclusion
Derived attributes are a great way to enrich your entities with derived values while maintaining fetch performance. Aggregate functions allow you to easily calculate the number of objects in a relationship while to-one keypath functions can be powerful to optimize search performance.
If you like to improve your Core Data knowledge, even more, check out the Core Data category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!