Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9fcc1b3
Initial commit
MartinCardozo-SDK Aug 8, 2025
d342431
New SplitEventWithMetadata
MartinCardozo-SDK Aug 8, 2025
7378c38
Switching branch
MartinCardozo-SDK Aug 8, 2025
f68cfb0
Stable point
MartinCardozo-SDK Aug 11, 2025
23ed38f
Merge branch 'FME-8393-Events-Baseline' into FME-8393-Events-Metadata
MartinCardozo-SDK Aug 11, 2025
ba7328c
Simplified tests
MartinCardozo-SDK Aug 11, 2025
740f793
Equatable removed from SplitActionWithMetadata
MartinCardozo-SDK Aug 11, 2025
8dd3be6
EventsManager Mock and Stub now record the events correctly
MartinCardozo-SDK Aug 11, 2025
0b885a4
Tests fixed
MartinCardozo-SDK Aug 11, 2025
ad58ec3
Tests fixed
MartinCardozo-SDK Aug 11, 2025
30e203a
Tests covering the new events listeners with metadata
MartinCardozo-SDK Aug 11, 2025
ed50d1c
Removed code from the next feature (Events Errors)
MartinCardozo-SDK Aug 11, 2025
a759fa7
Added nested comment for SonarQuabe quality gate
MartinCardozo-SDK Aug 11, 2025
9270d65
Tests added to conform to SonarQube quality gate
MartinCardozo-SDK Aug 11, 2025
71c5f68
Typo fixed:
MartinCardozo-SDK Aug 11, 2025
93c1b96
SplitEventsTests added to TestPlan
MartinCardozo-SDK Aug 11, 2025
e9b139d
Test added to cover listening with metadata on a Queue
MartinCardozo-SDK Aug 11, 2025
eafca8f
Added sdkError cases
MartinCardozo-SDK Aug 12, 2025
4263d1a
Merge pull request #718 from splitio/FME-8393-Events-Metadata
MartinCardozo-SDK Aug 12, 2025
647ebe9
Tests added
MartinCardozo-SDK Aug 13, 2025
5ca81f3
Removing empty line changes for better readability
MartinCardozo-SDK Aug 13, 2025
d0201be
Added two more test to cover the case were response from server is 20…
MartinCardozo-SDK Aug 13, 2025
b4c9e44
Update Split/Events/SplitEventsManager.swift
MartinCardozo-SDK Aug 13, 2025
a2ddfc6
Better comments
MartinCardozo-SDK Aug 13, 2025
12f5af2
Merge branch 'FME-8356-Errors' of github.com:splitio/ios-client into …
MartinCardozo-SDK Aug 13, 2025
eefa195
Cleanup of Workers
MartinCardozo-SDK Aug 13, 2025
b845d46
Better comments. Errors now on camel case to follow convention.
MartinCardozo-SDK Aug 13, 2025
77c4528
Merge branch 'FME-8356-Errors' into FME-8356-Cleanup
MartinCardozo-SDK Aug 13, 2025
e6d87a5
Removed unnecessary types to let Swift infer it
MartinCardozo-SDK Aug 13, 2025
20385cb
New comments
MartinCardozo-SDK Aug 13, 2025
d3f55fa
Merge branch 'FME-8356-Errors' into FME-8356-Cleanup
MartinCardozo-SDK Aug 13, 2025
d0b2b85
Initializers shrinked for better readability
MartinCardozo-SDK Aug 13, 2025
8544340
Methods requeriments condensed
MartinCardozo-SDK Aug 13, 2025
9ca0010
Minor details
MartinCardozo-SDK Aug 13, 2025
41904f5
Merge pull request #720 from splitio/FME-8356-Errors
MartinCardozo-SDK Aug 13, 2025
625a5d7
Merge pull request #721 from splitio/FME-8356-Cleanup
MartinCardozo-SDK Aug 13, 2025
42c1dd1
Unintended Synchronization bug removed
MartinCardozo-SDK Aug 14, 2025
beb3215
Merge branch 'development' into FME-8393-Events-Baseline
MartinCardozo-SDK Aug 14, 2025
a444c3b
Retry on failure for all tests
MartinCardozo-SDK Aug 15, 2025
74b6ce5
Flaky test of wrong JSON for Segments fixed
MartinCardozo-SDK Aug 15, 2025
ed87a88
Test Fixed
MartinCardozo-SDK Aug 15, 2025
6102e9f
Flaky test fixed
MartinCardozo-SDK Aug 15, 2025
b35691e
Merge branch 'development' into FME-8393-Events-Baseline
MartinCardozo-SDK Aug 15, 2025
0715bff
StorageHelper restored
MartinCardozo-SDK Aug 15, 2025
b09b580
Merge branch 'development' into FME-8393-Events-Baseline
MartinCardozo-SDK Aug 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Split.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,10 @@
59FB7C35220329B900ECC96A /* SplitFactoryBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C34220329B900ECC96A /* SplitFactoryBuilderTests.swift */; };
59FB7C3C2203795F00ECC96A /* LocalhostSplitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */; };
59FB7C3E22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */; };
5B0162682E4A9C7A0009D3B7 /* SplitEventsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0162672E4A9C5D0009D3B7 /* SplitEventsTests.swift */; };
5B26B0D72E4F70A00025AAB7 /* StorageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B343EAC2E26E937006BEBE7 /* StorageHelper.swift */; };
5B279CF92E340FC600B73A36 /* splitschanges_no_segments.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B279CF82E340FB900B73A36 /* splitschanges_no_segments.json */; };
5B343EAD2E26E93B006BEBE7 /* StorageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B343EAC2E26E937006BEBE7 /* StorageHelper.swift */; };
5B343EAE2E26E93B006BEBE7 /* StorageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B343EAC2E26E937006BEBE7 /* StorageHelper.swift */; };
5B48D8172DEA2CED00351925 /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; };
5B91B8392DDE4A3B000510F0 /* SplitDTOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */; };
5BF52DF72DE0B60700FEDAFE /* PrerequisitesMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF52DF52DE0B60300FEDAFE /* PrerequisitesMatcher.swift */; };
Expand Down Expand Up @@ -1560,6 +1561,7 @@
59FB7C34220329B900ECC96A /* SplitFactoryBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitFactoryBuilderTests.swift; sourceTree = "<group>"; };
59FB7C3B2203795F00ECC96A /* LocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalhostSplitsParser.swift; sourceTree = "<group>"; };
59FB7C3D22037B9400ECC96A /* SpaceDelimitedLocalhostSplitsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceDelimitedLocalhostSplitsParser.swift; sourceTree = "<group>"; };
5B0162672E4A9C5D0009D3B7 /* SplitEventsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitEventsTests.swift; sourceTree = "<group>"; };
5B279CF82E340FB900B73A36 /* splitschanges_no_segments.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = splitschanges_no_segments.json; sourceTree = "<group>"; };
5B343EAC2E26E937006BEBE7 /* StorageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageHelper.swift; sourceTree = "<group>"; };
5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitDTOTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2893,6 +2895,7 @@
isa = PBXGroup;
children = (
C539CAE52D947D2A0050C732 /* Common */,
5B0162672E4A9C5D0009D3B7 /* SplitEventsTests.swift */,
5B91B8382DDE4A30000510F0 /* SplitDTOTests.swift */,
C53EDFCC2DD4E10A000DCDBC /* SplitsSyncHelperWithProxyHandlerTests.swift */,
C53EDFCA2DD3E257000DCDBC /* OutdatedSplitProxyHandlerTests.swift */,
Expand Down Expand Up @@ -4249,7 +4252,7 @@
59F4AA9B24FE93E300A1C69A /* NotificationManagerKeeper.swift in Sources */,
95C1600D27D28CF4008562E3 /* PersistentAttributesStorage.swift in Sources */,
95726075262F548500350CCA /* SplitBgSynchronizer.swift in Sources */,
5B343EAE2E26E93B006BEBE7 /* StorageHelper.swift in Sources */,
5B26B0D72E4F70A00025AAB7 /* StorageHelper.swift in Sources */,
95C1600B27D28CB8008562E3 /* OneKeyPersistentAttributesStorage.swift in Sources */,
5BF52DF72DE0B60700FEDAFE /* PrerequisitesMatcher.swift in Sources */,
9519A91127D6935700278AEC /* ByKeyAttributesStorage.swift in Sources */,
Expand Down Expand Up @@ -4438,6 +4441,7 @@
95B1801E2763BF7E002DC9DF /* TelemetryStatsRecorderWorkerTests.swift in Sources */,
95F3F002258D3EE700084AF8 /* HttpEventsRecorderStub.swift in Sources */,
59D84BE3221734F5003DA248 /* LocalhostSplitClientTests.swift in Sources */,
5B0162682E4A9C7A0009D3B7 /* SplitEventsTests.swift in Sources */,
59B2043924F5667A0092F2E9 /* SseNotificationProcessorTest.swift in Sources */,
592C6AC6211B718E002D120C /* SplitEventsManagerTest.swift in Sources */,
95C7569D2696457500696148 /* NotificationHelperStub.swift in Sources */,
Expand Down
33 changes: 27 additions & 6 deletions Split/Api/DefaultSplitClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,37 @@ extension DefaultSplitClient {
task.event = event
on(event: event, executeTask: task)
}

