Using Core Data with Swift
This is a second article in our development-focused series in which we talk about the development challenges we face, sharing our learnings and findings with the community. In this post our iOS developer Boris introduces workarounds for some common issues when using Core Data with Swift and shows how Swift is different from Objective-C in this particular aspect. As an interested mobile developer, you might also want to take a look at the first article in this series, Android and the DEX 64K Methods Limit.
While implementing the example iOS apps for our blog space template, we were using Core Data with Swift for the first time seriously. In this post you will learn about some caveats and changes from using Objective-C.
Documented changes
The most interesting part of using Core Data with Swift is that it requires the use of the dedicated @NSManaged
attribute, which is a special flavour of dynamic
. What this means is that it tells the compiler that the storage and implementation of the property will be provided at runtime, similarly to the @dynamic
attribute in Objective-C. As we will learn in the rest of this post, it seems as if this is providing a fairly specific behavior which currently breaks a number of Swift features. Apple's documentation doesn't talk much about this fact and only mentions that one has to include the module name when defining class names for an entity in the Core Data editor. Currently, it’s also necessary to perform this step manually when using our Xcode plugin to generate a data model, but we are working on it.
ContentfulPersistence was using NSStringFromClass()
to determine the entity name from the class name when creating new objects during synchronization. Unfortunately, the string representation of a Swift class will also include the module name, but the entity name will not. For that reason, our CoreDataManager
now removes the module name prefix before creating new entities. This is just something to keep in mind if you dynamically create NSManagedObject
instances from class names.
Optional attributes
Long before Swift gave us optionals, we could define optional properties in Core Data models. Their semantics were a bit different, namely saving a managed object with a non-optional property fails if it has never been given a value. Still, it seems like a reasonable expectation that since an optional property can be nil
at any point in time, it should be a Swift optional in the generated NSManagedObject
subclasses. However, this is not the case, and unless you manually change that, you will not be able to check if a property is nil
, because the Swift compiler does not know that it can have no value. So you should make sure to correctly mark all optional model attributes as Swift optional properties in your model code and also reasonably ensure that all the other properties are never nil
to begin with – for example, by adding default values to your model. This was reported as a radar, feel free to duplicate.
Implementing protocols
Now it is getting more messy. The way ContentfulPersistence works is that you are expected to implement certain protocols provided by the Contentful SDK in your NSManagedObject
subclasses, so that we can ensure that all required properties exist, but if one does that with a class written in Swift:
So what is even going on here? Swift uses symbol mangling in its code generation process, and the swift-demangle
tool that comes with Xcode allows us to demangle them:
A materializeForSet
function is usually used to set an initial value for a property. Because @NSManaged
properties are entirely dynamic, this function does not exist for managed properties at all. However, some part of the Swift compiler still generates code which uses it, apparently. I have yet to look more deeply into that part, as I was more interested in fixing it. By using subclassing or Swift property observing we can fool the compiler in generating all the required code, but that will break @NSManaged
entirely – more on that in the next section. Since we know that the missing function is not really needed anyway, how about fooling the compiler about its presence?
Let's add a simple C file to our project:
and voilà , the project builds and runs just fine. We have fooled the compiler to believe that the missing function is actually present and nobody ever calls it at runtime anyway. It is quite a hack, but for now, we have to live with it, meanwhile reporting it as another radar, which also contains some minimal example code for reproducing this.
Using property observers
I mentioned earlier that using property observers will cause the compiler to generate slightly different code, so as a final exercise, let us look at that. Consider this code:
It compiles just fine, but will actually cause the dynamic nature of @NSManaged
to fail at runtime with a crash if one tries to work with such a property. I have also reported this as a radar and you should generally avoid any kind of override
of managed variables, as this compiles just fine, but will break at runtime.
Conclusion
Using Core Data with Swift has some rough edges, but it is possible to avoid them all once you know them. Take a look at our example iOS apps as an example.