Simplify Data Store & Operations in Your iOS Projects with Realm

Are you tired of using CoreData? If so, then learn how to use Realm with a code-along project

Shubham Singh
Mar 3 · 12 min read

If you are an iOS Developer, you must have come across CoreData and used it for Data Persistence. To make CoreData work seamlessly, you need a good understanding of its APIs.

I used CoreData a lot before, and I felt that it required a lot of context management and work. Even after this, there were times when there would be an unexpected crash.

I tried Realm Database to see for myself how much of a difference it made in comparison to Core Data, and I must say I was surprised. Realm makes it a lot easier to perform operations and is quite performant.

In this article, We learn about Realm Database, its advantages over Core Data, and create an iOS Project and integrate Realm.

Let us first take a look at the description of Realm-

Realm is an open-source, cross-platform mobile database that you can integrate directly into your iOS projects.

Realm provides a developer-friendly alternative to CoreData and SQLite for storing and fetching data.

Realm removes the need for ORM that causes performance and maintenance issues. It is faster than even raw SQLite operations.

Advantages of using Realm-

  1. Model creation is easy. You only need to inherit from the Object class and implement the required variable and functions.
  2. Realm has its engine, quick and efficient. Thanks to its zero-copy design, Realm is much faster than CoreData.
  3. You can install Realm Studio and see the data stored by the application in your MacBook. You can even add and modify the entries.

Alright, now that we know what Realm is and does, Let us take a look at the project that we will create by the end of this article-

Pokemon Search App with Realm DB by Shubham Singh

Looks fun? Let us start :D

Note — This tutorial uses the UIKit Framework. If you only use the SwiftUI framework, you can still go through the Article.

Prerequisites

  • A basic understanding of iOS development concepts.
  • Persistence to read through the full tutorial.

I have explained everything simply, and you shall have no trouble following through with the article.

Introduction

  1. We will fetch Pokemons and display them in the HomeScreen in a paginated way with an offset and a limit. As the user scrolls the TableView, we get more data from Realm and add it to the TableView.
  2. We will provide the ability to search the Pokemon by its name.
  3. We will provide the ability to search the Pokemon by its type. Since we are not creating a separate FilterView, we search for Pokemons based on type when the user prefixes the query by type=.

Tutorial

Add Realm as a Dependency

  1. Inside XCode > File > Swift Packages > Add Package Dependency.
  2. Enter “https://github.com/realm/realm-cocoa” as the repository URL, and press next.
  3. We do not need to do anything to the version, press next.

Once the package has been added, choose Realm-Swift and press finish.

Add RealmSwift to the project.

Note- If you are more comfortable with using CocoaPods, use

pod ‘RealmSwift’ 

inside the Podfile and do a pod install.

Let us go over the basics of Realm-

i) Realm Instance- A Realm instance represents a Realm database. They can either be stored on disk or in memory.

ii) Realm Configuration- A Configuration instance describes the different options used to create an instance of a Realm. They are just plain structs.

iii) Object- An Object is a class that defines Realm Model entities. In Realm, you define your Model classes by subclassing the Object class and adding the required properties to be stored to it.

Create a Service for Handling Realm Operations

RealmManager.swift Part 1/4

For this tutorial, we will just use the schemaVersion and migrationBlock.

  • RealmConfiguration is a struct with configurable variables for various use cases.
  • Schema version helps you keep track of your Realm Database. When you update variables in a Realm Model, you use the migrationBlock to delete or move the existing data.
  • realmInstance() function takes in a configuration and provides a Realm Instance, we will use this instance to perform operations on Realm.

Now, we need to write functions for performing the CRUD operations in Realm. We first create a protocol and add the required functions to it.

We now implement these functions-

RealmManager.swift Part 3/4

Here is a brief explanation about the functions-

i) write() function — This function writes the data to Realm. It takes in a Model that inherits to the Object class. The ADD, DELETE & UPDATE operations use this method.

ii) add(_ object: Object) function— This function adds a single Object to Realm.

iii) add(_ objects: S) function — This function adds a list of Objects to Realm.

iv) get(fromEntity: R) function— This function provides the data from Realm. You need to pass a Model Type to fetch data from Realm. This function also has a predicate parameter that you can pass to filter the data, and it also has a sortKey to sort the data.

v) delete(_ object: Object) function — This function deletes a single Object from Realm.

vi) delete(_ objects: S) function — This function deletes a list of Objects from Realm.

