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.
I have created a sample project — you can find it on GitHub.
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.
Once you open Data Model file (.xcdatamodel
d 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:
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.
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.
NSManagedObjectContext
type.randomString
function in the sample project, it’s just a copy-paste iAhmed’s answer on StackOverflow.saveContext
method from AppDelegate
that saves our changes in the context.That was quite simple, wasn’t it 🙂
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 []
}
NSFetchRequest<Article>(entityName: „Article”)
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)
}
}
NSManagedObject
’s objects) using the previous function.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
.
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")
}
}
CoreData
is an Objective-C framework we have to make our class available in Objective-C and Objective-C runtime.NSManagedObject
.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.