This is a response to Dave DeLong’s article, which is itself a response to Matt Diephouse’s article, which is itself a response to John Sundell’s article. You should go read these first.
Dave starts off by saying:
Matt starts off by saying:
Earlier this week, John Sundell wrote a nice article about building an enum-based analytics system in Swift. He included many fine suggestions, but I believe he’s wrong about one point: that enums are the right choice.
Therefore, I’ll start similarly:
Earlier this week, Matt Diephouse wrote a nice article about building a struct-based analytics system in Swift. He included many fine suggestions, but I believe he’s wrong about one point: that structs are the right choice.
Therefore, I’ll start similarly:
Earlier this week, Dave DeLong wrote a nice article about building a protocol-based analytics system in Swift. He included many fine suggestions, but I believe he’s wrong about one point: that protocols are the right choice.
As examples in both articles show, analytic events can have different payloads of information that they’re going to capture. While you can use many different approaches to solve this problem, I believe creating a deep inheritance hierarchy is the best solution.
Using reference types (class
in Swift) with an inheritance hierarchy yields all the upsides of the other solutions. Like John’s solution of enums, they can store data on a per-event basis. Like Matt’s solution, you can create new analytics events across module boundaries. And like Dave’s solution, you can build a hierarchy of categorization for your analytics events.
However, in addition to all these benefits, subclassing brings a few extra benefits the other solutions don’t have. Subclassing allows you to store new information with each layer of your hierarchy. Let’s take a look at the metadata specifically. If you have a base class called AnalyticsEvent
, a subclass for NetworkEvent
, a subclass for NetworkErrorEvent
, and a subclass for NoInternetNetworkErrorEvent
, each subclass can bring its own components to the metadata
. For example:
open class AnalyticsEvent {
var name: String {
fatalError("name must be provided by a subclass")
}
var metadata: [String: Any] {
return ["AppVersion": version]
}
}
open class NetworkEvent: AnalyticsEvent {
var urlSessionConfiguration: URLSessionConfiguration
override var metadata: [String: Any] {
return super
.metadata
.merging(["UserAgent": userAgent]) { (_, new) in new }
}
}
open class NetworkErrorEvent: NetworkEvent {
var error: Error
override var metadata: [String: Any] {
return super
.metadata
.merging(["ErrorCode": error.code]) { (_, new) in new }
}
}
open class NoInternetNetworkErrorEvent: NetworkErrorEvent {
override var name = "NoInternetNetworkErrorEvent"
override var metadata: [String: Any] {
return super
.metadata
.merging(["Message": "No Internet"]) { (_, new) in new }
}
}
As you can see, this reduces duplication between various types of analytics events. Each layer refers to the layers above it, in an almost recursive style.
I hope this article has convinced you to try subclassing to solve your next problem. While Swift gives us many different ways to solve a problem, I’m confident that a deep inheritance hierarchy is the solution for this one.