My Dwelo iOS Journey

shinan liu
Dwelo Research and Development
6 min readJan 24, 2021

Introduction

Before joining Dwelo R&D as an iOS intern, the only thing I knew about iOS development was textbook iOS architecture: the MVC pattern and using Core Data. At Dwelo, we need to deal with lots of sensors and user events, which is challenging if we still using the delegate methods to get the job done. In this post, we will show the frameworks, tools, and platforms we adopted in our Dwelo iOS app by using some basic examples.

1. MVVM and RxSwift

When developing modern iOS applications, developers always need to deal with live states in the app to receive user events or make API calls. But when the app becomes complex, we do not want to have a giant ViewController which is hard to maintain and test. In our app, we use the MVVM (Model View ViewModel) architecture, compared with the traditional MCV pattern, in which there is a layer called ViewModel to handle all data the view will display. When developing against the Apple SDK using MVVM pattern, most of the time ViewControllers’ are only responsible for presenting data, and we process all the data in our ViewModel.

To achieve the MVVM architecture we use RxSwift, which is the reactive framework for Swift. In the reactive world, every state we care about is a source that can emit events when it is changed. And if the state keeps changing, all the events will compose a sequence, and we can subscribe to the sequence and do what we want to like changing view, model, or view model. And yes, this is classic Observer design pattern, which defines a one-to-many dependency between objects. When the subscribed object changes, which in our case means the state changes, all the subscribers will be notified.

Here are some code snippets to help you better understand the idea. We want to “validate our text field by ensuring the input count is greater than 8”, then the button will be enabled.

In our ViewModel:

we receive the input of our TextField as a String sequence, and we also define a boolean driver to control the button in ViewController.

The map function here just transfers this String sequence to a Boolean sequence to verify whether the user had inputted more than 8 characters.

In our ViewController:

In our bindModel function, we bind our whether our button is enabled to our driver

This is the basic idea. A driver is just a special Observable for UIElements which will never error and always be observed on the main thread.

And there is also a stage where we can combine multiple drivers. One use case is that in our dashboard we need to manage different IoT devices for users, including locks, lights, and thermostats. There are also TableView cells like PortalLink for the manager, cell status, and review card. To implement this, every cell has its own driver to tell the View whether it should show up, and we are combining all cell drivers into one. In this manner, even if there is a change in either one of the cells, we can detect that and reload the table data.

The same design pattern is also used in error handling. First, we subscribe to different errors from their drivers. Second, we merge them into one driver. Finally, in our ViewController, we observe the single error driver and handle it. Because of the asynchronous particularity of RxSwift we also use it in API calls alike.

2. The Realm Database

To deal with synchronization for the sensors in the IoT devices we are using Realm for our mobile database. Realm is a third-party database that is high speed to use compared to Core Data. When there is a change for the sensor, we write it into our database and there is an observer that emits an event telling the view to change the UI. The relational feature of CoreData isn’t well-suited to our needs: data consistency, speed, and security. Data in Realm is well synchronized and easy to observe. Realm lets our mobile code be much simpler, and we don’t need to deal with the complex Core Data API. All sensors have their own recoding block be saved and read immediately in Realm, which will lower the chances of data inconsistency. When we receive a sensor state change from our backend, we just simply write our database to update the sensor state, and the job of the API call is done. Our subscribers who are observing these objects in Realm will see that modification and make the change we need. By doing this we can make our code loosely coupled, which has the advantages of being easy to maintain and scale.

3. JSONDecoder

To send user commands and transfer data to our backend service, we need an efficient solution to model our data. Before iOS 7.0 there were third-party solutions like ObjectMapper. But after iOS 7.0 Apple launched an official framework to let developers decode data easier. We are using JSONDecoder to decode complex JSON from our backend API. There may be nested JSON with arbitrary types to deal with. In the native JSONDecoder API, we can easily model our JSON data to Structs that conform to Decodable protocol.

Here is an example of the basic case which does not need the container decoder and does not need to deal with arbitrary types.

Our hub sends wifi connection data through our backend, which has three fields:

UUID: a String that shows the unique id of this wifi connection

SSID: a String that shows the Wifi the SSID hub is connected to

is_active: which is a boolean that shows whether the connection is activated

we can simply map our JSON data to a struct like this:

a struct that conforms to the Decodable protocol

4. Firebase

For the cloud service, we integrate with Firebase in a few ways. Firebase helps us analyze our app events and crash reports. It can also unlock configuration flexibility. We use Firebase as a remote config tool so that we can change some app settings without requiring users to upgrade their apps. For example, the support URL is stored in our app in Firebase, but when we want that URL to point to another endpoint we can just change that in Firebase and do not need to change the code.

5. CI/CD

After all the work has been done, how can we ship our app in a modern way? To deal with provisioning profiles, certifications, and even deploying to the Apple app store, we are using Fastlane, Gitflow, and Travis to accomplish these jobs easier. Every time we push code to a git repository, it triggers a Travis build after passing all the automation tests, which results in a new distribution in Firebase for us to test on real devices. And if it is a “release” branch Fastlane will run the release lane we defined in the Fastfile, and upload the binary file to the App store.

Conclusion

In this internship I learned the whole pipeline of iOS development, the Reactive framework and MVVM pattern. But our Dwelo app journey does not end here. With great tools emerging in the industry, iOS developers will have more chances to focus on the core features of their apps. Apple also launched the SwiftUI and Combine framework recently. Instead of working on a complex and massive storyboard, developers can mash it into single SwiftUI views which are easy to implement and even support real-time manipulation in Xcode canvas. In a nutshell, technologies should always serve people, and we strive for our iOS app to make pragmatic use of new technologies.

--

--