3 Aug 20187 min read

Swifty CoreData

CoreData is a framework that helps you manage the model layer objects in your application and it’s quite simple to use as some of the needed code can be automatically generated.

Sadly since CoreData has been written in Objective-C times (sounds like it has been ages 👀) and there are few issues (or shall I call them „restrictions”) with this framework while using it together with Swift.

In this article I will try to stick to CoreData staff only as much as possible, you won’t find any information creating the whole application and I assume you know basics about Swift and iOS development. Also… one more important thing — try to not think about CoreData the same as you think about SQL databases, it is and it works a little bit different.

If you are familiar with the CoreData basics and want to know how to generate model’s classes manually just jump to the end of the article.

Sample Project

I have created a sample project — you can find it on GitHub.

Let’s get started!

While creating new project you can check the mark Use Core Data to include a little bit of boilerplate code in your application — it contains support for saving Core Data and gives us a persistent container which can be used to load store to our application, this will also add an empty data model file to the project. Both those methods are created in the AppDelegate file.

Project Setup

Once you open Data Model file (.xcdatamodeld extension) you will see editor where we can add entities, attributes in entities, fetch requests etc. Let’s start with creating an entity — call it Article. Now we can add some attributes to it — we will make this sample as simple as possible so just create just two attributes and call them Title and Content. By now we should have model configured as you can see below:

Model Setup

Since article should usually always have a title and some content let’s make those non-optional (although remember Core Data is still an Objective-C framework, so it’s not the same optional to which you are used to in Swift) — you can do it by unchecking the Optional checkbox in the Utilities on the right.

Awesome! As I mentioned at the beginning, with basic configuration all our models will be automatically generated as classes so all we have to do now is just start saving and fetching data from our Core Data model.

Saving

We have to populate our model with some data first — create a function in a view controller

private func saveSomeArticles() {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
        return
    }

    // 1
    let context = appDelegate.persistentContainer.viewContext

    // 2
    let mediumArticle = Article(context: context)

    // 3
    mediumArticle.title = randomString(length: 10)
    mediumArticle.content = randomString(length: 50)

    // 4
    appDelegate.saveContext()
}

I created a reference to AppDelegate here since we will need that to save our articles in the store.

  1. The managed object context associated with the main queue. It is an object of NSManagedObjectContext type.
  2. Since our entity was automatically generated we can easily create its object in the code.
  3. We fill article with some random title and content (you can find the randomString function in the sample project, it’s just a copy-paste iAhmed’s answer on StackOverflow.
  4. Simple call to saveContext method from AppDelegate that saves our changes in the context.

That was quite simple, wasn’t it 🙂

Fetching

But… does it actually got saved? Let’s fetch those article(s) and check if they are where they should be — in our store. Fetching is a little bit more complicated… just kidding, it’s a few lines of code longer but will not take longer than few minutes to do some basic fetching. Do not forget to import CoreData framework if you do that in a file that is not already importing it like AppDelegate.

private func loadArticles() -> [Article] {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return []
        }

        let context = appDelegate.persistentContainer.viewContext

        // 1
        let request: NSFetchRequest<Article> = Article.fetchRequest()

        // 2
//        request.predicate = NSPredicate(format: "title = %@", "MEDIUM")
        do {
            // 3
            let articles = try context.fetch(request)

            // 4
            articles.forEach { article in
                guard
                    let title = article.title
                else {
                    fatalError("This was not supposed to happen")
                }

                print(title)
            }

            return articles
        }  catch {
            fatalError("This was not supposed to happen")
        }

        return []
    }
  1. Create a fetch request. Since the code is automatically generated we can easily call extension’s function that creates a request for us — it returns NSFetchRequest<Article>(entityName: „Article”)
  2. You can configure your request — I won’t get into details too much but among the other things you can create a predicate that will fetch only objects with given precondition.
  3. Fetches all objects (articles in our case) from the context’s store. Since it’s generic method and takes our request as the parameter, the result will be cast to the proper object.
  4. Loops through all fetched articles and prints their title.

Deleting

Removing objects from the store is as easy as above operations. All we have to do is create/fetch object(s) that we want to remove and just call delete on the context, giving an object to remove as the parameter.

private func deleteAllArticles() {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
        return
    }

    let context = appDelegate.persistentContainer.viewContext

    // 1
    let articles = loadArticles()

    // 2
    articles.forEach { article in
        context.delete(article)
    }
}
  1. Load articles (NSManagedObject’s objects) using the previous function.
  2. Loops through articles and deletes them.

What’s next?

Automatic does not really mean better, so let’s write our models manually so we can take advantage of optional values (who knows, maybe someday it will be generated considering the optional values). Go back to data model’s editor and in the Utilities under Class section, change Codegen to Manual/None.

Codegen Manual

Once we do that the project will no longer compile so all we have to do it is create our own Article class.

// 1
@objc(Article)
// 2
final class Article: NSManagedObject {
    // 3
    @NSManaged var title: String
    @NSManaged var content: String
}


extension Article {
    // 4
    @nonobjc class func fetchRequest() -> NSFetchRequest<Article> {
        return NSFetchRequest<Article>(entityName: "Article")
    }
}
  1. Since CoreData is an Objective-C framework we have to make our class available in Objective-C and Objective-C runtime.
  2. Name the class the same as it is named in the data model editor and inherit from NSManagedObject.
  3. Add properties to match attributes from the model — here we can add some nice stuff. I did not do that in my sample project but ie. We can create our own types and aliases and use them here or we can add some extra properties that are not mentioned in the model so temporarily store them.
  4. Extension with a fetchRequest method to make it compatible with previous implementation and… well… isn’t it just a nice way to create fetch requests?

Writing models on your own has one huge advantage — you can create aliases or wrappers for any attribute to make it easier for others to understand models (If you want me to write more about that just let me know in comments below).

Auto-generated classes and extensions are stored in DerivedData directory so actually it’s harder to maintain those between a group of people — each time someone changes something in the model, everyone in the team has to re-generate them (ie. by clearing DerivedData first). There is a similar issue with manual classes — have to keep them in sync with model manually but personally, I like to have everything in the control.