Barriers Reference
Barriers provide a powerful mechanism to control when dispatches are sent to dispatchers in the Tealium SDK. They act as gates that can block or allow the flow of events based on various conditions such as network connectivity, batch size requirements, or custom business logic.
Overview
The barrier system consists of several key components:
Barrier: The core protocol that defines how barriers control dispatch flowConfigurableBarrier: Barriers that can be configured at runtime with settingsBarrierScope: Defines which dispatchers a barrier applies toBarrierState: Represents whether a barrier is open (allowing dispatches) or closed (blocking dispatches)BarrierRegistrar: Interface for registering custom barriers at runtime
Barriers are evaluated before each dispatch is sent to determine if the dispatch should proceed or be queued until conditions are met.
Basic Concepts
Barrier States
A barrier can be in one of two states:
public enum BarrierState {
case closed // Blocks dispatches from proceeding
case open // Allows dispatches to proceed
}
Barrier Scopes
Barriers can be applied to different scopes within the dispatch pipeline:
public enum BarrierScope {
case all // Apply to all dispatchers
case dispatcher(id: String) // Apply only to specific dispatcher
}
Flushable Barriers
Barriers can be marked as “flushable”, meaning they can be bypassed during flush operations (Tealium.flushEventQueue()). This is useful for ensuring critical events are sent even if certain barriers are closed:
// A barrier that can be bypassed during flush
var isFlushable: Observable<Bool> {
Observables.just(true)
// or dynamic logic to determine flushability
}
// A barrier that cannot be bypassed during flush
var isFlushable: Observable<Bool> {
Observables.just(false)
}
Note: If any barriers that are not flushable are closed, then the dispatch won’t proceed regardless of the flushable barriers’ states.
Built-in Barriers
There are two built-in barriers provided by the SDK: the connectivity barrier and the batching barrier. By default (if you don’t configure any of them at all), these are scoped: the connectivity barrier - to the Collect dispatcher, and the batching barrier is deactivated.
You can add Batching Barrier with no enforced settings - it will be scoped to Collect dispatcher only, and will use default batch size of 1.
config.addBarrier(Barriers.batching())
The scopes and other settings can be customized as usual with Programmatic > Remote > Local configuration options.
Connectivity Barrier
The connectivity barrier blocks dispatches when network connectivity is unavailable or doesn’t meet specified requirements.
// Add connectivity barrier that applies to collect dispatcher only (this is default behavior)
config.addBarrier(
Barriers.connectivity(defaultScopes: [.dispatcher(id: Modules.Types.collect)])
)
// Add connectivity barrier that applies to all dispatchers
config.addBarrier(
Barriers.connectivity(defaultScopes: [.all])
)
Configuration Options:
wifi_only: Whentrue, only allows dispatches over WiFi or Ethernet connections
Behavior:
- Blocks dispatches when no network connection is available
- Optionally blocks cellular connections when
wifi_onlyis enabled - Is flushable when any network connection is available
- Automatically opens when connectivity is restored
Batching Barrier
The batching barrier blocks dispatches until a specified number of events have been queued for a dispatcher.
// Add batching barrier that applies to all dispatchers
config.addBarrier(
Barriers.batching(defaultScopes: [.all])
)
// Add batching barrier that applies to specific dispatchers
config.addBarrier(
Barriers.batching(defaultScopes: [.dispatcher(id: "analytics_dispatcher")])
)
Configuration Options:
batch_size: Number of queued events required before opening the barrier
Behavior:
- Blocks dispatches until queue size reaches the configured batch size
- Uses
Dispatcher.dispatchLimitas batch size, ifbatch_sizevalue is greater than that - Uses batch size of 1 if configured value is negative or zero
- Always flushable during flush operations
Creating Custom Barriers
Simple Barrier Implementation
This example shows a simple time-based barrier that periodically opens for a brief window to allow dispatches through. The barrier opens every specified interval and then closes again after 1 second.
Important: Barriers are used from the Tealium worker queue internally by the SDK. Always use TealiumQueue.worker when creating timers or performing operations that change barrier state, as using other queues might lead to crashes.
Note: This non-configurable barrier can only be added by a custom module created by the user using the BarrierRegistrar.registerScopedBarrier(_:scopes:) method (see the Runtime Barrier Management section below). If you need to add barriers through the TealiumConfig, you should create a configurable barrier with its factory instead (see the Configurable Barrier Implementation section below).
class CustomTimerBarrier: Barrier {
private var timer: RepeatingTimer?
private var closeTimer: RepeatingTimer?
@StateSubject(.closed)
private var state: ObservableState<BarrierState>
init(interval: TimeInterval) {
timer = RepeatingTimer(timeInterval: interval, queue: TealiumQueue.worker) { [weak self] in
self?._state.value = .open
// Schedule closing the barrier after 1 second
self?.closeTimer = RepeatingTimer(timeInterval: 1.0, repeating: .never, queue: TealiumQueue.worker) { [weak self] in
self?._state.value = .closed
}
self?.closeTimer?.resume()
}
timer?.resume()
}
deinit {
timer?.suspend()
closeTimer?.suspend()
}
func onState(for dispatcherId: String) -> Observable<BarrierState> {
state
}
// This barrier can be bypassed during flush operations
var isFlushable: Observable<Bool> {
Observables.just(true)
}
}
Configurable Barrier Implementation
class CustomBusinessLogicBarrier: ConfigurableBarrier {
static var id: String = "CustomBusinessLogicBarrier"
@StateSubject(.open)
private var state: ObservableState<BarrierState>
private var settings: BusinessLogicSettings
init(configuration: DataObject) {
self.settings = BusinessLogicSettings(dataObject: configuration)
updateBarrierState()
}
func onState(for dispatcherId: String) -> Observable<BarrierState> {
state
}
func updateConfiguration(_ configuration: DataObject) {
settings = BusinessLogicSettings(dataObject: configuration)
updateBarrierState()
}
private func updateBarrierState() {
// Custom business logic to determine barrier state
let shouldBlock = settings.maintenanceMode ||
!settings.allowedUserTypes.contains(getCurrentUserType())
_state.value = shouldBlock ? .closed : .open
}
// Not flushable - business logic barriers should not be bypassed
var isFlushable: Observable<Bool> {
Observables.just(false)
}
}
struct BusinessLogicSettings {
let maintenanceMode: Bool
let allowedUserTypes: [String]
init(dataObject: DataObject) {
maintenanceMode = dataObject.get(key: "maintenance_mode") ?? false
allowedUserTypes = dataObject.getArray(key: "allowed_user_types", of: String.self)?
.compactMap { $0 } ?? []
}
}
Barrier Factory Implementation
extension CustomBusinessLogicBarrier {
class Factory: BarrierFactory {
private let _defaultScopes: [BarrierScope]
init(defaultScopes: [BarrierScope]) {
self._defaultScopes = defaultScopes
}
func create(context: TealiumContext, configuration: DataObject) -> CustomBusinessLogicBarrier {
CustomBusinessLogicBarrier(configuration: configuration)
}
func defaultScopes() -> [BarrierScope] {
_defaultScopes
}
}
}
Configuration
Programmatic Configuration
// Register barrier factory during SDK initialization
// Only scopes can be set programmatically; other settings are handled via remote/local configuration
config.addBarrier(
CustomBusinessLogicBarrier.Factory(defaultScopes: [.all])
)
JSON Configuration
Barrier configuration is handled through remote settings or local JSON files. The barrier will receive its configuration when created by the factory.
{
"barriers": {
"ConnectivityBarrier": {
"barrier_id": "ConnectivityBarrier",
"scopes": ["all"],
"configuration": {
"wifi_only": true
}
},
"BatchingBarrier": {
"barrier_id": "BatchingBarrier",
"scopes": ["analytics_dispatcher", "collect_dispatcher"],
"configuration": {
"batch_size": 10
}
}
}
}
The barriers will automatically receive updateConfiguration() calls when settings change.
Runtime Barrier Management
Registering Barriers at Runtime
If, for some reason, your Module implementation needs to register barriers at runtime, you can do so via the BarrierRegistrar:
// Get the barrier registrar from `TealiumContext`
let barrierRegistrar = context.barrierRegistrar
// Create and register a custom barrier
let customBarrier = CustomTimerBarrier(interval: 30.0)
barrierRegistrar.registerScopedBarrier(
customBarrier,
scopes: [.dispatcher(id: "my_dispatcher")]
)
// Unregister when no longer needed
barrierRegistrar.unregisterScopedBarrier(customBarrier)
Advanced Barrier Patterns
Conditional Barriers
Create barriers that change behavior based on application status:
class ConditionalBarrier: ConfigurableBarrier {
static var id: String = "ConditionalBarrier"
@StateSubject(.open)
private var state: ObservableState<BarrierState>
private let onApplicationStatus: Observable<ApplicationStatus>
private var subscription: Disposable?
init(onApplicationStatus: Observable<ApplicationStatus>, configuration: DataObject) {
self.onApplicationStatus = onApplicationStatus
// Update barrier state based on application status
subscription = onApplicationStatus.subscribe { [weak self] appStatus in
let shouldBlock = appStatus.type == .backgrounded
self?._state.value = shouldBlock ? .closed : .open
}
}
deinit {
subscription?.dispose()
}
func onState(for dispatcherId: String) -> Observable<BarrierState> {
state
}
func updateConfiguration(_ configuration: DataObject) {
// Handle configuration updates
let blockInBackground = configuration.get(key: "block_in_background") ?? true
// Update logic based on configuration
}
}
Dispatcher-Specific Barriers
Create barriers that behave differently for different dispatchers:
class DispatcherSpecificBarrier: Barrier {
private let highPriorityDispatchers: Set<String>
@StateSubject(.closed)
private var generalState: ObservableState<BarrierState>
init(highPriorityDispatchers: [String]) {
self.highPriorityDispatchers = Set(highPriorityDispatchers)
}
func onState(for dispatcherId: String) -> Observable<BarrierState> {
if highPriorityDispatchers.contains(dispatcherId) {
// High priority dispatchers are never blocked
return Observables.just(.open)
} else {
// Other dispatchers follow general state
return generalState
}
}
func setGeneralState(_ state: BarrierState) {
_generalState.value = state
}
}
Time-Based Barriers
Create barriers that open and close based on time schedules:
class ScheduledBarrier: ConfigurableBarrier {
static var id: String = "ScheduledBarrier"
@StateSubject(.open)
private var state: ObservableState<BarrierState>
private var allowedHours: ClosedRange<Int> = 9...17
private var timer: Timer?
init(configuration: DataObject) {
updateConfiguration(configuration)
startTimer()
}
func onState(for dispatcherId: String) -> Observable<BarrierState> {
state
}
func updateConfiguration(_ configuration: DataObject) {
let startHour = configuration.get(key: "start_hour") ?? 9
let endHour = configuration.get(key: "end_hour") ?? 17
allowedHours = startHour...endHour
updateStateForCurrentTime()
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 3600, repeats: true) { [weak self] _ in
self?.updateStateForCurrentTime()
}
}
private func updateStateForCurrentTime() {
let currentHour = Calendar.current.component(.hour, from: Date())
_state.value = allowedHours.contains(currentHour) ? .open : .closed
}
}
Flush Operations
Understanding Flush Behavior
Flush operations allow you to bypass flushable barriers to ensure important events are sent:
// Trigger a flush operation
tealium.flushEventQueue()
// Flush is automatically triggered when:
// - Application status changes (backgrounded, foregrounded, initialized)
// - The system detects queued events that need to be sent
Controlling Flush Behavior
class SelectivelyFlushableBarrier: Barrier {
@StateSubject(true)
private var canFlush: ObservableState<Bool>
func onState(for dispatcherId: String) -> Observable<BarrierState> {
// Barrier logic
return Observables.just(.closed)
}
var isFlushable: Observable<Bool> {
canFlush
}
func setFlushable(_ flushable: Bool) {
_canFlush.value = flushable
}
}
Error Handling and Best Practices
Error Handling
class RobustBarrier: ConfigurableBarrier {
static var id: String = "RobustBarrier"
@StateSubject(.open)
private var state: ObservableState<BarrierState>
func onState(for dispatcherId: String) -> Observable<BarrierState> {
do {
let calculatedState = try calculateBarrierState(for: dispatcherId)
_state.value = calculatedState
} catch {
// Log error and default to open to avoid blocking dispatches
print("Barrier error: \(error)")
_state.value = .open
}
return state
}
private func calculateBarrierState(for dispatcherId: String) throws -> BarrierState {
// Barrier logic that might throw
return .open
}
func updateConfiguration(_ configuration: DataObject) {
// Safely handle configuration updates
do {
let newState = try processConfiguration(configuration)
_state.value = newState
} catch {
print("Configuration error: \(error)")
// Keep current state on configuration errors
}
}
private func processConfiguration(_ configuration: DataObject) throws -> BarrierState {
// Configuration processing that might throw
return .open
}
}
Best Practices
1. Use Appropriate Scopes
// Good: Connectivity barrier only for network-dependent dispatchers
config.addBarrier(Barriers.connectivity(defaultScopes: [.dispatcher(id: "collect")]))
// Avoid: Applying connectivity barrier to local-only dispatchers unnecessarily
config.addBarrier(Barriers.connectivity(defaultScopes: [.all])) // if you have local-only dispatchers
2. Handle Configuration Gracefully
func updateConfiguration(_ configuration: DataObject) {
// Provide sensible defaults
let batchSize = max(configuration.get(key: "batch_size") ?? 1, 1)
let timeout = configuration.get(key: "timeout") ?? 30.0
// Validate configuration
guard timeout > 0 else {
print("Invalid timeout configuration, using default")
return
}
// Apply configuration
applySettings(batchSize: batchSize, timeout: timeout)
}
3. Implement Proper Resource Management
class ResourceManagedBarrier: ConfigurableBarrier {
private var timer: Timer?
private var subscription: Disposable?
deinit {
timer?.invalidate()
subscription?.dispose()
}
// Implementation...
}
4. Use TealiumQueue for State Changes
Always use TealiumQueue.worker when changing barrier state or performing operations that affect barrier behavior. Barriers are used from the Tealium worker queue internally by the SDK, and using other queues can lead to crashes:
// Good: Using TealiumQueue.worker
class SafeBarrier: Barrier {
@StateSubject(.open)
private var state: ObservableState<BarrierState>
func updateState(_ newState: BarrierState) {
TealiumQueue.worker.ensureOnQueue { [weak self] in
self?._state.value = newState
}
}
}
// Avoid: Using other queues for state changes
class UnsafeBarrier: Barrier {
func updateState(_ newState: BarrierState) {
DispatchQueue.main.async { // This could cause crashes
self._state.value = newState
}
}
}
5. Use Descriptive Barrier IDs
// Good
static var id: String = "UserPermissionBarrier"
// Avoid
static var id: String = "Barrier1"
6. Consider Performance Impact
// Efficient: Cache expensive calculations
class EfficientBarrier: Barrier {
private var cachedState: BarrierState = .open
private var lastCalculation: Date = .distantPast
private let cacheTimeout: TimeInterval = 60
func onState(for dispatcherId: String) -> Observable<BarrierState> {
if Date().timeIntervalSince(lastCalculation) > cacheTimeout {
cachedState = calculateExpensiveState()
lastCalculation = Date()
}
return Observables.just(cachedState)
}
}
Integration with Other SDK Features
Barriers and LoadRules
Barriers and LoadRules work together to provide comprehensive dispatch control:
// LoadRules determine IF a module should process a dispatch
config.setLoadRule(
Rule.just(Condition.equals(ignoreCase: false, variable: "user_type", target: "premium")),
forId: "premium_analytics"
)
// Barriers determine WHEN dispatches should be sent
config.addBarrier(
Barriers.connectivity(defaultScopes: [.all])
)
Barriers and Transformations
Barriers control dispatch timing while transformations modify dispatch content. The order of execution depends on the transformation scope:
.afterCollectorsscope: Transformations happen before barriers are checked.allDispatchersand.dispatcher(id)scopes: Transformations happen after a successful barrier check
// This transformation runs BEFORE barrier checks
let preBarrierTransformation = TransformationSettings(
id: "user_enrichment",
transformerId: "enrichment_transformer",
scopes: [.afterCollectors] // Runs before barriers
)
// This transformation runs AFTER barrier checks pass
let postBarrierTransformation = TransformationSettings(
id: "data_formatting",
transformerId: "format_transformer",
scopes: [.dispatcher(id: "AnalyticsDispatcher")] // Runs after barriers
)
This execution order ensures that:
- Data collection transformations (
.afterCollectors) always run regardless of barrier state - Dispatch-specific transformations only run when barriers allow the dispatch to proceed
- Resources aren’t wasted on transformations for dispatches that will be blocked
Monitoring and Debugging
Barrier State Monitoring
Barrier state monitoring is handled internally by the SDK. You can observe the effects of barriers through:
- Event delivery timing
- Queue behavior during network connectivity changes
- Batch dispatch patterns
Queue Monitoring
Queue monitoring is handled internally by the SDK. The batching barrier automatically responds to queue size changes, and you can observe its effects through the timing of event dispatches.
Conclusion
Barriers provide essential control over when your analytics data is sent, ensuring optimal performance, respecting user preferences, and maintaining data quality. By understanding and properly implementing barriers, you can create a robust analytics implementation that adapts to various conditions and requirements.
-
Utility object for getting built-in
BarrierFactoryobjects when configuring the Tealium instance.Some barriers are added to the system by default, but remain accessible here to allow users to override the “scopes” that they apply to.
See moreDeclaration
Swift
public enum Barriers -
An object that will change its state to stop or allow dispatching of events to some dispatchers.
See moreDeclaration
Swift
public protocol Barrier : AnyObject -
The [BarrierFactory] is responsible for creating new [ConfigurableBarrier] instances.
See moreDeclaration
Swift
public protocol BarrierFactory<BarrierType> -
The
BarrierScopedefines the available scopes that can be assigned to aBarriervia aBarrierSettings.There are only two available scopes that a
Barriercan impact:alldispatcher
A
See moreBarrierscoped toallwill be checked for its state for every dispatcher before dispatching events to it. ABarrierscoped todispatcherwill only be checked for its state for the specific dispatcher as identified by the given dispatcher name.Declaration
Swift
public enum BarrierScope : RawRepresentable, Equatableextension BarrierScope: DataInputConvertible -
A builder class for configuring barrier settings.
See moreDeclaration
Swift
open class BarrierSettingsBuilder -
Represents the current state of a barrier that controls dispatch flow.
See moreDeclaration
Swift
public enum BarrierState -
A builder used to configure the
See moreBatchingBarriersettings.Declaration
Swift
public class BatchingBarrierSettingsBuilder : BarrierSettingsBuilder -
A builder used to configure the
See moreConnectivityBarriersettings.Declaration
Swift
public class ConnectivityBarrierSettingsBuilder : BarrierSettingsBuilder