Skip to content

Instantly share code, notes, and snippets.

@Skorch
Last active December 3, 2016 22:37
Show Gist options
  • Save Skorch/1005d49ad5526fe917fea7ebfdb77625 to your computer and use it in GitHub Desktop.
Save Skorch/1005d49ad5526fe917fea7ebfdb77625 to your computer and use it in GitHub Desktop.
A protocol based solution to capturing analytics in Swift 3 v2
//
// AnalyticsSegmentIOPublisher.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-03.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
import Analytics
class AnalyticsSegmentIOPublisher{
private let serialQueue = DispatchQueue(label: "com.drew.beaupre.dropshift.analytics.segment")
static let sharedInstance: AnalyticsSegmentIOPublisher = {
return AnalyticsSegmentIOPublisher()
}()
func handleEvent(eventType: EventType, eventProperties: [String: Any]?) -> Void{
serialQueue.async{
SEGAnalytics.shared().track(eventType.segmentName, properties: eventProperties)
}
}
}
//
// EventType+GameEnded.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-07.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
extension EventType{
struct GameEnded: PublishableEvent{
//MARK PropertyNames
private enum FieldNames: String{
case opponentType = "Opponent Type",
winningPlayer = "Winning Player",
moveCounts = "Number of Moves",
value = "value"
}
//MARK Properties
var opponentType: PlayerType
var winnningPlayer: GamePlayer
var moveCount: Int
//MARK:- PublishableEvent
var eventProperties: [String: Any]? {
return [
FieldNames.opponentType.rawValue: opponentType.description,
FieldNames.winningPlayer.rawValue: winnningPlayer.description,
FieldNames.moveCounts.rawValue: MoveCountCategory(fromInt: moveCount).description,
FieldNames.value.rawValue: moveCount
]
}
//MARK:- SubscribableEvent
static var eventType: EventType{
return .gameEnded
}
}
}
//
// EventType+GameStarted.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-03.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
extension EventType{
struct GameStarted: PublishableEvent{
//MARK PropertyNames
private enum FieldNames: String{
case opponentType
}
//MARK Properties
private(set) var opponentType: PlayerType
//MARK:- PublishableEvent
var eventProperties: [String: Any]? {
return [
FieldNames.opponentType.rawValue: opponentType.description
]
}
//MARK:- SubscribableEvent
static var eventType: EventType{
return .gameStarted
}
}
}
//
// EventType+SurveyAnswer.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-18.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
extension EventType{
struct SurveyAnswer: PublishableEvent{
//MARK PropertyNames
private enum FieldNames: String{
case question, answer
}
//MARK Properties
private(set) var questionText: String
private(set) var answerValue: String
//MARK:- PublishableEvent
var eventProperties: [String: Any]? {
return [
FieldNames.question.rawValue: questionText,
FieldNames.answer.rawValue: answerValue
]
}
//MARK:- SubscribableEvent
static var eventType: EventType{
return .surveyAnswer
}
}
}
//
// EventType.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-03.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
enum EventType: String{
case gameStarted = "Game Started"
case gameEnded = "Game Ended"
case gameReset = "Game Reset"
case surveyAnswer = "Survey Answer"
init?(notificationName: Notification.Name){
self.init(rawValue: notificationName.rawValue)
}
static func allEventTypes() -> [EventType]{
return [
.gameStarted,
.gameEnded,
.gameReset,
.surveyAnswer
]
}
}
extension EventType{
var notificationName: NSNotification.Name{
return NSNotification.Name(rawValue: self.rawValue)
}
}
extension EventType{
var segmentName: String{
return self.rawValue
}
var gameAnalyticsName: String{
return self.rawValue.lowercased().replacingOccurrences(of: " ", with: ":")
}
}
//
// MoveCountCategory.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-07.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
internal enum MoveCountCategory: String, CustomStringConvertible{
case invalid = "invalid"
case lessThanEight = "< 8"
case eightToFourteen = "8 - 14"
case fifteenToTwenty = "15 - 20"
case twentyOneToThirty = "21 - 30"
case greaterThan30 = "> 30"
init(fromInt value: Int){
switch value{
case 0..<8: self = .lessThanEight
case 8...14: self = .eightToFourteen
case 15...20: self = .fifteenToTwenty
case 21...30: self = .twentyOneToThirty
case 31...Int.max: self = .greaterThan30
default: self = .invalid
}
}
var description: String{
return self.rawValue
}
}
//
// PublishableEvent.swift
// DropShift
//
// Created by Drew Beaupre on 2016-10-03.
// Copyright © 2016 drew.beaupre. All rights reserved.
//
import Foundation
protocol PublishableEvent{
static var eventType: EventType {get}
var eventProperties: [String: Any]? { get }
}
extension PublishableEvent {
func publish() -> Void{
AnalyticsSegmentIOPublisher.sharedInstance.handleEvent(eventType: Self.eventType, eventProperties: eventProperties)
}
var eventProperties: [String: Any]? {
return [String: Any]()
}
}
/*
The goal is to make tracking an event as simple and safe as possible.
Each event type is namespaced within the EventType enum for discoverability
Each event type explicitly lists its necessary property values
Each event type handles all mapping, publishing, concurrency
Event prorties are strongly typed
*/
EventType.GameStarted(opponentType: opponentPlayerType).publish()
EventType.GameEnded(opponentType: opponentPlayerType, winnningPlayer: winners.first!.gamePlayer, moveCount: moves).publish()
@Skorch
Copy link
Author

Skorch commented Dec 3, 2016

A protocol based solution to capturing analytics in Swift 3

There were two main goals for this solution:

Tracking an event as simple and safe as possible.

  • Each event type is namespaced within the EventType enum for discoverability
  • Each event type explicitly lists its necessary property values
  • Each event type handles all mapping, publishing, concurrency
  • Event prorties are strongly typed
EventType.GameStarted(opponentType: opponentPlayerType).publish()
EventType.GameEnded(opponentType: opponentPlayerType, winnningPlayer: winners.first!.gamePlayer, moveCount: moves).publish()

Creating new event types should be simple

  • adhere to open/closed principle
  • be obvious - protocol points out necessary implementation
  • minimal code to define an event
extension EventType{
    
    struct GameStarted: PublishableEvent{
        
//MARK: PublishableEvent
 
        static var eventType: EventType{
            return .gameStarted
        }
    }    
}

Events should support being complex if necessary

extension EventType{
    
    struct GameEnded: PublishableEvent{
        
        private enum FieldNames: String{
            case opponentType = "Opponent Type",
            winningPlayer = "Winning Player",
            moveCounts = "Number of Moves",
            value = "value"
        }
        
        var opponentType: PlayerType
        
        var winnningPlayer: GamePlayer
        
        var moveCount: Int
        
        //MARK:- PublishableEvent
        
        var eventProperties: [String: Any]? {
            return [
                FieldNames.opponentType.rawValue: opponentType.description,
                FieldNames.winningPlayer.rawValue: winnningPlayer.description,
                FieldNames.moveCounts.rawValue: MoveCountCategory(fromInt: moveCount).description,
                FieldNames.value.rawValue: moveCount
            ]
        }
        
        static var eventType: EventType{
            return .gameEnded
        }
    }
    
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment