Skip to content

Commit

Permalink
chore: cache flags, distinct id and anon id in memory to avoid file I…
Browse files Browse the repository at this point in the history
…O every time (#177)
  • Loading branch information
marandaneto authored Sep 2, 2024
1 parent a54c525 commit ba8b00e
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- chore: cache flags, distinct id and anon id in memory to avoid file IO every time ([#177](https://github.com/PostHog/posthog-ios/pull/177))

## 3.8.1 - 2024-08-30

- fix: do not clear events when reset is called ([#175](https://github.com/PostHog/posthog-ios/pull/175))
Expand Down
47 changes: 37 additions & 10 deletions PostHog/PostHogFeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class PostHogFeatureFlags {
private var loadingFeatureFlags = false
private var sessionReplayFlagActive = false

private var featureFlags: [String: Any]?
private var featureFlagPayloads: [String: Any]?

private let dispatchQueue = DispatchQueue(label: "com.posthog.FeatureFlags",
target: .global(qos: .utility))

Expand Down Expand Up @@ -94,18 +97,18 @@ class PostHogFeatureFlags {

self.featureFlagsLock.withLock {
if errorsWhileComputingFlags {
let cachedFeatureFlags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any] ?? [:]
let cachedFeatureFlagsPayloads = self.storage.getDictionary(forKey: .enabledFeatureFlagPayloads) as? [String: Any] ?? [:]
let cachedFeatureFlags = self.getCachedFeatureFlags() ?? [:]
let cachedFeatureFlagsPayloads = self.getCachedFeatureFlagPayload() ?? [:]

let newFeatureFlags = cachedFeatureFlags.merging(featureFlags) { _, new in new }
let newFeatureFlagsPayloads = cachedFeatureFlagsPayloads.merging(featureFlagPayloads) { _, new in new }

// if not all flags were computed, we upsert flags instead of replacing them
self.storage.setDictionary(forKey: .enabledFeatureFlags, contents: newFeatureFlags)
self.storage.setDictionary(forKey: .enabledFeatureFlagPayloads, contents: newFeatureFlagsPayloads)
self.setCachedFeatureFlags(newFeatureFlags)
self.setCachedFeatureFlagPayload(newFeatureFlagsPayloads)
} else {
self.storage.setDictionary(forKey: .enabledFeatureFlags, contents: featureFlags)
self.storage.setDictionary(forKey: .enabledFeatureFlagPayloads, contents: featureFlagPayloads)
self.setCachedFeatureFlags(featureFlags)
self.setCachedFeatureFlagPayload(featureFlagPayloads)
}
}

Expand All @@ -129,7 +132,7 @@ class PostHogFeatureFlags {
func getFeatureFlags() -> [String: Any]? {
var flags: [String: Any]?
featureFlagsLock.withLock {
flags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any]
flags = self.getCachedFeatureFlags()
}

return flags
Expand All @@ -138,7 +141,7 @@ class PostHogFeatureFlags {
func isFeatureEnabled(_ key: String) -> Bool {
var flags: [String: Any]?
featureFlagsLock.withLock {
flags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any]
flags = self.getCachedFeatureFlags()
}

let value = flags?[key]
Expand All @@ -158,16 +161,40 @@ class PostHogFeatureFlags {
func getFeatureFlag(_ key: String) -> Any? {
var flags: [String: Any]?
featureFlagsLock.withLock {
flags = self.storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any]
flags = self.getCachedFeatureFlags()
}

return flags?[key]
}

private func getCachedFeatureFlagPayload() -> [String: Any]? {
if featureFlagPayloads == nil {
featureFlagPayloads = storage.getDictionary(forKey: .enabledFeatureFlagPayloads) as? [String: Any]
}
return featureFlagPayloads
}

private func setCachedFeatureFlagPayload(_ featureFlagPayloads: [String: Any]) {
self.featureFlagPayloads = featureFlagPayloads
storage.setDictionary(forKey: .enabledFeatureFlagPayloads, contents: featureFlagPayloads)
}

private func getCachedFeatureFlags() -> [String: Any]? {
if featureFlags == nil {
featureFlags = storage.getDictionary(forKey: .enabledFeatureFlags) as? [String: Any]
}
return featureFlags
}

private func setCachedFeatureFlags(_ featureFlags: [String: Any]) {
self.featureFlags = featureFlags
storage.setDictionary(forKey: .enabledFeatureFlags, contents: featureFlags)
}

func getFeatureFlagPayload(_ key: String) -> Any? {
var flags: [String: Any]?
featureFlagsLock.withLock {
flags = self.storage.getDictionary(forKey: .enabledFeatureFlagPayloads) as? [String: Any]
flags = getCachedFeatureFlagPayload()
}

let value = flags?[key]
Expand Down
46 changes: 39 additions & 7 deletions PostHog/PostHogStorageManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,28 @@ class PostHogStorageManager {
private let distinctLock = NSLock()
private let idGen: (UUID) -> UUID

private var distinctId: String?
private var cachedDistinctId = false
private var anonymousId: String?

init(_ config: PostHogConfig) {
storage = PostHogStorage(config)
idGen = config.getAnonymousId
}

public func getAnonymousId() -> String {
var anonymousId: String?
anonLock.withLock {
anonymousId = storage.getString(forKey: .anonymousId)

if anonymousId == nil {
let uuid = UUID.v7()
anonymousId = idGen(uuid).uuidString
setAnonId(anonymousId ?? "")
var anonymousId = storage.getString(forKey: .anonymousId)

if anonymousId == nil {
let uuid = UUID.v7()
anonymousId = idGen(uuid).uuidString
setAnonId(anonymousId ?? "")
} else {
// update the memory value
self.anonymousId = anonymousId
}
}
}

Expand All @@ -41,29 +49,53 @@ class PostHogStorageManager {
}

private func setAnonId(_ id: String) {
anonymousId = id
storage.setString(forKey: .anonymousId, contents: id)
}

public func getDistinctId() -> String {
var distinctId: String?
distinctLock.withLock {
distinctId = storage.getString(forKey: .distinctId) ?? getAnonymousId()
if self.distinctId == nil {
// since distinctId is nil until its identified, no need to read from
// cache every single time, otherwise anon users will never used the
// cached values
if !cachedDistinctId {
distinctId = storage.getString(forKey: .distinctId)
cachedDistinctId = true
}

// do this to not assign the AnonymousId to the DistinctId, its just a fallback
if distinctId == nil {
distinctId = getAnonymousId()
} else {
// update the memory value
self.distinctId = distinctId
}
} else {
// read from memory
distinctId = self.distinctId
}
}
return distinctId ?? ""
}

public func setDistinctId(_ id: String) {
distinctLock.withLock {
distinctId = id
storage.setString(forKey: .distinctId, contents: id)
}
}

public func reset() {
distinctLock.withLock {
storage.remove(key: .distinctId)
distinctId = nil
cachedDistinctId = false
}
anonLock.withLock {
storage.remove(key: .anonymousId)
anonymousId = nil
}
}
}
2 changes: 1 addition & 1 deletion PostHogExample/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
// PostHogSDK.shared.capture("App started!")
// PostHogSDK.shared.reset()

PostHogSDK.shared.identify("Manoel")
// PostHogSDK.shared.identify("Manoel")

let defaultCenter = NotificationCenter.default

Expand Down
2 changes: 1 addition & 1 deletion PostHogExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class FeatureFlagsModel: ObservableObject {

struct ContentView: View {
@State var counter: Int = 0
@State private var name: String = "Tim"
@State private var name: String = "Max"
@State private var showingSheet = false
@State private var showingRedactedSheet = false
@StateObject var api = Api()
Expand Down

0 comments on commit ba8b00e

Please sign in to comment.