preface

Developers who have worked with Core Data will have seen the Derived and Transient properties in the properties pane on the right when editing the Data Model. There isn’t much documentation about these two attributes, and most developers don’t know how or when to use them. The text will introduce the functions, usage and precautions of the Derived and Transient attributes based on my experience in using them.

The original post was posted on my blog wwww.fatbobman.com

Welcome to subscribe my public account: [Elbow’s Swift Notepad]

Derived

What is a Derived attribute

Starting with iOS 13, Apple added a Derived attribute to Core Data, and its name already indicates what that attribute means — the value of that attribute is Derived from the value of one or more other attributes.

In layman’s terms, Core Data automatically generates values for derived properties when a managed object instance is created or modified. The value is computed from other attribute values based on the preset Derived Expression.

The function of the Derived attribute

Here is a concrete example to help you understand the power of derived attributes.

There are two entities in the project, TodoList and Item. TodoList has a one-to-many relationship with Item, and Item has a one-to-one relationship with TodoList.

Before, if we wanted to see how many items are under each TodoList, we could use the following code:

let count = todolist.items.count
Copy the code

Using Derived attributes, we can get the number of items by using the following code.

let count = todolist.count
Copy the code

How do I set the Derived property

Typically we need to set the derived property in the Data Model Editor of Core Data, as shown below. We create the derived property count for TodoList in the example above

  • Create an attribute named count for TodoList
  • Choose Derived
  • Set Derivation to items.@count (calculate the number of items for the items relationship)

Developers can set the types and derived expressions of derived attributes as required. Currently, the following types of derived expressions are supported:

  • Just copy content

    Commonly used in one-to-one relationships, such as in the example above, we can use the Derived expression todolist.name to set a Derived attribute of todolistName for Item, which holds the name of the todolist corresponding to that Item. There are no specific restrictions on the type of property to copy.

  • Saves an attribute (of type string) by transformation

    Only String attributes are supported. You can use different attributes in the same Entity or Entity attributes of a To-one Entity. Uppercase:, lowercase:, and Canonical: are supported. Provides search efficiency by saving string variants. For example, the derived expression that saves the lowercase version of TodoList’s name is lowercase:(todolist.name).

  • Calculate count and sum for a pair of relationships

    Computes the number of to-many objects or the sum of specified attributes. To use @sum, the attribute must be of a computable value type. For example, the expression for calculating the sum value of an entity named Student and attribute named age is student.age.@sum.

  • The current time

    Save the date of the Sqlite operation to update the data record corresponding to the managed object. Usually used for timestamps like lastModifiedDate. The derived expression is now().

Usually we use Derived with Optinal, but if you don’t select Optional you have to do something special to make it work. It is specified in the notes below.

If you write nS-Managedobject code by hand, the Derived property is written exactly the same as the other properties (which still need to be set in the Data Model Editor). For example, count can be defined as follows:

@NSManaged var count: Int
Copy the code

Update mechanism of Derived data

Who computed the value of derived data

The values of derived data are computed and updated directly by Sqlite.

The calculation of Derived values is one of the few operations in Core Data that is done directly using Sqlite’s built-in mechanism, rather than by Swift (or Objective-C) code.

For example, the now() expression, Core Data will generate Sql code similar to the following when creating a table:

CREATE TRIGGER IF NOT EXISTS Z_DA_ZITEM_Item_update_UPDATE AFTER UPDATE OF Z_OPT ON ZITEM FOR EACH ROW BEGIN UPDATE ZITEM SET ZUPDATE = NSCoreDataNow() WHERE Z_PK = NEW.Z_PK; SELECT NSCoreDataDATriggerUpdatedAffectedObjectValue('ZITEM', Z_ENT, Z_PK, 'update', ZUPDATE) FROM ZITEM WHERE Z_PK = NEW.Z_PK; END'
Copy the code

The code for @count:

UPDATE ZITEM SET ZCOUNT = (SELECT IFNULL(COUNT(ZITEM), 0) FROM ZATTACHEMENT WHERE ZITEM = ZITEM.Z_PK);
Copy the code

Therefore, Sql is more efficient than Swift (or Objective-C) for the same functionality.

In Core Data, you usually get the result from a persistent store, put it back into context, evaluate it, and persist it. There are many I/O processes in the middle, which affects the efficiency.

When is derived data updated

Because it is handled directly by Sqlte, Sqlite updates the corresponding derived data only when the data is persisted. Handling non-persistence only in context will not yield the correct derived value. The behavior of persistence can be invoked using code viewContext.save (), or via network synchronization, for example.

Advantages and Disadvantages of Derived

advantages

  • High efficiency

    Due to its unique update mechanism, the processing of values is more efficient and there is no redundant processing action (updates are only made when persisted).

  • Simple and clear logic

    When used properly, the configuration requires less code and is clearer. For example, now ()

disadvantages

  • Limited expressions are supported

    Sqlite has a very limited set of expressions to support for more complex business needs.

  • The code is harder to read for developers who don’t understand Derived

    The configuration of Derived is done in the Data Model Editor, and just reading the code won’t tell you where the Data came from or how it was processed.

The alternative to Derived

Calculate attribute

For infrequently used attribute values, it may be a better choice to create computed attributes for managed objects, such as the number of TodoList items calculated above.

extension TodoList {
    var count:Int { items.count }
}
Copy the code

willSave

Use the willSave method of NS-managed object to set the value for the specified property before persisting the data. Such as:

