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-
- Working with Realm is much easier than with CoreData.
- Model creation is easy. You only need to inherit from the Object class and implement the required variable and functions.
- Realm has its engine, quick and efficient. Thanks to its zero-copy design, Realm is much faster than CoreData.
- 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-
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.
- MacBook and Xcode.
- 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.
Let me quickly give you guys a brief introduction about the functionalities that we will add to the Pokemon Search app.
- 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.
- We will provide the ability to search the Pokemon by its name.
- 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=.
We start by creating an iOS application and choosing the interface as Storyboard.
Add Realm as a Dependency
We will use the Swift Package Manager to add the Realm dependency to the project.
- Inside XCode > File > Swift Packages > Add Package Dependency.
- Enter “https://github.com/realm/realm-cocoa” as the repository URL, and press next.
- We do not need to do anything to the version, press next.
Once the package has been added, choose Realm-Swift and press finish.
Note- If you are more comfortable with using CocoaPods, use
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
We will start by creating a service class for handling everything related to Realm. We will name it RealmManager.
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-
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.
We have created three functions with different parameters to do the job.
The RealmManager class is complete.
Create Object Model
We start by creating a Swift Model for storing data into Realm.
But first, we will see the structure of the JSON returned by the API-
"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-
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
To intercept and modify the type Array into a type String(joined with
|) , we need to write a custom decoder-
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-
This completes the Pokemon Model.
Create the UserInterfaces
Now, we create the User Interface through storyboard and XIB.
I) Home Screen
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-
We need to do these steps-
- Embed the ViewController inside the NavigationController.
- Select the NavigationBar under NavigationController and select the Prefers Large Title checkbox.
- 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.
The PokemonTableViewCell contains these-
iv) PokemonType CollectionView
First, we create a custom Cocoa Touch class that subclasses TableViewCell, and select the create XIB box.
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.
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 now use the RealmManager functions and write functionalities for the ViewControllers and Cells.
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.
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-
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.
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-
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
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-
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-
Now we can resume working on the PokemonTableViewCell. We will now write the method for customising the cell based on the data-
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-
Finally, we write the code for the ViewController-
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-
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.
1. GitHub Repository for the Pokemon Search app -
2. MVVM Architecture
I hope you found this article useful. It took me a fair amount of time to write this, and I hope you learned about Realm and picked up some new tricks.
Thanks for reading this piece. Hope to see you again in the next article!