From 971fd087e914e539aaf324ffa5b6054c4e19d1a5 Mon Sep 17 00:00:00 2001 From: Daniel <95646168+daniel-statsig@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:55:56 -0800 Subject: [PATCH] Use 'weak' for references captured by closures (#222) * Weak-ref in all closures * Spawn many tasks --- .../ExampleDirectoryViewController.swift | 2 + .../Examples/ManyUpdatesViewController.swift | 64 +++++++++++++++++++ .../StatsigSamples.xcodeproj/project.pbxproj | 4 ++ Sources/Statsig/EventLogger.swift | 10 +-- Sources/Statsig/InternalStore.swift | 9 +-- Sources/Statsig/NetworkService.swift | 3 +- Sources/Statsig/Statsig.swift | 2 +- Sources/Statsig/StatsigClient.swift | 8 ++- 8 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 Sample/App/Examples/ManyUpdatesViewController.swift diff --git a/Sample/App/Examples/ExampleDirectoryViewController.swift b/Sample/App/Examples/ExampleDirectoryViewController.swift index cae64b9..0602f8d 100644 --- a/Sample/App/Examples/ExampleDirectoryViewController.swift +++ b/Sample/App/Examples/ExampleDirectoryViewController.swift @@ -9,6 +9,8 @@ let Examples: [(String, UIViewController)] = [ ( "Basic (ObjC)", BasicViewControllerObjC() ), ( "Perf (ObjC)", PerfViewControllerObjC() ), ( "Many Gates (SwiftUI)", ManyGatesSwiftUIViewController() ), + ( "Many Updates (Swift)", ManyUpdatesViewController() ), + ] diff --git a/Sample/App/Examples/ManyUpdatesViewController.swift b/Sample/App/Examples/ManyUpdatesViewController.swift new file mode 100644 index 0000000..95b47cb --- /dev/null +++ b/Sample/App/Examples/ManyUpdatesViewController.swift @@ -0,0 +1,64 @@ +import UIKit +import Statsig + +class ManyUpdatesViewController: UIViewController { + + var queues: [DispatchQueue] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.white + + let user = StatsigUser(userID: "a-user") + let opts = StatsigOptions(enableCacheByFile: true) + + Statsig.start( + sdkKey: Constants.CLIENT_SDK_KEY, + user: user, + options: opts + ) { [weak self] err in + if let err = err { + print("Error \(err)") + } + + self?.addButton() + } + } + + private func addButton() { + let button = UIButton(type: .system) + button.frame = CGRect(x: 100, y: 100, width: 200, height: 50) + button.setTitle("Click", for: .normal) + button.addTarget(self, action: #selector(runUpdates), for: .touchUpInside) + view.addSubview(button) + } + + @objc private func runUpdates() { + let numberOfTasks = 10 + + if (queues.isEmpty) { + for i in 0.. StatsigUser { + StatsigUser(userID: "user_\(Int.random(in: 1...100))") + } +} + diff --git a/Sample/StatsigSamples.xcodeproj/project.pbxproj b/Sample/StatsigSamples.xcodeproj/project.pbxproj index ed89730..f44b1b1 100644 --- a/Sample/StatsigSamples.xcodeproj/project.pbxproj +++ b/Sample/StatsigSamples.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 5C68BE072AF9A6DC006FD9B5 /* ManyGatesSwiftUIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C68BE062AF9A6DC006FD9B5 /* ManyGatesSwiftUIViewController.swift */; }; 5C68BFBA2AFAA68B006FD9B5 /* ExampleDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C68BFB92AFAA68B006FD9B5 /* ExampleDirectoryViewController.swift */; }; 5C8094602ADF17F2001AF600 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C80945F2ADF17F2001AF600 /* Constants.swift */; }; + 5C9C31A02B17970900FA7212 /* ManyUpdatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9C319F2B17970900FA7212 /* ManyUpdatesViewController.swift */; }; 5CAFD04F2ADAEEDD00ACAF79 /* PerfViewControllerObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CAFD04E2ADAEEDD00ACAF79 /* PerfViewControllerObjC.m */; }; 5CD93AB02AF953C2009A5898 /* StatsigSamplesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CD93AAF2AF953C2009A5898 /* StatsigSamplesTests.swift */; }; 5CDBFC9A2AD4EFE0002E6E60 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDBFC992AD4EFE0002E6E60 /* AppDelegate.swift */; }; @@ -37,6 +38,7 @@ 5C68BE062AF9A6DC006FD9B5 /* ManyGatesSwiftUIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManyGatesSwiftUIViewController.swift; sourceTree = ""; }; 5C68BFB92AFAA68B006FD9B5 /* ExampleDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleDirectoryViewController.swift; sourceTree = ""; }; 5C80945F2ADF17F2001AF600 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 5C9C319F2B17970900FA7212 /* ManyUpdatesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManyUpdatesViewController.swift; sourceTree = ""; }; 5CAFD04D2ADAEEDD00ACAF79 /* PerfViewControllerObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerfViewControllerObjC.h; sourceTree = ""; }; 5CAFD04E2ADAEEDD00ACAF79 /* PerfViewControllerObjC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PerfViewControllerObjC.m; sourceTree = ""; }; 5CD93AAD2AF953C2009A5898 /* StatsigSamplesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StatsigSamplesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -83,6 +85,7 @@ 5C10DDA82AD4FB60003C4CE1 /* BasicViewControllerObjC.m */, 5CAFD04D2ADAEEDD00ACAF79 /* PerfViewControllerObjC.h */, 5CAFD04E2ADAEEDD00ACAF79 /* PerfViewControllerObjC.m */, + 5C9C319F2B17970900FA7212 /* ManyUpdatesViewController.swift */, 5CDBFC9D2AD4EFE0002E6E60 /* BasicViewController.swift */, 5C68BE062AF9A6DC006FD9B5 /* ManyGatesSwiftUIViewController.swift */, ); @@ -253,6 +256,7 @@ 5C68BE072AF9A6DC006FD9B5 /* ManyGatesSwiftUIViewController.swift in Sources */, 5CAFD04F2ADAEEDD00ACAF79 /* PerfViewControllerObjC.m in Sources */, 5CDBFC9E2AD4EFE0002E6E60 /* BasicViewController.swift in Sources */, + 5C9C31A02B17970900FA7212 /* ManyUpdatesViewController.swift in Sources */, 5CDBFC9A2AD4EFE0002E6E60 /* AppDelegate.swift in Sources */, 5C68BFBA2AFAA68B006FD9B5 /* ExampleDirectoryViewController.swift in Sources */, 5C8094602ADF17F2001AF600 /* Constants.swift in Sources */, diff --git a/Sources/Statsig/EventLogger.swift b/Sources/Statsig/EventLogger.swift index 1dc7911..1e992dd 100644 --- a/Sources/Statsig/EventLogger.swift +++ b/Sources/Statsig/EventLogger.swift @@ -36,9 +36,9 @@ class EventLogger { userDefaults.removeObject(forKey: EventLogger.loggingRequestUserDefaultsKey) networkService.sendRequestsWithData(failedRequestQueue) { [weak self] failedRequestsData in - guard let failedRequestsData = failedRequestsData, let self = self else { return } - DispatchQueue.main.async { - self.addFailedLogRequest(failedRequestsData) + guard let failedRequestsData = failedRequestsData else { return } + DispatchQueue.main.async { [weak self] in + self?.addFailedLogRequest(failedRequestsData) } } } @@ -75,8 +75,8 @@ class EventLogger { } func flush() { - logQueue.async { - self.flushInternal(shutdown: false) + logQueue.async { [weak self] in + self?.flushInternal(shutdown: false) } } diff --git a/Sources/Statsig/InternalStore.swift b/Sources/Statsig/InternalStore.swift index 4ab827c..8c3f180 100644 --- a/Sources/Statsig/InternalStore.swift +++ b/Sources/Statsig/InternalStore.swift @@ -389,11 +389,12 @@ class InternalStore { func saveValues(_ values: [String: Any], _ cacheKey: UserCacheKey, _ userHash: String?, _ completion: (() -> Void)? = nil) { storeQueue.async(flags: .barrier) { [weak self] in - guard let self = self else { return } - self.cache.saveValues(values, cacheKey, userHash) - DispatchQueue.global().async { - self.cache.writeToStorage() + self?.cache.saveValues(values, cacheKey, userHash) + + DispatchQueue.global().async { [weak self] in + self?.cache.writeToStorage() } + DispatchQueue.main.async { completion?() } diff --git a/Sources/Statsig/NetworkService.swift b/Sources/Statsig/NetworkService.swift index bdd77ab..5ff1f79 100644 --- a/Sources/Statsig/NetworkService.swift +++ b/Sources/Statsig/NetworkService.swift @@ -246,10 +246,11 @@ class NetworkService { completion: @escaping NetworkCompletionHandler, taskCapture: TaskCaptureHandler ) { - DispatchQueue.main.async { + DispatchQueue.main.async { [weak self] in let currentAttempt = failedAttempts + 1 marker?.start(attempt: currentAttempt) + let task = URLSession.shared.dataTask(with: request) { [weak self] responseData, response, error in diff --git a/Sources/Statsig/Statsig.swift b/Sources/Statsig/Statsig.swift index 622a612..950f3f6 100644 --- a/Sources/Statsig/Statsig.swift +++ b/Sources/Statsig/Statsig.swift @@ -48,7 +48,7 @@ public class Statsig { } if options?.enableCacheByFile == true { - DispatchQueue.main.async { + DispatchQueue.main.async { StatsigUserDefaults.defaults = FileBasedUserDefaults() _initialize() } diff --git a/Sources/Statsig/StatsigClient.swift b/Sources/Statsig/StatsigClient.swift index 771c023..8a92191 100644 --- a/Sources/Statsig/StatsigClient.swift +++ b/Sources/Statsig/StatsigClient.swift @@ -164,8 +164,10 @@ public class StatsigClient { "evalReason": "\(self.store.cache.reason)" ] - DispatchQueue.main.async { - DebugViewController.show(self.sdkKey, state) + DispatchQueue.main.async { [weak self] in + if let self = self { + DebugViewController.show(self.sdkKey, state) + } } } @@ -705,7 +707,7 @@ extension StatsigClient { store.updateUser(currentUser) logger.user = currentUser - DispatchQueue.main.async { [weak self, completion] in + DispatchQueue.main.async { [weak self] in self?.fetchValuesFromNetwork { [weak self, completion] error in guard let self = self else { return