private func on(event: SplitEvent, executeTask task: SplitEventTask) {
if event != .sdkReadyFromCache,
eventsManager.eventAlreadyTriggered(event: event) {
Logger.w("A handler was added for \(event.toString()) on the SDK, " +
"which has already fired and won’t be emitted again. The callback won’t be executed.")

private func on(event: SplitEvent, executeTask task: SplitEventActionTask) {
if event != .sdkReadyFromCache, eventsManager.eventAlreadyTriggered(event: event) {
Logger.w("A handler was added for \(event.toString()) on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.")
return
}
eventsManager.register(event: event, task: task)
}

// MARK: Listeners with Metadata
public func on(event: SplitEvent, executeWithMetadata action: @escaping SplitActionWithMetadata) {
on(event: event, runInBackground: true, queue: nil, executeWithMetadata: action)
}

public func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) {
on(event: event, runInBackground: runInBackground, queue: nil, executeWithMetadata: executeWithMetadata)
}

public func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue? = nil, executeWithMetadata action: @escaping SplitActionWithMetadata) {
on(event: event, runInBackground: runInBackground, queue: queue, action: action)
}

public func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) {
on(event: event, runInBackground: true, queue: queue, action: action)
}

private func on(event: SplitEvent, runInBackground: Bool, queue: DispatchQueue? = nil, action: @escaping SplitActionWithMetadata) {
guard let factory = clientManager?.splitFactory else { return }
let task = SplitEventActionTask(action: action, event: event, runInBackground: runInBackground, factory: factory, queue: queue)
on(event: event, executeTask: task)
}
}

