From 2fb6ad618549a7bf2ff58a8eceda2bf07e3789f5 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 15 Apr 2024 14:07:06 +0200 Subject: [PATCH 1/3] chore: fix strong concurrency warnings --- PostHog.xcodeproj/project.pbxproj | 10 +++++++ PostHog/Models/PostHogEvent.swift | 2 +- PostHog/PostHogApi.swift | 8 +++--- PostHog/PostHogConsumerPayload.swift | 2 +- PostHog/PostHogContext.swift | 26 ++++++++----------- PostHog/PostHogFeatureFlags.swift | 4 +-- PostHog/PostHogQueue.swift | 4 +-- PostHog/PostHogSDK.swift | 20 +++++++------- PostHog/PostHogVersion.swift | 4 +-- PostHog/Replay/PostHogReplayIntegration.swift | 8 +++--- PostHog/Replay/UIApplicationTracker.swift | 3 ++- PostHog/Replay/URLSessionInterceptor.swift | 7 ++--- PostHog/Replay/URLSessionSwizzler.swift | 8 +++--- PostHog/Replay/ViewLayoutTracker.swift | 5 ++-- PostHog/Utils/Hedgelog.swift | 2 +- PostHog/Utils/PostHogAnySendable.swift | 16 ++++++++++++ PostHog/Utils/Reachability.swift | 6 ++--- 17 files changed, 81 insertions(+), 54 deletions(-) create mode 100644 PostHog/Utils/PostHogAnySendable.swift diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index 6d1a5da44..a5b6a98e6 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ 699991952AFE1B56000DCB78 /* PostHog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 6999919A2AFE1BAB000DCB78 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699991992AFE1BAB000DCB78 /* AppDelegate.swift */; }; 69BA38D72B888E8500AA69D6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */; }; + 69BDF1942BCD45EA00ED15FA /* PostHogAnySendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69BDF1932BCD45EA00ED15FA /* PostHogAnySendable.swift */; }; 69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */; }; 69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */; }; 69EE82BE2BA9C8AA00EB9542 /* ViewLayoutTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */; }; @@ -327,6 +328,7 @@ 699991902AFE1B39000DCB78 /* PostHogExampleMacOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PostHogExampleMacOS.entitlements; sourceTree = ""; }; 699991992AFE1BAB000DCB78 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 69BDF1932BCD45EA00ED15FA /* PostHogAnySendable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnySendable.swift; sourceTree = ""; }; 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogReplayIntegration.swift; sourceTree = ""; }; 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionReplayConfig.swift; sourceTree = ""; }; 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLayoutTracker.swift; sourceTree = ""; }; @@ -470,6 +472,7 @@ 690FF0BE2AEFA97F00A0B06B /* FileUtils.swift */, 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */, 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */, + 69BDF1932BCD45EA00ED15FA /* PostHogAnySendable.swift */, ); path = Utils; sourceTree = ""; @@ -1063,6 +1066,7 @@ 69F23A742BB3088E001194F6 /* URLSessionSwizzler.swift in Sources */, 69F518142BAC7F4300F52C14 /* Date+Util.swift in Sources */, 69F5183A2BB2BA8300F52C14 /* UIApplicationTracker.swift in Sources */, + 69BDF1942BCD45EA00ED15FA /* PostHogAnySendable.swift in Sources */, 69F518182BAC80A300F52C14 /* String+Util.swift in Sources */, 69F5181A2BAC81FC00F52C14 /* UITextInputTraits+Util.swift in Sources */, 69F517E82BAC675800F52C14 /* RRWireframe.swift in Sources */, @@ -1252,6 +1256,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 13.0; @@ -1287,6 +1292,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 13.0; @@ -1354,6 +1360,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TVOS_DEPLOYMENT_TARGET = 13.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1415,6 +1422,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; TVOS_DEPLOYMENT_TARGET = 13.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -1458,6 +1466,7 @@ SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 13.0; @@ -1500,6 +1509,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 13.0; diff --git a/PostHog/Models/PostHogEvent.swift b/PostHog/Models/PostHogEvent.swift index 3446cde87..2ee3a5b4d 100644 --- a/PostHog/Models/PostHogEvent.swift +++ b/PostHog/Models/PostHogEvent.swift @@ -7,7 +7,7 @@ import Foundation -public class PostHogEvent { +public class PostHogEvent: @unchecked Sendable { public var event: String public var distinctId: String public var properties: [String: Any] diff --git a/PostHog/PostHogApi.swift b/PostHog/PostHogApi.swift index 79d00c134..e75a47b61 100644 --- a/PostHog/PostHogApi.swift +++ b/PostHog/PostHogApi.swift @@ -7,7 +7,7 @@ import Foundation -class PostHogApi { +class PostHogApi: @unchecked Sendable { private let config: PostHogConfig // default is 60s but we do 10s @@ -35,7 +35,7 @@ class PostHogApi { return request } - func batch(events: [PostHogEvent], completion: @escaping (PostHogBatchUploadInfo) -> Void) { + func batch(events: [PostHogEvent], completion: @escaping @Sendable (PostHogBatchUploadInfo) -> Void) { guard let url = URL(string: "batch", relativeTo: config.host) else { hedgeLog("Malformed batch URL error.") return completion(PostHogBatchUploadInfo(statusCode: nil, error: nil)) @@ -92,7 +92,7 @@ class PostHogApi { }.resume() } - func snapshot(events: [PostHogEvent], completion: @escaping (PostHogBatchUploadInfo) -> Void) { + func snapshot(events: [PostHogEvent], completion: @escaping @Sendable (PostHogBatchUploadInfo) -> Void) { guard let url = URL(string: config.snapshotEndpoint, relativeTo: config.host) else { hedgeLog("Malformed snapshot URL error.") return completion(PostHogBatchUploadInfo(statusCode: nil, error: nil)) @@ -153,7 +153,7 @@ class PostHogApi { distinctId: String, anonymousId: String, groups: [String: String], - completion: @escaping ([String: Any]?, _ error: Error?) -> Void + completion: @escaping @Sendable ([String: Any]?, _ error: Error?) -> Void ) { var urlComps = URLComponents() urlComps.path = "/decide" diff --git a/PostHog/PostHogConsumerPayload.swift b/PostHog/PostHogConsumerPayload.swift index 923752fdf..fd12972f1 100644 --- a/PostHog/PostHogConsumerPayload.swift +++ b/PostHog/PostHogConsumerPayload.swift @@ -9,5 +9,5 @@ import Foundation struct PostHogConsumerPayload { let events: [PostHogEvent] - let completion: (Bool) -> Void + let completion: @Sendable (Bool) -> Void } diff --git a/PostHog/PostHogContext.swift b/PostHog/PostHogContext.swift index 4f0c9149d..4c296d4cf 100644 --- a/PostHog/PostHogContext.swift +++ b/PostHog/PostHogContext.swift @@ -13,12 +13,20 @@ import Foundation import AppKit #endif -class PostHogContext { +class PostHogContext: @unchecked Sendable { #if !os(watchOS) private let reachability: Reachability? #endif - private lazy var theStaticContext: [String: Any] = { + #if !os(watchOS) + init(_ reachability: Reachability?) { + self.reachability = reachability + } + #else + init() {} + #endif + + @MainActor func staticContext() -> [String: Any] { // Properties that do not change over the lifecycle of an application var properties: [String: Any] = [:] @@ -80,18 +88,6 @@ class PostHogContext { #endif return properties - }() - - #if !os(watchOS) - init(_ reachability: Reachability?) { - self.reachability = reachability - } - #else - init() {} - #endif - - func staticContext() -> [String: Any] { - theStaticContext } private func platform() -> String { @@ -102,7 +98,7 @@ class PostHogContext { return String(cString: machine) } - func dynamicContext() -> [String: Any] { + @MainActor func dynamicContext() -> [String: Any] { var properties: [String: Any] = [:] #if os(iOS) || os(tvOS) diff --git a/PostHog/PostHogFeatureFlags.swift b/PostHog/PostHogFeatureFlags.swift index bbffb0bb0..9c71336f9 100644 --- a/PostHog/PostHogFeatureFlags.swift +++ b/PostHog/PostHogFeatureFlags.swift @@ -7,7 +7,7 @@ import Foundation -class PostHogFeatureFlags { +class PostHogFeatureFlags: @unchecked Sendable { private let config: PostHogConfig private let storage: PostHogStorage private let api: PostHogApi @@ -38,7 +38,7 @@ class PostHogFeatureFlags { distinctId: String, anonymousId: String, groups: [String: String], - callback: @escaping () -> Void + callback: @escaping @Sendable () -> Void ) { isLoadingLock.withLock { if self.isLoadingFeatureFlags { diff --git a/PostHog/PostHogQueue.swift b/PostHog/PostHogQueue.swift index add4b846d..2171fda03 100644 --- a/PostHog/PostHogQueue.swift +++ b/PostHog/PostHogQueue.swift @@ -17,7 +17,7 @@ import Foundation */ -class PostHogQueue { +class PostHogQueue: @unchecked Sendable { enum PostHogApiEndpoint: Int { case batch case snapshot @@ -221,7 +221,7 @@ class PostHogQueue { flushIfOverThreshold() } - private func take(_ count: Int, completion: @escaping (PostHogConsumerPayload) -> Void) { + private func take(_ count: Int, completion: @escaping @Sendable (PostHogConsumerPayload) -> Void) { dispatchQueue.async { self.isFlushingLock.withLock { if self.isFlushing { diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index b8398fab7..09f17ac66 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -19,7 +19,8 @@ let maxRetryDelay = 30.0 private let sessionChangeThreshold: TimeInterval = 60 * 30 // renamed to PostHogSDK due to https://github.com/apple/swift/issues/56573 -@objc public class PostHogSDK: NSObject { +// @unchecked because its operations are manually locked +@objc public class PostHogSDK: NSObject, @unchecked Sendable { private var config: PostHogConfig private init(_ config: PostHogConfig) { @@ -45,7 +46,8 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 private var flagCallReported = Set() private var featureFlags: PostHogFeatureFlags? private var context: PostHogContext? - private static var apiKeys = Set() + // nonisolated because its manually locked with setupLock + private nonisolated(unsafe) static var apiKeys = Set() private var capturedAppInstalled = false private var appFromBackground = false private var sessionId: String? @@ -74,7 +76,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 toggleHedgeLog(enabled) } - @objc public func setup(_ config: PostHogConfig) { + @MainActor @objc public func setup(_ config: PostHogConfig) { setupLock.withLock { toggleHedgeLog(config.debug) if enabled { @@ -617,7 +619,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 } @objc(reloadFeatureFlagsWithCallback:) - public func reloadFeatureFlags(_ callback: @escaping () -> Void) { + public func reloadFeatureFlags(_ callback: @escaping @Sendable () -> Void) { if !isEnabled() { return } @@ -757,7 +759,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 return config.optOut } - @objc public func close() { + @MainActor @objc public func close() { if !isEnabled() { return } @@ -799,13 +801,13 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 } } - @objc public static func with(_ config: PostHogConfig) -> PostHogSDK { + @MainActor @objc public static func with(_ config: PostHogConfig) -> PostHogSDK { let postHog = PostHogSDK(config) postHog.setup(config) return postHog } - private func unregisterNotifications() { + @MainActor private func unregisterNotifications() { let defaultCenter = NotificationCenter.default #if os(iOS) || os(tvOS) @@ -819,7 +821,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 #endif } - private func registerNotifications() { + @MainActor private func registerNotifications() { let defaultCenter = NotificationCenter.default #if os(iOS) || os(tvOS) @@ -852,7 +854,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 #endif } - private func captureScreenViews() { + @MainActor private func captureScreenViews() { if config.captureScreenViews { #if os(iOS) || os(tvOS) UIViewController.swizzleScreenView() diff --git a/PostHog/PostHogVersion.swift b/PostHog/PostHogVersion.swift index f33266e90..e53996022 100644 --- a/PostHog/PostHogVersion.swift +++ b/PostHog/PostHogVersion.swift @@ -9,7 +9,7 @@ import Foundation // if you change this, make sure to also change it in the podspec and check if the script scripts/bump-version.sh still works // This property is internal only -public var postHogVersion = "3.3.0-alpha.1" +public nonisolated(unsafe) var postHogVersion = "3.3.0-alpha.1" // This property is internal only -public var postHogSdkName = "posthog-ios" +public nonisolated(unsafe) var postHogSdkName = "posthog-ios" diff --git a/PostHog/Replay/PostHogReplayIntegration.swift b/PostHog/Replay/PostHogReplayIntegration.swift index f4793b57b..87e5de467 100644 --- a/PostHog/Replay/PostHogReplayIntegration.swift +++ b/PostHog/Replay/PostHogReplayIntegration.swift @@ -63,7 +63,7 @@ timer = nil } - private func generateSnapshot(_ view: UIView, _ screenName: String? = nil) { + @MainActor private func generateSnapshot(_ view: UIView, _ screenName: String? = nil) { var hasChanges = false let timestamp = Date().toMillis() @@ -127,7 +127,7 @@ style.paddingLeft = Int(insets.left) } - private func toWireframe(_ view: UIView, parentId: Int? = nil) -> RRWireframe? { + @MainActor private func toWireframe(_ view: UIView, parentId: Int? = nil) -> RRWireframe? { if !view.isVisible() { return nil } @@ -262,7 +262,7 @@ return wireframe } - static func getCurrentWindow() -> UIWindow? { + @MainActor static func getCurrentWindow() -> UIWindow? { guard let activeScene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) else { return nil } @@ -273,7 +273,7 @@ return window } - @objc private func snapshot() { + @MainActor @objc private func snapshot() { if !PostHogSDK.shared.isSessionReplayActive() { return } diff --git a/PostHog/Replay/UIApplicationTracker.swift b/PostHog/Replay/UIApplicationTracker.swift index 181a6b514..bedab27fd 100644 --- a/PostHog/Replay/UIApplicationTracker.swift +++ b/PostHog/Replay/UIApplicationTracker.swift @@ -10,7 +10,7 @@ import UIKit enum UIApplicationTracker { - private static var hasSwizzled = false + private nonisolated(unsafe) static var hasSwizzled = false static func swizzleSendEvent() { if hasSwizzled { @@ -78,6 +78,7 @@ let data: [String: Any] = ["type": 3, "data": touchData, "timestamp": timestamp] snapshotsData.append(data) } + if !snapshotsData.isEmpty { DispatchQueue.global().async { PostHogSDK.shared.capture("$snapshot", properties: ["$snapshot_source": "mobile", "$snapshot_data": snapshotsData]) diff --git a/PostHog/Replay/URLSessionInterceptor.swift b/PostHog/Replay/URLSessionInterceptor.swift index 6108f29d2..4b77bbea1 100644 --- a/PostHog/Replay/URLSessionInterceptor.swift +++ b/PostHog/Replay/URLSessionInterceptor.swift @@ -8,7 +8,7 @@ import Foundation - class URLSessionInterceptor { + class URLSessionInterceptor: @unchecked Sendable { private let config: PostHogConfig init(_ config: PostHogConfig) { @@ -17,7 +17,7 @@ /// An internal queue for synchronising the access to `samplesByTask`. private let queue = DispatchQueue(label: "com.posthog.URLSessionInterceptor", target: .global(qos: .utility)) - private var samplesByTask: [URLSessionTask: NetworkSample] = [:] + private nonisolated(unsafe) var samplesByTask: [URLSessionTask: NetworkSample] = [:] // MARK: - Interception Flow @@ -37,7 +37,8 @@ } let date = Date() - queue.async { + queue.async { [weak self] in + guard let self = self else { return } let sample = NetworkSample(timeOrigin: date, url: url.absoluteString) self.samplesByTask[task] = sample } diff --git a/PostHog/Replay/URLSessionSwizzler.swift b/PostHog/Replay/URLSessionSwizzler.swift index a635af84f..59d700321 100644 --- a/PostHog/Replay/URLSessionSwizzler.swift +++ b/PostHog/Replay/URLSessionSwizzler.swift @@ -63,7 +63,7 @@ @convention(c) (URLSession, Selector, URLRequest, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask > { - private static let selector = #selector( + private nonisolated(unsafe) static let selector = #selector( URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URLRequest, @escaping CompletionHandler) -> URLSessionDataTask ) @@ -122,7 +122,7 @@ @convention(c) (URLSession, Selector, URL, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask > { - private static let selector = #selector( + private nonisolated(unsafe) static let selector = #selector( URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URL, @escaping CompletionHandler) -> URLSessionDataTask ) @@ -178,7 +178,7 @@ @convention(c) (URLSession, Selector, URLRequest) -> URLSessionDataTask, @convention(block) (URLSession, URLRequest) -> URLSessionDataTask > { - private static let selector = #selector( + private nonisolated(unsafe) static let selector = #selector( URLSession.dataTask(with:) as (URLSession) -> (URLRequest) -> URLSessionDataTask ) @@ -215,7 +215,7 @@ @convention(c) (URLSession, Selector, URL) -> URLSessionDataTask, @convention(block) (URLSession, URL) -> URLSessionDataTask > { - private static let selector = #selector( + private nonisolated(unsafe) static let selector = #selector( URLSession.dataTask(with:) as (URLSession) -> (URL) -> URLSessionDataTask ) diff --git a/PostHog/Replay/ViewLayoutTracker.swift b/PostHog/Replay/ViewLayoutTracker.swift index d48029c09..a24d63053 100644 --- a/PostHog/Replay/ViewLayoutTracker.swift +++ b/PostHog/Replay/ViewLayoutTracker.swift @@ -3,8 +3,9 @@ import UIKit enum ViewLayoutTracker { - static var hasChanges = false - private static var hasSwizzled = false + // nonisolated because its manually locked with setupLock + nonisolated(unsafe) static var hasChanges = false + private nonisolated(unsafe) static var hasSwizzled = false static func viewDidLayout(view _: UIView) { hasChanges = true diff --git a/PostHog/Utils/Hedgelog.swift b/PostHog/Utils/Hedgelog.swift index f53ad8694..ddcf21ee7 100644 --- a/PostHog/Utils/Hedgelog.swift +++ b/PostHog/Utils/Hedgelog.swift @@ -7,7 +7,7 @@ import Foundation -var hedgeLogEnabled = false +nonisolated(unsafe) var hedgeLogEnabled = false func toggleHedgeLog(_ enabled: Bool) { hedgeLogEnabled = enabled diff --git a/PostHog/Utils/PostHogAnySendable.swift b/PostHog/Utils/PostHogAnySendable.swift new file mode 100644 index 000000000..edb953eda --- /dev/null +++ b/PostHog/Utils/PostHogAnySendable.swift @@ -0,0 +1,16 @@ +// +// PostHogAnySendable.swift +// PostHog +// +// Created by Manoel Aranda Neto on 15.04.24. +// + +import Foundation + +struct PostHogAnySendable { + let value: T + + init(_ value: T) { + self.value = value + } +} diff --git a/PostHog/Utils/Reachability.swift b/PostHog/Utils/Reachability.swift index ea6cae487..cb32f91f1 100644 --- a/PostHog/Utils/Reachability.swift +++ b/PostHog/Utils/Reachability.swift @@ -45,7 +45,7 @@ import Foundation static let reachabilityChanged = Notification.Name("reachabilityChanged") } - public class Reachability { + public class Reachability: @unchecked Sendable { public typealias NetworkReachable = (Reachability) -> Void public typealias NetworkUnreachable = (Reachability) -> Void @@ -72,7 +72,7 @@ import Foundation } @available(*, deprecated, renamed: "unavailable") - public static let none: Connection = .unavailable + public nonisolated(unsafe) static let none: Connection = .unavailable } public var whenReachable: NetworkReachable? @@ -271,7 +271,7 @@ import Foundation } func notifyReachabilityChanged() { - let notify = { [weak self] in + let notify: @Sendable () -> Void = { [weak self] in guard let self = self else { return } self.connection != .unavailable ? self.whenReachable?(self) : self.whenUnreachable?(self) self.notificationCenter.post(name: .reachabilityChanged, object: self) From 19365775f0aed34ef25bc6135e0ac1f6e67ad130 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 15 Apr 2024 14:40:42 +0200 Subject: [PATCH 2/3] fix --- PostHog/PostHogFeatureFlags.swift | 11 +- PostHog/Replay/UIApplicationTracker.swift | 2 +- PostHog/Replay/URLSessionSwizzler.swift | 178 +++++++++++----------- 3 files changed, 97 insertions(+), 94 deletions(-) diff --git a/PostHog/PostHogFeatureFlags.swift b/PostHog/PostHogFeatureFlags.swift index 9c71336f9..66050a392 100644 --- a/PostHog/PostHogFeatureFlags.swift +++ b/PostHog/PostHogFeatureFlags.swift @@ -52,8 +52,9 @@ class PostHogFeatureFlags: @unchecked Sendable { groups: groups) { data, _ in self.dispatchQueue.async { - guard let featureFlags = data?["featureFlags"] as? [String: Any], - let featureFlagPayloads = data?["featureFlagPayloads"] as? [String: Any] + guard let data = data, + let featureFlags = data["featureFlags"] as? [String: Any], + let featureFlagPayloads = data["featureFlagPayloads"] as? [String: Any] else { hedgeLog("Error: Decide response missing correct featureFlags format") @@ -61,12 +62,12 @@ class PostHogFeatureFlags: @unchecked Sendable { return callback() } - let errorsWhileComputingFlags = data?["errorsWhileComputingFlags"] as? Bool ?? false + let errorsWhileComputingFlags = data["errorsWhileComputingFlags"] as? Bool ?? false #if os(iOS) - if let sessionRecording = data?["sessionRecording"] as? Bool { + if let sessionRecording = data["sessionRecording"] as? Bool { self.config.sessionReplay = self.config.sessionReplay && sessionRecording - } else if let sessionRecording = data?["sessionRecording"] as? [String: Any] { + } else if let sessionRecording = data["sessionRecording"] as? [String: Any] { // keeps the value from config.sessionReplay since having sessionRecording // means its enabled on the project settings, but its only enabled // when local config.sessionReplay is also enabled diff --git a/PostHog/Replay/UIApplicationTracker.swift b/PostHog/Replay/UIApplicationTracker.swift index bedab27fd..ca15e8a98 100644 --- a/PostHog/Replay/UIApplicationTracker.swift +++ b/PostHog/Replay/UIApplicationTracker.swift @@ -78,7 +78,7 @@ let data: [String: Any] = ["type": 3, "data": touchData, "timestamp": timestamp] snapshotsData.append(data) } - + if !snapshotsData.isEmpty { DispatchQueue.global().async { PostHogSDK.shared.capture("$snapshot", properties: ["$snapshot_source": "mobile", "$snapshot_data": snapshotsData]) diff --git a/PostHog/Replay/URLSessionSwizzler.swift b/PostHog/Replay/URLSessionSwizzler.swift index 59d700321..6237e3705 100644 --- a/PostHog/Replay/URLSessionSwizzler.swift +++ b/PostHog/Replay/URLSessionSwizzler.swift @@ -56,122 +56,124 @@ // MARK: - Swizzlings - typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void + typealias CompletionHandler = @Sendable (Data?, URLResponse?, Error?) -> Void /// Swizzles the `URLSession.dataTask(with:completionHandler:)` for `URLRequest`. class DataTaskWithURLRequestAndCompletion: MethodSwizzler< @convention(c) (URLSession, Selector, URLRequest, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask - > { - private nonisolated(unsafe) static let selector = #selector( - URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URLRequest, @escaping CompletionHandler) -> URLSessionDataTask + >, @unchecked Sendable { + private nonisolated(unsafe) static let selector = #selector( + URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URLRequest, @escaping CompletionHandler) -> URLSessionDataTask + ) + + private let method: FoundMethod + private let interceptor: URLSessionInterceptor + + static func build(interceptor: URLSessionInterceptor) throws -> DataTaskWithURLRequestAndCompletion { + try DataTaskWithURLRequestAndCompletion( + selector: selector, + klass: URLSession.self, + interceptor: interceptor ) + } - private let method: FoundMethod - private let interceptor: URLSessionInterceptor - - static func build(interceptor: URLSessionInterceptor) throws -> DataTaskWithURLRequestAndCompletion { - try DataTaskWithURLRequestAndCompletion( - selector: selector, - klass: URLSession.self, - interceptor: interceptor - ) - } - - private init(selector: Selector, klass: AnyClass, interceptor: URLSessionInterceptor) throws { - method = try Self.findMethod(with: selector, in: klass) - self.interceptor = interceptor - super.init() - } + private init(selector: Selector, klass: AnyClass, interceptor: URLSessionInterceptor) throws { + method = try Self.findMethod(with: selector, in: klass) + self.interceptor = interceptor + super.init() + } - func swizzle() { - typealias Signature = @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask - swizzle(method) { previousImplementation -> Signature in { session, urlRequest, completionHandler -> URLSessionDataTask in - let task: URLSessionDataTask - if completionHandler != nil { - var taskReference: URLSessionDataTask? - let newCompletionHandler: CompletionHandler = { data, response, error in - if let task = taskReference { // sanity check, should always succeed - if let data = data { - self.interceptor.taskReceivedData(task: task, data: data) - } - self.interceptor.taskCompleted(task: task, error: error) + func swizzle() { + typealias Signature = @convention(block) (URLSession, URLRequest, CompletionHandler?) -> URLSessionDataTask + swizzle(method) { previousImplementation -> Signature in { session, urlRequest, completionHandler -> URLSessionDataTask in + let task: URLSessionDataTask + if completionHandler != nil { + var taskReference: URLSessionDataTask? + let newCompletionHandler: CompletionHandler = { [weak self, taskReference] data, response, error in + guard let self = self else { return } + if let task = taskReference { // sanity check, should always succeed + if let data = data { + self.interceptor.taskReceivedData(task: task, data: data) } - completionHandler?(data, response, error) + self.interceptor.taskCompleted(task: task, error: error) } - - task = previousImplementation(session, Self.selector, urlRequest, newCompletionHandler) - taskReference = task - } else { - // The `completionHandler` can be `nil` in two cases: - // - on iOS 11 or 12, where `dataTask(with:)` (for `URL` and `URLRequest`) calls - // the `dataTask(with:completionHandler:)` (for `URLRequest`) internally by nullifying the completion block. - // - when `[session dataTaskWithURL:completionHandler:]` is called in Objective-C with explicitly passing - // `nil` as the `completionHandler` (it produces a warning, but compiles). - task = previousImplementation(session, Self.selector, urlRequest, completionHandler) + completionHandler?(data, response, error) } - self.interceptor.taskCreated(task: task, session: session) - return task - } + + task = previousImplementation(session, Self.selector, urlRequest, newCompletionHandler) + taskReference = task + } else { + // The `completionHandler` can be `nil` in two cases: + // - on iOS 11 or 12, where `dataTask(with:)` (for `URL` and `URLRequest`) calls + // the `dataTask(with:completionHandler:)` (for `URLRequest`) internally by nullifying the completion block. + // - when `[session dataTaskWithURL:completionHandler:]` is called in Objective-C with explicitly passing + // `nil` as the `completionHandler` (it produces a warning, but compiles). + task = previousImplementation(session, Self.selector, urlRequest, completionHandler) } + self.interceptor.taskCreated(task: task, session: session) + return task + } } } + } /// Swizzles the `URLSession.dataTask(with:completionHandler:)` for `URL`. class DataTaskWithURLAndCompletion: MethodSwizzler< @convention(c) (URLSession, Selector, URL, CompletionHandler?) -> URLSessionDataTask, @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask - > { - private nonisolated(unsafe) static let selector = #selector( - URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URL, @escaping CompletionHandler) -> URLSessionDataTask + >, @unchecked Sendable { + private nonisolated(unsafe) static let selector = #selector( + URLSession.dataTask(with:completionHandler:) as (URLSession) -> (URL, @escaping CompletionHandler) -> URLSessionDataTask + ) + + private let method: FoundMethod + private let interceptor: URLSessionInterceptor + + static func build(interceptor: URLSessionInterceptor) throws -> DataTaskWithURLAndCompletion { + try DataTaskWithURLAndCompletion( + selector: selector, + klass: URLSession.self, + interceptor: interceptor ) + } - private let method: FoundMethod - private let interceptor: URLSessionInterceptor - - static func build(interceptor: URLSessionInterceptor) throws -> DataTaskWithURLAndCompletion { - try DataTaskWithURLAndCompletion( - selector: selector, - klass: URLSession.self, - interceptor: interceptor - ) - } - - private init(selector: Selector, klass: AnyClass, interceptor: URLSessionInterceptor) throws { - method = try Self.findMethod(with: selector, in: klass) - self.interceptor = interceptor - super.init() - } + private init(selector: Selector, klass: AnyClass, interceptor: URLSessionInterceptor) throws { + method = try Self.findMethod(with: selector, in: klass) + self.interceptor = interceptor + super.init() + } - func swizzle() { - typealias Signature = @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask - swizzle(method) { previousImplementation -> Signature in { session, url, completionHandler -> URLSessionDataTask in - let task: URLSessionDataTask - if completionHandler != nil { - var taskReference: URLSessionDataTask? - let newCompletionHandler: CompletionHandler = { data, response, error in - if let task = taskReference { // sanity check, should always succeed - if let data = data { - self.interceptor.taskReceivedData(task: task, data: data) - } - self.interceptor.taskCompleted(task: task, error: error) + func swizzle() { + typealias Signature = @convention(block) (URLSession, URL, CompletionHandler?) -> URLSessionDataTask + swizzle(method) { previousImplementation -> Signature in { session, url, completionHandler -> URLSessionDataTask in + let task: URLSessionDataTask + if completionHandler != nil { + var taskReference: URLSessionDataTask? + let newCompletionHandler: CompletionHandler = { [weak self, taskReference] data, response, error in + guard let self = self else { return } + if let task = taskReference { // sanity check, should always succeed + if let data = data { + self.interceptor.taskReceivedData(task: task, data: data) } - completionHandler?(data, response, error) + self.interceptor.taskCompleted(task: task, error: error) } - task = previousImplementation(session, Self.selector, url, newCompletionHandler) - taskReference = task - } else { - // The `completionHandler` can be `nil` in one case: - // - when `[session dataTaskWithURL:completionHandler:]` is called in Objective-C with explicitly passing - // `nil` as the `completionHandler` (it produces a warning, but compiles). - task = previousImplementation(session, Self.selector, url, completionHandler) + completionHandler?(data, response, error) } - self.interceptor.taskCreated(task: task, session: session) - return task - } + task = previousImplementation(session, Self.selector, url, newCompletionHandler) + taskReference = task + } else { + // The `completionHandler` can be `nil` in one case: + // - when `[session dataTaskWithURL:completionHandler:]` is called in Objective-C with explicitly passing + // `nil` as the `completionHandler` (it produces a warning, but compiles). + task = previousImplementation(session, Self.selector, url, completionHandler) } + self.interceptor.taskCreated(task: task, session: session) + return task + } } } + } /// Swizzles the `URLSession.dataTask(with:)` for `URLRequest`. class DataTaskWithURLRequest: MethodSwizzler< From b1f3818b78193653344218345f4f74e660579304 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Mon, 15 Apr 2024 14:58:01 +0200 Subject: [PATCH 3/3] compile --- PostHog.xcodeproj/project.pbxproj | 4 -- PostHog/PostHogContext.swift | 2 + PostHog/PostHogSDK.swift | 48 +++++++++++----------- PostHog/Replay/URLSessionInterceptor.swift | 2 +- PostHog/Utils/PostHogAnySendable.swift | 16 -------- PostHogExample/ContentView.swift | 2 +- 6 files changed, 28 insertions(+), 46 deletions(-) delete mode 100644 PostHog/Utils/PostHogAnySendable.swift diff --git a/PostHog.xcodeproj/project.pbxproj b/PostHog.xcodeproj/project.pbxproj index a5b6a98e6..64149ce96 100644 --- a/PostHog.xcodeproj/project.pbxproj +++ b/PostHog.xcodeproj/project.pbxproj @@ -82,7 +82,6 @@ 699991952AFE1B56000DCB78 /* PostHog.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3AC745B5296D6FE60025C109 /* PostHog.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 6999919A2AFE1BAB000DCB78 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699991992AFE1BAB000DCB78 /* AppDelegate.swift */; }; 69BA38D72B888E8500AA69D6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */; }; - 69BDF1942BCD45EA00ED15FA /* PostHogAnySendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69BDF1932BCD45EA00ED15FA /* PostHogAnySendable.swift */; }; 69EE82BA2BA9C50400EB9542 /* PostHogReplayIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */; }; 69EE82BC2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */; }; 69EE82BE2BA9C8AA00EB9542 /* ViewLayoutTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */; }; @@ -328,7 +327,6 @@ 699991902AFE1B39000DCB78 /* PostHogExampleMacOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PostHogExampleMacOS.entitlements; sourceTree = ""; }; 699991992AFE1BAB000DCB78 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 69BA38D62B888E8500AA69D6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - 69BDF1932BCD45EA00ED15FA /* PostHogAnySendable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnySendable.swift; sourceTree = ""; }; 69EE82B92BA9C50400EB9542 /* PostHogReplayIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogReplayIntegration.swift; sourceTree = ""; }; 69EE82BB2BA9C53000EB9542 /* PostHogSessionReplayConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogSessionReplayConfig.swift; sourceTree = ""; }; 69EE82BD2BA9C8AA00EB9542 /* ViewLayoutTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewLayoutTracker.swift; sourceTree = ""; }; @@ -472,7 +470,6 @@ 690FF0BE2AEFA97F00A0B06B /* FileUtils.swift */, 690FF0B42AEBBD3C00A0B06B /* DictUtils.swift */, 690FF0AE2AEB9C1400A0B06B /* DateUtils.swift */, - 69BDF1932BCD45EA00ED15FA /* PostHogAnySendable.swift */, ); path = Utils; sourceTree = ""; @@ -1066,7 +1063,6 @@ 69F23A742BB3088E001194F6 /* URLSessionSwizzler.swift in Sources */, 69F518142BAC7F4300F52C14 /* Date+Util.swift in Sources */, 69F5183A2BB2BA8300F52C14 /* UIApplicationTracker.swift in Sources */, - 69BDF1942BCD45EA00ED15FA /* PostHogAnySendable.swift in Sources */, 69F518182BAC80A300F52C14 /* String+Util.swift in Sources */, 69F5181A2BAC81FC00F52C14 /* UITextInputTraits+Util.swift in Sources */, 69F517E82BAC675800F52C14 /* RRWireframe.swift in Sources */, diff --git a/PostHog/PostHogContext.swift b/PostHog/PostHogContext.swift index 4c296d4cf..73444336d 100644 --- a/PostHog/PostHogContext.swift +++ b/PostHog/PostHogContext.swift @@ -26,6 +26,7 @@ class PostHogContext: @unchecked Sendable { init() {} #endif + // UIDevice is marked as @MainActor @MainActor func staticContext() -> [String: Any] { // Properties that do not change over the lifecycle of an application var properties: [String: Any] = [:] @@ -98,6 +99,7 @@ class PostHogContext: @unchecked Sendable { return String(cString: machine) } + // UIScreen is marked as @MainActor @MainActor func dynamicContext() -> [String: Any] { var properties: [String: Any] = [:] diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 09f17ac66..fa8a2382e 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -221,7 +221,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 return properties } - private func buildProperties(distinctId: String, + @MainActor private func buildProperties(distinctId: String, properties: [String: Any]?, userProperties: [String: Any]? = nil, userPropertiesSetOnce: [String: Any]? = nil, @@ -337,18 +337,18 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 } } - @objc public func identify(_ distinctId: String) { + @MainActor @objc public func identify(_ distinctId: String) { identify(distinctId, userProperties: nil, userPropertiesSetOnce: nil) } - @objc(identifyWithDistinctId:userProperties:) + @MainActor @objc(identifyWithDistinctId:userProperties:) public func identify(_ distinctId: String, userProperties: [String: Any]? = nil) { identify(distinctId, userProperties: userProperties, userPropertiesSetOnce: nil) } - @objc(identifyWithDistinctId:userProperties:userPropertiesSetOnce:) + @MainActor @objc(identifyWithDistinctId:userProperties:userPropertiesSetOnce:) public func identify(_ distinctId: String, userProperties: [String: Any]? = nil, userPropertiesSetOnce: [String: Any]? = nil) @@ -384,18 +384,18 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 } } - @objc public func capture(_ event: String) { + @MainActor @objc public func capture(_ event: String) { capture(event, properties: nil, userProperties: nil, userPropertiesSetOnce: nil, groupProperties: nil) } - @objc(captureWithEvent:properties:) + @MainActor @objc(captureWithEvent:properties:) public func capture(_ event: String, properties: [String: Any]? = nil) { capture(event, properties: properties, userProperties: nil, userPropertiesSetOnce: nil, groupProperties: nil) } - @objc(captureWithEvent:properties:userProperties:) + @MainActor @objc(captureWithEvent:properties:userProperties:) public func capture(_ event: String, properties: [String: Any]? = nil, userProperties: [String: Any]? = nil) @@ -403,7 +403,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 capture(event, properties: properties, userProperties: userProperties, userPropertiesSetOnce: nil, groupProperties: nil) } - @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:) + @MainActor @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:) public func capture(_ event: String, properties: [String: Any]? = nil, userProperties: [String: Any]? = nil, @@ -420,7 +420,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 return false } - @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:groupProperties:) + @MainActor @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:groupProperties:) public func capture(_ event: String, properties: [String: Any]? = nil, userProperties: [String: Any]? = nil, @@ -479,11 +479,11 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 queue.add(posthogEvent) } - @objc public func screen(_ screenTitle: String) { + @MainActor @objc public func screen(_ screenTitle: String) { screen(screenTitle, properties: nil) } - @objc(screenWithTitle:properties:) + @MainActor @objc(screenWithTitle:properties:) public func screen(_ screenTitle: String, properties: [String: Any]? = nil) { if !isEnabled() { return @@ -509,7 +509,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 )) } - @objc public func alias(_ alias: String) { + @MainActor @objc public func alias(_ alias: String) { if !isEnabled() { return } @@ -560,7 +560,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 return mergedGroups ?? [:] } - private func groupIdentify(type: String, key: String, groupProperties: [String: Any]? = nil) { + @MainActor private func groupIdentify(type: String, key: String, groupProperties: [String: Any]? = nil) { if !isEnabled() { return } @@ -591,12 +591,12 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 )) } - @objc(groupWithType:key:) + @MainActor @objc(groupWithType:key:) public func group(type: String, key: String) { group(type: type, key: key, groupProperties: nil) } - @objc(groupWithType:key:groupProperties:) + @MainActor @objc(groupWithType:key:groupProperties:) public func group(type: String, key: String, groupProperties: [String: Any]? = nil) { if !isEnabled() { return @@ -640,7 +640,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 ) } - @objc public func getFeatureFlag(_ key: String) -> Any? { + @MainActor @objc public func getFeatureFlag(_ key: String) -> Any? { if !isEnabled() { return nil } @@ -658,7 +658,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 return value } - @objc public func isFeatureEnabled(_ key: String) -> Bool { + @MainActor @objc public func isFeatureEnabled(_ key: String) -> Bool { if !isEnabled() { return false } @@ -688,7 +688,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 return featureFlags.getFeatureFlagPayload(key) } - private func reportFeatureFlagCalled(flagKey: String, flagValue: Any?) { + @MainActor private func reportFeatureFlagCalled(flagKey: String, flagValue: Any?) { if !flagCallReported.contains(flagKey) { let properties: [String: Any] = [ "$feature_flag": flagKey, @@ -862,11 +862,11 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 } } - @objc func handleAppDidFinishLaunching() { + @MainActor @objc func handleAppDidFinishLaunching() { captureAppInstallLifecycle() } - private func captureAppInstallLifecycle() { + @MainActor private func captureAppInstallLifecycle() { if !config.captureApplicationLifecycleEvents { return } @@ -925,14 +925,14 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 } } - @objc func handleAppDidBecomeActive() { + @MainActor @objc func handleAppDidBecomeActive() { rotateSessionIdIfRequired() isInBackground = false captureAppOpened() } - private func captureAppOpened() { + @MainActor private func captureAppOpened() { if !config.captureApplicationLifecycleEvents { return } @@ -959,7 +959,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 capture("Application Opened", properties: props) } - @objc func handleAppDidEnterBackground() { + @MainActor @objc func handleAppDidEnterBackground() { captureAppBackgrounded() sessionLock.withLock { @@ -969,7 +969,7 @@ private let sessionChangeThreshold: TimeInterval = 60 * 30 isInBackground = true } - private func captureAppBackgrounded() { + @MainActor private func captureAppBackgrounded() { if !config.captureApplicationLifecycleEvents { return } diff --git a/PostHog/Replay/URLSessionInterceptor.swift b/PostHog/Replay/URLSessionInterceptor.swift index 4b77bbea1..88aeea8d1 100644 --- a/PostHog/Replay/URLSessionInterceptor.swift +++ b/PostHog/Replay/URLSessionInterceptor.swift @@ -102,7 +102,7 @@ config.sessionReplayConfig.captureNetworkTelemetry && PostHogSDK.shared.isSessionReplayActive() } - private func finish(task: URLSessionTask, sample: NetworkSample) { + @MainActor private func finish(task: URLSessionTask, sample: NetworkSample) { if !isCaptureNetworkEnabled() { return } diff --git a/PostHog/Utils/PostHogAnySendable.swift b/PostHog/Utils/PostHogAnySendable.swift deleted file mode 100644 index edb953eda..000000000 --- a/PostHog/Utils/PostHogAnySendable.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// PostHogAnySendable.swift -// PostHog -// -// Created by Manoel Aranda Neto on 15.04.24. -// - -import Foundation - -struct PostHogAnySendable { - let value: T - - init(_ value: T) { - self.value = value - } -} diff --git a/PostHogExample/ContentView.swift b/PostHogExample/ContentView.swift index d2e258465..f752ac8a2 100644 --- a/PostHogExample/ContentView.swift +++ b/PostHogExample/ContentView.swift @@ -46,7 +46,7 @@ class FeatureFlagsModel: ObservableObject { NotificationCenter.default.addObserver(self, selector: #selector(reloaded), name: PostHogSDK.didReceiveFeatureFlags, object: nil) } - @objc func reloaded() { + @MainActor @objc func reloaded() { boolValue = PostHogSDK.shared.isFeatureEnabled("4535-funnel-bar-viz") stringValue = PostHogSDK.shared.getFeatureFlag("multivariant") as? String payloadValue = PostHogSDK.shared.getFeatureFlagPayload("multivariant") as? [String: String]