Logo for tanaschita.com

How to handle non-optional Core Data properties in Swift

Explore the possibilities to use non-optional Swift values in Core Data.

07 Nov 2022 · 4 min read

As described in this article on how to create an NSManagedObject subsclass for a Core Data entity in Xcode, we can either let Xcode automatically generate entity subclasses and their properties for us or create and manage them manually.

While the auto code generation has the advantage that the entity classes are always up-to-date, the manual option gives us more control and flexibility for example to improve the handling of non-optional properties.

Let's look at how to do that.

Sponsorship logo
Capture HTTP(s) traffic with Proxyman
Proxyman - Your ultimate man-in-the-middle proxy to effortlessly capture, inspect, and manipulate HTTP(s) traffic on macOS, Windows, iOS, and Android devices.
Get started for free

Declaring a property as optional or non-optional in Core Data

When adding properties in Core Data's .xcdatamodeld file, we can decide whether the property should be optional or non-optional:

Optional checkbox for a Core Data entity property
Optional checkbox for a Core Data entity property.

But after declaring a property as non-optional, Core Data still generates the username property as a Swift optional property:

@NSManaged public var username: String?

That's because the concept of optional and non-optional values in Core Data is different from that in Swift.

The concept behind optional and non-optional values in Core Data

In Swift, the compiler enforces the presence or absence of a value, which gives us guarantees about whether or not it is safe to expect a value at compile time.

Just like in Swift, declaring a property as non-optional in Core Data means that we need to handle the abscence accordingly, but we don't get any compiler guarantees or enforcements when initializing the object. That's because when working with Core Data, the framework checks the values at run time with validation rules and throws an error when the validation fails.

Using non-optional Swift values in Core Data

Not having compiler guarantees for non-optionals when working with Core Data is a drawback. Let's look at how we can solve this.

1. Declaring the properties as non-optional in Swift

To define Core Data properties as Swift non-optionals, we need to manually generate the entity subclasses. After doing that, the first thing we can do is to simply declare the properties as non-optional by removing question marks where needed:

public class User: NSManagedObject {
@NSManaged public var username: String
@NSManaged public var lastLogin: Date
@NSManaged public var verified: Bool
}

With that in place, the compiler will prevent us from setting any of these properties to nil. That's a good start.

But since the properties are marked with the @NSManaged attribute - which indicates that Core Data dynamically provides its implementation at runtime - Swift’s initialization rules do not apply. This means that we can still call init() and init(context:) without initializing any of the properties.

2. Preventing initializers from being called

To prevent initializers from being called, we can use Swift's @available attribute to mark the init methods as unavailable.

@available(*, unavailable)
public init() {
fatalError()
}
@available(*, unavailable)
public init(context: NSManagedObjectContext) {
fatalError()
}

With that in place, the compiler will now produce an error when calling init() or init(context:).

3. Providing a designated initializer

Now we can provide our own initializer:

public init(context: NSManagedObjectContext,
username: String,
lastLogin: Date? = nil,
verified: Bool = false) {
let entity = NSEntityDescription.entity(forEntityName: "User", in: context)!
super.init(entity: entity, insertInto: context)
self.username = username
self.lastLogin = lastLogin
self.verified = verified
}

The initializer enforces non-optional values to be passed in with a value and allows us to define default values where needed.

Conclusion

The approach described above provides a lot more clarity and safety when working with entities in code.

A drawback is that there is a lot of manual work to do to enforce the rules Swift usually enforces for us. Every time we edit an entity, we need to manually keep the initializer and properties up-to-date.

Sponsorship logo
Capture HTTP(s) traffic with Proxyman
Proxyman - Your ultimate man-in-the-middle proxy to effortlessly capture, inspect, and manipulate HTTP(s) traffic on macOS, Windows, iOS, and Android devices.
Get started for free

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

core data

persistence

xcode

ios

How to perform a lightweight migration in Core Data

Learn the possibilities Core Data provides to migrate model changes.

19 Dec 2022 · 4 min read

Latest articles and tips

© 2023 tanaschita.com

Privacy policy

Impressum