// MARK: Treatment / Evaluation
Expand Down
12 changes: 12 additions & 0 deletions Split/Api/FailHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ class FailedClient: SplitClient {
func on(event: SplitEvent,
queue: DispatchQueue, execute action: @escaping SplitAction) {
}

func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {
/* Intentionally unimplemented */
}

func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) {
/* Intentionally unimplemented */
}

func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) {
/* Intentionally unimplemented */
}

func track(trafficType: String, eventType: String) -> Bool {
return false
Expand Down
12 changes: 12 additions & 0 deletions Split/Api/LocalhostSplitClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ public final class LocalhostSplitClient: NSObject, SplitClient {
eventsManager.register(event: event, task: task)
}
}

public func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata) {
/* Intentionally unimplemented */
}

public func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata) {
/* Intentionally unimplemented */
}

public func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata) {
/* Intentionally unimplemented */
}

public func track(trafficType: String, eventType: String) -> Bool {
return true
Expand Down
7 changes: 7 additions & 0 deletions Split/Api/SplitClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Foundation

public typealias SplitAction = () -> Void
public typealias SplitActionWithMetadata = (EventMetadata) -> Void

@objc public protocol SplitClient {

Expand All @@ -33,9 +34,15 @@ public typealias SplitAction = () -> Void
@objc(getTreatmentsWithConfigForSplits:attributes:evaluationOptions:)
func getTreatmentsWithConfig(splits: [String], attributes: [String: Any]?, evaluationOptions: EvaluationOptions?) -> [String: SplitResult]


// MARK: Events Listeners
func on(event: SplitEvent, execute action: @escaping SplitAction)
func on(event: SplitEvent, runInBackground: Bool, execute action: @escaping SplitAction)
func on(event: SplitEvent, queue: DispatchQueue, execute action: @escaping SplitAction)

func on(event: SplitEvent, executeWithMetadata: @escaping SplitActionWithMetadata)
func on(event: SplitEvent, runInBackground: Bool, executeWithMetadata: @escaping SplitActionWithMetadata)
func on(event: SplitEvent, queue: DispatchQueue, action: @escaping SplitActionWithMetadata)

// MARK: Track feature
func track(trafficType: String, eventType: String) -> Bool
Expand Down
15 changes: 10 additions & 5 deletions Split/Common/Structs/BlockingQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
var item: Item?
// Checks if stopped before waiting
try checkIfStopped()
self.semaphore.wait(timeout: .now() + .milliseconds(100))

Check warning on line 42 in Split/Common/Structs/BlockingQueue.swift

View workflow job for this annotation

GitHub Actions / test (SplitiOSUnit) / test

result of call to 'wait(timeout:)' is unused

Check warning on line 42 in Split/Common/Structs/BlockingQueue.swift

View workflow job for this annotation

GitHub Actions / test (SplitiOSUnit) / test

result of call to 'wait(timeout:)' is unused

Check warning on line 42 in Split/Common/Structs/BlockingQueue.swift

View workflow job for this annotation

GitHub Actions / test (SplitiOSStreaming_1) / test

result of call to 'wait(timeout:)' is unused

Check warning on line 42 in Split/Common/Structs/BlockingQueue.swift

View workflow job for this annotation

GitHub Actions / test (SplitiOSStreaming_1) / test

result of call to 'wait(timeout:)' is unused
try dispatchQueue.sync(flags: .barrier) {
// Checks if thread was awaked by stop or interruption
try checkIfStopped()
Expand Down Expand Up @@ -73,18 +73,23 @@

// Protocol to allow mocking
protocol InternalEventBlockingQueue {
func add(_ item: SplitInternalEvent)
func take() throws -> SplitInternalEvent
func add(_ item: SplitInternalEventWithMetadata)
func take() throws -> SplitInternalEventWithMetadata
func stop()
}

class DefaultInternalEventBlockingQueue: InternalEventBlockingQueue {
let blockingQueue = GenericBlockingQueue<SplitInternalEvent>()
func add(_ item: SplitInternalEvent) {
let blockingQueue = GenericBlockingQueue<SplitInternalEventWithMetadata>()

func add(_ item: SplitInternalEventWithMetadata) {
blockingQueue.add(item)
}

func add(_ item: SplitInternalEvent) {
blockingQueue.add(SplitInternalEventWithMetadata(item, metadata: nil))
}

func take() throws -> SplitInternalEvent {
func take() throws -> SplitInternalEventWithMetadata {
let value = try blockingQueue.take()
return value
}
Expand Down
20 changes: 15 additions & 5 deletions Split/Events/EventsManagerCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,30 @@ protocol SplitEventsManagerCoordinator: SplitEventsManager {
}

class MainSplitEventsManager: SplitEventsManagerCoordinator {

private var defaultManager: SplitEventsManager?
private var managers = [Key: SplitEventsManager]()
private var triggered = Set<SplitInternalEvent>()
private let queue = DispatchQueue(label: "split-event-manager-coordinator")
private let eventsToHandle: Set<SplitInternalEvent> = Set(
[.splitsLoadedFromCache,
.splitsUpdated,
.splitKilledNotification]
.splitsUpdated,
.splitKilledNotification,
.sdkError]
)

func notifyInternalEvent(_ event: SplitInternalEvent) {
if !eventsToHandle.contains(event) {
notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil))
}

func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) {
if !eventsToHandle.contains(event.type) {
return
}
queue.async { [weak self] in
guard let self = self else { return }

self.triggered.insert(event)
self.triggered.insert(event.type)
self.managers.forEach { _, manager in
manager.notifyInternalEvent(event)
}
Expand Down Expand Up @@ -77,4 +83,8 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator {
}

func register(event: SplitEvent, task: SplitEventTask) {}

func register(event: SplitEventWithMetadata, task: any SplitEventTask) {
/* Intentionally unimplemented */
}
}
29 changes: 21 additions & 8 deletions Split/Events/SplitEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,35 @@

import Foundation

@objcMembers public class SplitEventWithMetadata: NSObject {
let type: SplitEvent
let metadata: EventMetadata?

@objc public init(_ type: SplitEvent, metadata: EventMetadata? = nil) {
self.type = type
self.metadata = metadata
}
}

@objc public enum SplitEvent: Int {
case sdkReady
case sdkReadyTimedOut
case sdkReadyFromCache
case sdkUpdated
case sdkError

public func toString() -> String {
switch self {
case .sdkReady:
return "SDK_READY"
case .sdkUpdated:
return "SDK_UPDATE"
case .sdkReadyTimedOut:
return "SDK_READY_TIMED_OUT"
case .sdkReadyFromCache:
return "SDK_READY_FROM_CACHE"
case .sdkReady:
return "SDK_READY"
case .sdkUpdated:
return "SDK_UPDATE"
case .sdkReadyTimedOut:
return "SDK_READY_TIMED_OUT"
case .sdkReadyFromCache:
return "SDK_READY_FROM_CACHE"
case .sdkError:
return "SDK_ERROR"
}
}
}
33 changes: 21 additions & 12 deletions Split/Events/SplitEventActionTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,39 @@ import Foundation
class SplitEventActionTask: SplitEventTask {

private var eventHandler: SplitAction?
private var eventHandlerWithMetadata: SplitActionWithMetadata?
private var queue: DispatchQueue?

var event: SplitEvent
var runInBackground: Bool = false
var factory: SplitFactory

init(action: @escaping SplitAction,
event: SplitEvent,
runInBackground: Bool = false,
factory: SplitFactory,
queue: DispatchQueue? = nil) {

self.eventHandler = action
self.event = event
self.runInBackground = runInBackground
self.queue = queue
self.factory = factory
init(action: @escaping SplitActionWithMetadata, event: SplitEvent, runInBackground: Bool = false, factory: SplitFactory, queue: DispatchQueue? = nil) {
self.eventHandlerWithMetadata = action
self.event = event
self.runInBackground = runInBackground
self.queue = queue
self.factory = factory
}

init(action: @escaping SplitAction, event: SplitEvent, runInBackground: Bool = false, factory: SplitFactory, queue: DispatchQueue? = nil) {
self.eventHandler = action
self.event = event
self.runInBackground = runInBackground
self.queue = queue
self.factory = factory
}

func takeQueue() -> DispatchQueue? {
defer { queue = nil }
return queue
}

func run() {
func run(_ metadata: EventMetadata?) {
eventHandler?()

if let metadata = metadata {
eventHandlerWithMetadata?(metadata)
}
}
}
2 changes: 1 addition & 1 deletion Split/Events/SplitEventTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ protocol SplitEventTask {
var event: SplitEvent { get }
var runInBackground: Bool { get }
func takeQueue() -> DispatchQueue?
func run()
func run(_ metadata: EventMetadata?)
}
Loading
Loading