extension Item {
    override func willSave(a) {
      super.willSave()
      setPrimitiveValue(Date(), forKey: #keyPath(Item.lastModifiedDate))
  }
}
Copy the code

Derived and the above two methods have their own advantages and disadvantages, please choose the appropriate solution according to the specific use scenario.

Considerations Derived

When configuring the Derived attribute, if you execute the code instead of Optional, you will get an error like the following when adding data:

Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=1570 "count is a required value."
Copy the code

This is because Core Data requires us to default for the derived property because it is not an optional value, but because the derived property is read-only, we cannot directly assign a value to the derived property of the managed object instance in our code.

The solution is to pass Core Data’s property validity check by setting an initialization value for the derived property in awakeFromInsert.

extension TodoList {
    override func awakeFromInset(a){
        super.awakeFromInser()
        setPrimitiveValue(0, forKey: #keyPath(Todolist.count)) 
    }
}
Copy the code

The value can be any value (typed), because Sqlite will generate a new value to override our initialization value when persisting.

Transient

What are Transient properties

Transient (Transient property) is a non-persistent property. As part of the managed object definition, Core Data tracks Transient property changes and sets the corresponding managed object and managed object context state, but the contents of the property are not stored in the persistent store, nor are corresponding fields created in the persistent store.

Transient properties are no different from other Core Data properties except that they cannot be persisted, and support all available Data types, as well as Optional, Default values, and more.

How do I set Transient properties

In contrast to Derived, Transient property Settings are very simple, just check Transient.

Why use Transient

Since Transient is not persistent, why use the Data Model Editor to create it?

We can create storage properties for managed objects directly from code, for example:

@objc(Test)
public class ITEM: NSManagedObject {
  var temp:Int = 0
}

extension Item
    @NSManaged public var createDate: Date?
    @NSManaged public var title: String?

}

let item = Item(context:viewContext)
item.temp = 100
Copy the code

In the above code, no matter how we modify the Temp property of the item, Core Data will not notice.

The managed properties of the managed object (denoted by @NSManaged) are managed by Core Data, which keeps track of the managed properties in the managed object to set the corresponding state. Using the Transient property, Core Data sets hasChanges of the managed object instance and managed object context to true when the property content changes, So be it @ FetchRequest or NSFetchedResultsController will automatically response data changes.

Therefore, Transient is the only option when we don’t need persistence but need to be able to use dirty states.

Initialization of Transient value

Since the Transient property is non-persistent, the Transient property reverts to its initial value every time a managed object instance containing the Transient property appears in the context (Fetch, Create, Undo, and so on).

Although we can set default values for Transient in the Data Model Editor, there are many scenarios where we need to calculate and create initial values for Transient based on the situation or other Data. We can choose to set it at the following time:

  • awakeFromFetch

    When populating data for an instance of an idler state (Fault)

  • awakeFromInsert

    When creating a managed object instance

  • awake(fromSnapshotEvents:NSSnapshotEventType)

    When an instance is loaded from a snapshot

When setting Transient or other properties in these methods, the raw accessor methods should be used to set the data to avoid triggering KVO observer notifications. Such as:

setPrimitiveValue("hello",forKey:#keyPath(Movie.title))
Copy the code

Example for using Transient properties

In most Core Data books, Transient properties are often mentioned in passing. Authors often state that they have not encountered a suitable Transient use case.

It was not long ago that I encountered the first application scenario that met Transient characteristics.

In the process of development [health notes 3.0], I need to have a place a contains many relationships and record the managed object instance to Deep Copy (Copy below all relational data), Copy after the instance of the replace after completion of the Copy of the original instance (in order to solve the network data sharing in special requirements). Because @fetchRequest is used, two identical data records appear in the UI list for a second or two during the copy process, causing confusion for the user.

If I use a persistence scheme, I can create an attribute for the data to indicate whether it is displayed or not, such as visible. Solve the list duplication problem by setting this property and configuring Perdicate before and after the copy operation.

But because this scenario is used so infrequently (and many users may not use it at all), creating a persistent field would be wasteful.

Therefore, I created a Transient property called Visible for the managed object to avoid repeated displays without wasting storage space.

Other matters needing attention about Transient

  • The refreshAllObjects of the NS-Managed BjectContext will reset the Transient content
  • If only need to look at the managed object can use hasPersistentChangedValues sustainability attributes if there is a change
  • Do not use transient attributes as constraints in NSPredicate
    @FetchRequest(entity: Test.entity(),
                  sortDescriptors: [NSSortDescriptor(keyPath: \Test.title, ascending: true)],
                  predicate: NSPredicate(format: "visible = true"))
    var tests: FetchedResults<Test>
Copy the code

The above code is used incorrectly. If you want to display only visible == true, you can use the following method:

    @FetchRequest(entity: Test.entity(),
                  sortDescriptors: [NSSortDescriptor(keyPath: \Test.title, ascending: true)])

    ForEach(tests) { test in
         if test.visible {
             row(test: test)
         }
    }
Copy the code

conclusion

Core Data is a long-established framework that contains a number of very useful but little-known features. Even a general understanding of these features will not only open your mind, it may be a problem solving tool in a certain situation.

To read more about Core Data, check out my Core Data column.

Hope you found this article helpful.

The original post was posted on my blog wwww.fatbobman.com

Welcome to subscribe my public account: [Elbow’s Swift Notepad]