diff --git a/.github/workflows/flagship_ci.yml b/.github/workflows/flagship_ci.yml index 16029e6f..80ddd597 100644 --- a/.github/workflows/flagship_ci.yml +++ b/.github/workflows/flagship_ci.yml @@ -10,10 +10,10 @@ jobs: strategy: matrix: run-config: - - { scheme: 'Flagship', destination: 'platform=iOS Simulator,name=iPhone SE (3rd generation)', label: 'iOS'} + - { scheme: 'Flagship', destination: 'platform=iOS Simulator,name=iPhone 17', label: 'iOS'} - { scheme: 'Flagship-tvOS', destination: 'platform=tvOS Simulator,name=Apple TV', label: 'tvOS' } - { scheme: 'Flagship-macOS', destination: 'platform=macOS', label: 'macOS'} - - { scheme: 'Flagship-watchOS', destination: 'platform=watchOS Simulator,name=Apple Watch SE (40mm) (2nd generation)', label: 'watchOS'} + - { scheme: 'Flagship-watchOS', destination: 'platform=watchOS Simulator,name=Apple Watch Ultra 3 (49mm),OS=26.2', label: 'watchOS'} steps: @@ -28,7 +28,7 @@ jobs: - name: Checkout Project uses: actions/checkout@v4 - name: Test SDK Flagship - run: xcodebuild test -project Flagship/Flagship.xcodeproj -scheme "Flagship" -destination "platform=iOS Simulator,name=iPhone SE (3rd generation)" -enableCodeCoverage YES + run: xcodebuild test -project Flagship/Flagship.xcodeproj -scheme "Flagship" -destination "platform=iOS Simulator,name=iPhone 17" -enableCodeCoverage YES - name: Upload coverage to Codecov uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 with: diff --git a/FlagShip.podspec b/FlagShip.podspec index cc37389a..6e8aab5c 100644 --- a/FlagShip.podspec +++ b/FlagShip.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.name = "FlagShip" - s.version = "4.0.2" + s.version = "4.0.3" s.summary = "Flagship SDK" # This description is used to generate tags and improve search results. diff --git a/FlagShip/FlagShipTests/FlagshipBucketingTest.swift b/FlagShip/FlagShipTests/FlagshipBucketingTest.swift index 69cc8063..74ca4e48 100644 --- a/FlagShip/FlagShipTests/FlagshipBucketingTest.swift +++ b/FlagShip/FlagShipTests/FlagshipBucketingTest.swift @@ -94,9 +94,19 @@ class FlagshipBucketingTest: XCTestCase { Flagship.sharedInstance.start(envId: "gk87t3jggr10c6l6sdob", apiKey: "apiKey", config: fsConfig ?? FSConfigBuilder().build()) /// Create new visitor testVisitor = Flagship.sharedInstance.newVisitor(visitorId: "korso", hasConsented: true).withFetchFlagsStatus { _, _ in + + guard let visitor = self.testVisitor else { + XCTFail("testVisitor is nil in fetch flags callback") + expectationSync.fulfill() + return + } + + XCTAssertEqual(visitor.visitorId, "korso", "Unexpected visitorId in fetch flags callback") + // Get from alloc 100 - let flag2 = self.testVisitor?.getFlag(key: "stringFlag") - XCTAssertTrue(flag2?.value(defaultValue: "default") == "default") + let flag2 = visitor.getFlag(key: "stringFlag") + XCTAssertEqual(flag2.value(defaultValue: "default"), "default") + expectationSync.fulfill() }.build() /// Erase all cached data diff --git a/FlagShip/FlagShipTests/FlagshipTests.swift b/FlagShip/FlagShipTests/FlagshipTests.swift index 963a6943..a4d4a4da 100644 --- a/FlagShip/FlagShipTests/FlagshipTests.swift +++ b/FlagShip/FlagShipTests/FlagshipTests.swift @@ -10,45 +10,47 @@ import XCTest @testable import Flagship class FlagshipTests: XCTestCase { - - override func setUpWithError() throws { - - } - - - func testStart(){ - + override func setUpWithError() throws {} + + func testStart() { Flagship.sharedInstance.start(envId: "gk87t3jggr10c6l6sdob", apiKey: "apiKey") XCTAssert(Flagship.sharedInstance.envId == "gk87t3jggr10c6l6sdob") XCTAssert(Flagship.sharedInstance.apiKey == "apiKey") XCTAssert(Flagship.sharedInstance.currentStatus == .SDK_INITIALIZED) - + XCTAssert(Flagship.sharedInstance.currentConfig.logLevel == .ALL) XCTAssert(Flagship.sharedInstance.currentConfig.mode == .DECISION_API) XCTAssert(Flagship.sharedInstance.currentConfig.timeout == 2) - } - - - func testStartWithConfig(){ - + + func testStartWithConfig() { let fsConfig = FSConfigBuilder().DecisionApi().withTimeout(12).build() Flagship.sharedInstance.start(envId: "gk87t3jggr10c6l6sdob", apiKey: "apiKey", config: fsConfig) XCTAssert(Flagship.sharedInstance.envId == "gk87t3jggr10c6l6sdob") XCTAssert(Flagship.sharedInstance.apiKey == "apiKey") XCTAssert(Flagship.sharedInstance.currentStatus == .SDK_INITIALIZED) - + XCTAssert(Flagship.sharedInstance.currentConfig.logLevel == .ALL) XCTAssert(Flagship.sharedInstance.currentConfig.mode == .DECISION_API) - XCTAssert(Flagship.sharedInstance.currentConfig.timeout == 12/1000) - + XCTAssert(Flagship.sharedInstance.currentConfig.timeout == 12 / 1000) } - - func testLogManager(){ + + func testLogManager() { let customLoger = FSLogManager() customLoger.level = .WARNING customLoger.onLog(level: .DEBUG, tag: "testTag", message: "testMsg") - XCTAssertTrue( customLoger.getLevel() == .WARNING) + XCTAssertTrue(customLoger.getLevel() == .WARNING) + } + + func testInitFromThread() { + let expectation = XCTestExpectation(description: "Init Flagship from background thread completion handler") + + DispatchQueue.global(qos: .userInitiated).async { + Flagship.sharedInstance.start(envId: "gk87t3jggr10c6l6sdob", apiKey: "apiKey") + XCTAssert(Flagship.sharedInstance.currentStatus == .SDK_INITIALIZED) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 10.0) } - } diff --git a/FlagShip/Flagship.xcodeproj/project.pbxproj b/FlagShip/Flagship.xcodeproj/project.pbxproj index 79ee4515..0752895b 100644 --- a/FlagShip/Flagship.xcodeproj/project.pbxproj +++ b/FlagShip/Flagship.xcodeproj/project.pbxproj @@ -1793,7 +1793,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 4.0.2; + MARKETING_VERSION = 4.0.3; OTHER_LDFLAGS = ""; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = ABTasty.FlagShip; @@ -1826,7 +1826,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 4.0.2; + MARKETING_VERSION = 4.0.3; OTHER_LDFLAGS = ""; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = ABTasty.FlagShip; diff --git a/FlagShip/Source/Core/Flagship.swift b/FlagShip/Source/Core/Flagship.swift index 45c7f6a2..dd779ad1 100644 --- a/FlagShip/Source/Core/Flagship.swift +++ b/FlagShip/Source/Core/Flagship.swift @@ -9,12 +9,10 @@ import Foundation public class Flagship: NSObject { let fsQueue = DispatchQueue(label: "flagship.queue", attributes: .concurrent) - // envId var envId: String? // apiKey var apiKey: String? - // Configuration - var currentConfig: FlagshipConfig = FSConfigBuilder().build() + // Current visitor @objc public private(set) var sharedVisitor: FSVisitor? // Enabale Log @@ -36,9 +34,25 @@ public class Flagship: NSObject { } } } - + private var _currentStatus: FSSdkStatus = .SDK_NOT_INITIALIZED + // Configuration + private var _currentConfig: FlagshipConfig = FSConfigBuilder().build() + + var currentConfig: FlagshipConfig { + get { fsQueue.sync { _currentConfig } } + set { fsQueue.async(flags: .barrier) { self._currentConfig = newValue } } + } + + /// Synchronously update the current configuration. + /// Used during SDK start to ensure the config is visible before updating status. + private func setCurrentConfigSync(_ config: FlagshipConfig) { + fsQueue.sync(flags: .barrier) { + self._currentConfig = config + } + } + // Shared instace @objc public static let sharedInstance: Flagship = { let instance = Flagship() @@ -48,9 +62,11 @@ public class Flagship: NSObject { override private init() { lastInitializationTimestamp = FSTools.getUtcTimestamp() - } + } - @objc public func start(envId: String, apiKey: String, config: FlagshipConfig = FSConfigBuilder().build()) { + @objc public func start(envId: String, apiKey: String, config: FlagshipConfig? = nil) { + let resolvedConfig = config ?? FSConfigBuilder().build() + // Check the environmentId if FSTools.chekcXidEnvironment(envId) { Flagship.sharedInstance.envId = envId @@ -66,13 +82,13 @@ public class Flagship: NSObject { self.apiKey = apiKey // Set configuration - Flagship.sharedInstance.currentConfig = config + Flagship.sharedInstance.setCurrentConfigSync(resolvedConfig) - switch config.mode { case .DECISION_API: + switch resolvedConfig.mode { case .DECISION_API: Flagship.sharedInstance.updateStatus(.SDK_INITIALIZED) case .BUCKETING: // Init the polling script - pollingScript = FSPollingScript(pollingTime: config.pollingTime) + pollingScript = FSPollingScript(pollingTime: resolvedConfig.pollingTime) // Update status depend on the buckeitng file Flagship.sharedInstance.updateStatus(FSStorageManager.bucketingScriptAlreadyAvailable() ? .SDK_INITIALIZED : .SDK_INITIALIZING) } @@ -113,8 +129,11 @@ public class Flagship: NSObject { // Reset the sdk func reset() { - sharedVisitor = nil - currentStatus = .SDK_NOT_INITIALIZED + fsQueue.sync(flags: .barrier) { + self.sharedVisitor = nil + self._currentStatus = .SDK_NOT_INITIALIZED + self._currentConfig = FSConfigBuilder().build() + } } // Create new visitor @@ -129,16 +148,13 @@ public class Flagship: NSObject { // Update status func updateStatus(_ newStatus: FSSdkStatus) { - // _ if the staus has not changed then no need to trigger the callback - if newStatus == currentStatus { - return - } - // Update the status - currentStatus = newStatus - // Trigger the callback - if let callbackListener = currentConfig.onSdkStatusChanged { - callbackListener(newStatus) + var callbackListener: ((FSSdkStatus) -> Void)? + fsQueue.sync(flags: .barrier) { + guard newStatus != self._currentStatus else { return } + self._currentStatus = newStatus + callbackListener = self._currentConfig.onSdkStatusChanged } + callbackListener?(newStatus) } // When close is called will trigger all hits present in batch diff --git a/FlagShip/Source/Tools/FlagShipVersion.swift b/FlagShip/Source/Tools/FlagShipVersion.swift index bf4be885..eda5ad37 100644 --- a/FlagShip/Source/Tools/FlagShipVersion.swift +++ b/FlagShip/Source/Tools/FlagShipVersion.swift @@ -8,4 +8,4 @@ import Foundation /// This file is automatically updated 2.0.0 -public let FlagShipVersion = "4.0.2" +public let FlagShipVersion = "4.0.3"