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.
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 behaviour 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.
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.
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:
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.
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.
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.