Skip to content

segments

Joseph Samir edited this page Jun 21, 2026 · 2 revisions

Segments

When analyzing A/B test results, you often want to break down the data by visitor characteristics:

  • "How did mobile users respond to variation B?"
  • "Did premium-tier users convert more often?"
  • "What was the conversion rate for visitors from Canada?"

These categorizations are called segments. The SDK needs a way to evaluate which segments a visitor belongs to and include that information in tracking data sent to the Convert dashboard.

What is SegmentsManager? The Sorting Desk

Think of the SegmentsManager as a sorting desk that categorizes incoming mail (visitors) into the right mailboxes (segments). It checks visitor attributes against segment rules and stores the results for use in tracking and reporting.

Its key responsibilities are:

  1. Evaluate Segments: Merge default segment dimensions or append custom segment IDs for a visitor.
  2. Store Segment Data: Persist segment assignments in the visitor's DecisionStore.
  3. Provide Segments for Tracking: Supply segment information to the EventQueue for inclusion in tracking payloads.

SegmentsManager (Sources/ConvertSwiftSDKCore/Segments/SegmentsManager.swift) is a stateless Sendable struct — it owns no mutable state. All per-visitor segment state lives in the injected DecisionStore actor, so every ConvertContext from the same SDK handle shares one segment store, and concurrent setters cannot lose updates (actor isolation is the mutual-exclusion mechanism, not locks).

How Segments Are Used

You interact with segments through the ConvertContext object:

1. Set Default Segments (for reporting):

Default segments are standard dimensions that appear in Convert reports. The six recognized wire keys are country, browser, devices, source, campaign, and visitorType. Unknown keys WARN and are ignored — the SDK never throws.

// given a ready `context`
await context.setDefaultSegments([
    "country": "US",
    "browser": "safari",
    "devices": "mobile",
    "source": "organic",
    "visitorType": "returning",
])

setDefaultSegments uses merge semantics: keys present in the dict overlay the visitor's existing segments; keys NOT in the dict are retained (prior country still applies if you only set browser, for example). This matches the JS SDK's objectDeepMerge({...existing, ...new}) contract (AR14, FR28-30).

2. Set Custom Segments:

Custom segments are caller-supplied string identifiers (not rule-evaluated — those are Config Segments; see the note at the bottom). They are appended to the visitor's existing customSegments list.

// given a ready `context`
await context.setCustomSegments(["vip", "beta-tester"])

setCustomSegments uses append semantics: the provided IDs are added to the visitor's existing customSegments array. Dedup is left to the backend (matching the JS SDK); the six standard keys are untouched.

3. Effect on Bucketing:

After calling setDefaultSegments, the persisted segment values are automatically overlaid onto the attribute map the audience-rule evaluator sees. For example, if you set country: "US" and an experience's audience has a rule country equals "US", the visitor will match — even if you did not pass country as an attribute in createContext.

let sdk = ConvertSwiftSDK(configuration: ConvertConfiguration(sdkKey: "your-sdk-key"))
try await sdk.ready()

let context = sdk.createContext(visitorId: "user-123")
await context.setDefaultSegments(["country": "US"])

// The audience rule for country is now satisfied for this visitor:
let variation = await context.runExperience("us-only-test")

Under the Hood: How Segments Are Stored

Merge vs Append

SegmentsManager exposes two actor-delegating methods:

// Merge overlay (six known string keys → Segments fields; unknown keys → WARN + ignore)
public func setDefaultSegments(_ dict: [String: String], forVisitorKey key: String) async

// Append custom IDs (concurrent-safe: read-append-write in one actor step)
public func setCustomSegments(_ segmentIds: [String], forVisitorKey key: String) async

Both delegate to the DecisionStore actor, which performs the read-modify-write as a single non-suspending step (no await between the read and the write within the actor). This is the Swift 6 actor-reentrancy-safe pattern: the mutation is atomic from the perspective of any concurrent caller (F-172, AC15).

The Segments Model

// From Sources/ConvertSwiftSDKCore/Models/Segments.swift (actual field names)
public struct Segments: Codable, Sendable {
    public var country: String?
    public var browser: String?
    public var devices: String?
    public var source: String?
    public var campaign: String?
    public var visitorType: String?
    public var customSegments: [String]
}

Sequence: setDefaultSegments

sequenceDiagram
    participant Ctx as ConvertContext
    participant SM as SegmentsManager
    participant DS as DecisionStore (actor)
    participant Bus as EventBus (actor)

    Ctx->>+SM: setDefaultSegments(["country": "US"], forVisitorKey:)
    Note over SM: Map known wire keys → Segments overlay
    SM->>+DS: mergeSegments(overlay, forVisitorKey:)
    Note over DS: Read existing → merge overlay → write back (one actor step, AC15)
    DS-->>-SM: Done
    SM->>+DS: currentSegments(forVisitorKey:)
    DS-->>-SM: Segments { country: "US", … }
    SM-->>-Ctx: (returns)
    Ctx->>+Bus: fire(.segments, payload: .segments(SegmentsPayload(…)))
    Bus-->>-Ctx: (dispatched to subscribers)
Loading

Segments vs. Audiences

While both segments and audiences involve categorizing visitors, they serve different purposes:

  • Audiences determine whether a visitor qualifies for an experience. They act as gatekeepers evaluated before bucketing. A visitor who does not match the audience never gets assigned a variation.
  • Default Segments (setDefaultSegments) categorize visitors for reporting and audience-rule attribute overlay. They affect which variation a visitor sees only indirectly — by making the audience evaluator see country: "US" when the visitor previously set it.
  • Custom Segments (setCustomSegments) are caller-supplied IDs that appear in reporting to let you filter experiment results by custom cohorts. They are not evaluated against rule sets.

Config Segments (a third concept): the ProjectConfig.segments array contains ConfigSegment entries that come from segmentation-type audiences authored in the Convert UI. These are server-side-resolved segments — the backend converts them into ConfigSegment objects whose rules the SDK can evaluate. SegmentsManager's setCustomSegments deals with caller-supplied IDs, not ConfigSegment rule evaluation (that happens inside ExperienceManager via RuleManager).

Persistence and the DecisionStore

Segments are stored in the DecisionStore alongside bucketing decisions and goal dedup marks. Because DecisionStore persists to an Application Support file (via CoordinatedFileStore) and is restored via loadFromDisk() at SDK init, segment assignments survive app restarts. A visitor who set country: "US" in a previous session will still have it when they reopen the app — no caller action required.

Summary

The SegmentsManager handles visitor categorization for Convert's reporting and audience-rule overlay. It is a stateless struct that delegates all per-visitor state to the DecisionStore actor.

Key takeaways:

  1. Segments are important for analyzing A/B test results by visitor characteristics.
  2. Default reporting segments cover standard dimensions (country, browser, devices, source, campaign, visitorType) via merge semantics.
  3. Custom segment IDs are appended (not merged) and deduped by the backend.
  4. Segment data persists across app launches via DecisionStoreCoordinatedFileStore.
  5. After setDefaultSegments, the values are automatically visible to the audience rule evaluator on the next runExperience call.
  6. Both setDefaultSegments and setCustomSegments fire SystemEvent.segments with a SegmentsPayload on the shared EventBus.

Clone this wiki locally