vii) delete(fromEntity: Entity) function— This function takes a Model Type and deletes all entries inside the Entity from Realm. It also takes in an NSPredicate to delete specific entries.

viii) update(_ object: T) function— This function takes an existing Object, you can modify its values inside the block, and it overrides the data inside Realm.

Now, we create functions for performing operations in Realm for our use cases-

i) Fetching Pokemons from Realm, whose id is within the provided offset and a limit.

ii) Fetching Pokemons from Realm, whose name begins with the provided query, sorted by id.

iii) Fetching Pokemons from Realm, whose type contains the provided query, sorted by id.

RealmManager.swift Part 4/4

We have created three functions with different parameters to do the job.

The RealmManager class is complete.

Create Object Model

But first, we will see the structure of the JSON returned by the API-

{
"pokemon": [
{
"id": 1,
"num": "001",
"name": "Bulbasaur",
"img": "http://www.serebii.net/pokemongo/pokemon/001.png",
"type": [
"Grass",
"Poison"
],
"height": "0.71 m",
"weight": "6.9 kg",

},
....
]
}

Okay, before we create a Model for storing this into Realm, check out the properties Realm supports.

Few points to keep in mind when you create a Model for Realm-

i) They must be declared as a class and inherit the Object class.

ii) The properties/variables have to be prefixed with @objc dynamic var.

iii) You need to override the primaryKey() function and set a primary key.

iv) You need to override the ignoredProperties() function and add your additional variables and computed properties in it.

Now, we create a class called Pokemon. Since we also need to decode the JSON Response from the API, we will make our class conform to the Decodable protocol-

Pokemon.swift Part 1/3

Alright, we have declared the variables that store the properties. The type of Pokemon in the JSON response is an array of strings. To save Arrays in Realm, we need to use the List property.

To keep it simple, we join the type array with a separator | and store it in Realm.

When we need the type inside Swift code, we use the computed property pokemonType instead.

To intercept and modify the type Array into a type String(joined with |) , we need to write a custom decoder-

Pokemon.swift Part 2/3

We have declared the CodingKeys, to map the JSON Response to our model. The custom decoder is pretty straightforward. It assigns values to variables based on the set String, CodingKey.

Check how we decode type. First, we use the .type key and assign it to a variable. Then, we use the .joined(separator: "|") function to join the array into a string so that we can assign it to our Realm variable.

Now to complete our model, let us override the primaryKey and ignoredProperties functions-

Pokemon.swift Part 3/3

This completes the Pokemon Model.

Create the UserInterfaces

I) Home Screen

Description of the User Interface

The HomeScreen contains these-

i) A NavigationBar with a Large Title.

ii) Tableview with a custom UITableViewCell with a Xib.

iii) A SearchBar set as the HeaderView of the TableView.

Open Main.storyboard and add the UI Components-

Home.storyboard

We need to do these steps-

  1. Embed the ViewController inside the NavigationController.
  2. Select the NavigationBar under NavigationController and select the Prefers Large Title checkbox.
  3. Add a TableView to the ViewController and make it occupy the full screen.(We will add the SearchBar through code as a TableViewHeader)

4. Add the TableView outlet to the ViewController.

II) TableViewCell

PokemonTableViewCell

The PokemonTableViewCell contains these-

i) PokemonNumberLabel

ii) PokemonNameLabel

iii) PokemonImageView

iv) PokemonType CollectionView

First, we create a custom Cocoa Touch class that subclasses TableViewCell, and select the create XIB box.

Creating a custom TableViewCell with XIB

Once that finishes, open the XIB file and add the UI Components-

PS- Do not forget to add the outlets in the Swift file for the TableView.

III) TypeCollectionViewCell

Custom CollectionViewCell

We create the TypeCollectionViewCell in the same way as we did for the TableViewCell. Open the Xib file for the CollectionView and add a View, and inside that View, add a Label.

Write the Code

We will use the MVVM architecture for the project. It helps to decoupe the UI code from the logic, and it also helps to keep the ViewControllers smaller.

First, we create a Box class.

Box.Swift

The Box class has a value variable, whenever it is set, it calls the bind function and the value is accessible inside the closure.

Now, we implement the API call for fetching the data in the AppDelegate-

AppDelegate.swift

It checks the value of pokemonsStored inside UserDefaults. As it will be false the first time, we call the API to fetch Pokemon Data, store it to Realm, and set the value of the pokemonsStored to true.

