Why you should abstract any iOS third party libraries
If you have an iOS app, you might have integrated external libraries and tools to help you getting your product ready faster. However your iOS architecture and swift code shouldn’t depend on those libraries.
As many iOS developers, you have implemented external services to your app. It could be a payment system like Stripe, a monitoring tool like NewRelic or Crashlytics, or maybe a tracking platform like Google Analytics.
Those tools are amazing, but let’s say you have now to switch to a new service. Does it mean you have hours to refactor your approach? Would you have to rewrite hundreds of lines of code? If yes, then that’s the reason we should always abstract your implementation for external services.
In this demo, I show from a Firebase integration how to switch it to Fabric for logging events. This approach include how to abstract the implementation to contain third-party libraries into our code and make it easier to migrate.
To prepare our code for this migration, we need to abstract couple elements: our events to send, our loggers and we might also need a manager to keep everything under a singleton.
Assuming we have 3 classic events, landing on home page, signing up and logging in. My view might look like that
import UIKit
import Firebase
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
FirebaseApp.configure()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
Analytics.logEvent("homepage", parameters: nil)
}
func didLogin() {
Analytics.logEvent("login", parameters: ["userID" : "1234567890ABC"])
}
func didSignUp() {
Analytics.logEvent("signup", parameters: ["userID" : "1234567890ABC"])
}
}
That’s what we would like to avoid. Here if we change for a new tool, we have a lot of work to move, and probably the same approach for each pages.
Let’s abstract our Firebase implementation
enum Events : String {
case login
case home
case sign_up
}
protocol LoggerProtocol {
func setup()
func log(event: Events, parameters: [String: Any]?)
}
Here I just designed events I want to recognise and a protocol to make it generic across the app. I’m ready to reimplement Firebase the right way.
import Firebase
struct FirebaseLogger : LoggerProtocol {
func setup() {
FirebaseApp.configure()
}
func log(event: Events, parameters: [String : Any]? = nil) {
Analytics.logEvent(event.rawValue, parameters: parameters)
}
}
I also created a LoggerManager to keep every setup and logger under the same umbrella, using a singleton to get access to it.
class LoggerManager : LoggerProtocol {
static let shared : LoggerManager = {
var manager = LoggerManager()
manager.loggers.append(FirebaseLogger())
manager.setup()
return manager
}()
private var loggers : [LoggerProtocol] = []
internal func setup() {
self.loggers.forEach({ $0.setup() })
}
func log(event: Events, parameters: [String : Any]? = nil) {
self.loggers.forEach({ $0.log(event: event, parameters: parameters) })
}
}
Let’s now update our view, removing as much as possible dependencies from the tool itself.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
LoggerManager.shared.log(event: .home)
}
func didLogin() {
LoggerManager.shared.log(event: .login, parameters: ["userID" : "1234567890ABC"])
}
func didSignUp() {
LoggerManager.shared.log(event: .signup, parameters: ["userID" : "1234567890ABC"])
}
}
It looks like we’re good to go, no more dependency to Firebase in our view.
We’re good to migrate to any new tools.
Here is my Fabric logger
import Fabric
import Answers
struct FabricLogger : LoggerProtocol {
func setup() {
Fabric.with([Answers.self])
}
func log(event: Events, parameters: [String : Any]?) {
Answers.logCustomEvent(withName: event.rawValue, customAttributes: parameters)
}
}
Don’t forget to update your LoggerManager, remove Firebase one for Fabric.
static let shared : LoggerManager = {
var manager = LoggerManager()
manager.loggers.append(FabricLogger())
manager.setup()
return manager
}()
As you can see the code is working as before and we didn’t have to update the view side.
In conclusion, we manage to abstract our third-party library integrations to make it easier to maintain and to reuse. We could actually keep Firebase and Fabric together, and limit their logging if we had a specific approach.
My main goal here was to show you how to avoid your iOS projects and swift code being dependant of other tools. If not convinced, another good reason is it’s time saving. You don’t want to spend couple days every year to re-implement a new tool depending on what’s new on the market.