storeData() — This function uses the URLSession.shared.dataTask function to fetch the response from the API. We will then decode the data and map the response to conform to the Pokemon Model that we created. If that is successful, we add the data to Realm.

Now, we create the ViewModel.

HomeViewModel.swift Part 1/3

The ViewModel has a realmManager instance to query the Pokemon Entity. It also has two variables to store Pokemons. Initially, both of them contain the same data which is fetched from Realm based on the offset and limit.

Once the user starts querying for Pokemons, the contents of the filteredPokemons change, when the user cancels/closes the SearchBar, the filteredPokemons is updated with the value stored in the pokemons variable.

We now write functions that the ViewController uses for fetching and filtering data-

HomeViewModel.swift Part 2/3

Take a look at the working of these functions-.

i) fetchPokemons() — This function gets called in the init() of the ViewModel. It fetches the Pokemons by their ID with the current offset and limit value. (If the data is not yet stored in Realm, it calls the same function again after one second.)

Once the result comes from Realm, we add a logic to check if the pokemons variable already has existing values(when the user scrolls through the TableView). If its value is nil, then it is directly assigned. Then, the value of the filteredPokemons is updated.

We listen to the filteredPokemons in our ViewController and not the pokemons variable so that we do not need to change our data source based on whether the search is active or not.

ii) searchPokemons() — This function is called by the ViewController when the user enters the input in the searchBar. It checks if the query is not nil. After that, it checks if it begins with a type prefix.

If it does, it calls the Realm function that searches Pokemons by their type. If it does not, it calls the function that searches Pokemons by their name.

If the query is empty, it calls the resetDataIfNeeded() function.

We will write another set of functions for resetting and fetching more data-

iii) resetData() — This function sets the searchState as false and copies the value from the pokemons variable to the filteredPokemons var.

iv) fetchMoreDataIfNeeded() — This function fetches more data as the user scrolls through the app. We fetch the data before the user reaches the fifth last displayed cell to prevent any delay in displaying data.

We also have a condition that checks whether the search is inactive. We do not want to add the Pokemons to the filtered data when the search is active.

Now, we write code for the TableViewCell-

PokemonTableViewCell.swift Part 1/3

Till here, we have declared the outlets, a type property- used by the collectionView. In the awakeFromNib() we register the TypeCollectionViewCell and add a shadow to the containerView.

If you remember the API response, we are getting the URL string for the PokemonImage. We need to use URLSession’s DataTask to get the ImageData, which we set asynchronously.

If we proceed without optimising, it will call the API again for cells as the user scroll through the TableView and comes back.

To avoid that, we create a shared dictionary to cache the PokemonID and PokemonImageData. We name it ImageCache-

ImageCache.swift

Now we can resume working on the PokemonTableViewCell. We will now write the method for customising the cell based on the data-

PokemonTableViewCell.swift Part 2/3

setupCell() — This function takes a parameter of Pokemon type, and sets the data for the UI Components.

It first checks if the image exists in the ImageCache. If the ImageData exists, it sets the PokemonImage. If it does not find anything for the ID, it calls URLSession.shared.dataTask to fetch the Image data, then it saves it in the ImageCache and sets the PokemonImage.

We now will implement and add the data for the TypeCollectionViewCell-

PokemonTableViewCell.swift Part 3/3

Finally, we write the code for the ViewController-

HomeViewController.swift Part 1/3

We have declared a lazy variable for the SearchBar, we are adding that to the TableView by calling the addSearchBarInTableView() in the viewDidLoad().

In this method, we are registering the PokemonTableViewCell and setting the required delegates.

The most important line of code is on lines 39-45, here we are using accessing the filteredPokemons Box variable, and listening to changes on that variable through the Bind function. When the Bind closure is called and the filteredPokemons is not nil, we reload the TableView.

Now, we shall implement the delegate functions for the SearchBar-

HomeViewController.swift Part 2/3

i) searchBar(_ searchBar, textDidChange) — This function calls the Search function of the ViewModel whenever the text of the SearchBar changes.

ii) searchBarCancelButtonClicked(_ searchBar) — This function calls the Reset function of the ViewModel when the Cancel button of the SearchBar is pressed.

Now, we need to implement the TableView delegate functions-

For most of the delegate methods, we check the value of the filteredPokemons and instantiating the TableViewCell.

And we are finished! We have done everything we wanted, and you can now run the project.

Conclusion

Thanks for reading this piece. Hope to see you again in the next article!

Dailyrounds Engineering

Learn about Dailyrounds engineering efforts, work culture, technological developments and more.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store