Skip to content

refactor(core): move global environment behind KlaviyoEnv holder for Swift 6#568

Draft
ndurell wants to merge 2 commits intomasterfrom
refactor/nd-20260429-klaviyo-env-holder
Draft

refactor(core): move global environment behind KlaviyoEnv holder for Swift 6#568
ndurell wants to merge 2 commits intomasterfrom
refactor/nd-20260429-klaviyo-env-holder

Conversation

@ndurell
Copy link
Copy Markdown
Contributor

@ndurell ndurell commented Apr 29, 2026

Description

Step 1.1 of the Swift 6 strict-concurrency migration. Replaces the public mutable global environment at Sources/KlaviyoCore/KlaviyoEnvironment.swift:13 with a typed holder KlaviyoEnv.current, gated behind @_spi(KlaviyoPrivate). The original environment symbol remains as a deprecated SPI shim for source compatibility while call sites migrate (chunk 1.2.*).

Due Diligence

  • I have tested this on a simulator or a physical device.
  • I have added sufficient unit/integration tests of my changes.
  • I have adjusted or added new test cases to team test docs, if applicable.
  • I am confident these changes are compatible with all iOS and XCode versions the SDK currently supports.

(No new tests — this is a pure refactor with zero behavior change. Existing test suite passes.)

Release/Versioning Considerations

  • Patch Contains internal changes or backwards-compatible bug fixes.
  • Minor Contains changes to the public API.
  • Major Contains breaking changes.
  • Contains readme or migration guide changes.
  • This is planned work for an upcoming release.

The new KlaviyoEnv symbol and the deprecated environment shim are both @_spi(KlaviyoPrivate) — they can only be reached via @_spi(KlaviyoPrivate) import KlaviyoCore. The pre-existing environment global was plain public; tightening it to SPI is a deliberate access narrowing. All call sites in this repo (and our sibling test apps, which already use the SPI import) are unaffected.

Changelog / Code Overview

New canonical entrypoint:

@_spi(KlaviyoPrivate) public enum KlaviyoEnv {
    nonisolated(unsafe) public static var current = KlaviyoEnvironment.production
}
  • nonisolated(unsafe) is the documented Apple stopgap for nonisolated global shared mutable state under -strict-concurrency=complete. Race surface is unchanged — labeled, not eliminated. Real isolation lands when the closures inside KlaviyoEnvironment get rehomed into per-feature containers in later chunks.

Deprecated source-compat shim:

@_spi(KlaviyoPrivate)
@available(*, deprecated, renamed: "KlaviyoEnv.current")
public var environment: KlaviyoEnvironment {
    get { KlaviyoEnv.current }
    set { KlaviyoEnv.current = newValue }
}

Knock-on changes required by the SPI gating:

  • All cross-module imports of KlaviyoCore flipped to @_spi(KlaviyoPrivate) import KlaviyoCore (40 source + test files). Mechanical change; no call-site edits.

  • Three public inits in KlaviyoCore reference environment in their default-argument values, which the compiler rejects for non-SPI public ABI (default args become part of the public interface):

    • KlaviyoAPI.init(send:)
    • KlaviyoRequest.init(id:endpoint:)
    • AppLifeCycleEvents.init(lifeCycleEvents:)

    These are gated @_spi(KlaviyoPrivate) to match. All current callers already have SPI access via the import flip; customer-facing surface (KlaviyoSDK, Klaviyo.shared, etc.) is unchanged.

  • Dropped @usableFromInline on runtimeWarn in LoggerClient.swift — no @inlinable callers exist in KlaviyoCore, and the annotation was inadvertently exposing its default args to the cross-module surface.

What chunk 1.2. will do (out of scope here):*

  • Migrate ~30 in-repo environment.X call sites to KlaviyoEnv.current.X (deprecation warnings flag them today).
  • Delete the deprecated environment shim once call sites are migrated.

Out of scope for the broader 1.x effort, separate chunks:

  • The networkSession global at KlaviyoEnvironment.swift:300.
  • Sibling test apps in klaviyo-{ios,swift}-test-app (no changes needed — they already use @_spi(KlaviyoPrivate) import KlaviyoCore).

Test Plan

  • xcodebuild build -scheme klaviyo-swift-sdk-Package -destination "platform=iOS Simulator,name=iPhone 17 Pro" — succeeds, with the expected deprecation warnings on existing environment.X call sites (to be cleared in chunk 1.2.*).
  • make test-library — full test suite passes.
  • Verified deprecation warnings flow through ('environment' is deprecated: renamed to 'KlaviyoEnv.current') on all in-repo call sites.

Related Issues/Tickets

Swift 6 strict concurrency initiative.

ndurell added 2 commits April 29, 2026 15:42
…Swift 6

Step 1.1 of the Swift 6 strict-concurrency migration. Replaces the public
mutable global `environment` with a typed holder `KlaviyoEnv.current`,
gated behind `@_spi(KlaviyoPrivate)`. The original `environment` symbol
remains as a deprecated SPI shim for source compatibility while call
sites migrate (chunk 1.2.*).

- New canonical entrypoint: `KlaviyoEnv.current` (SPI-gated, marked
  `nonisolated(unsafe)` per the documented Apple stopgap for nonisolated
  global shared mutable state under -strict-concurrency=complete).
- Deprecated shim: `environment` is now `@available(*, deprecated, renamed:
  "KlaviyoEnv.current")` and SPI-gated.
- All cross-module imports of KlaviyoCore in this repo flipped to
  `@_spi(KlaviyoPrivate) import KlaviyoCore` (40 files).
- Three public inits in KlaviyoCore (`KlaviyoAPI.init(send:)`,
  `KlaviyoRequest.init(id:endpoint:)`, `AppLifeCycleEvents.init`)
  reference `environment` in their default-argument values, which the
  compiler rejects for non-SPI public ABI; gated those inits SPI to
  match. All current callers already have SPI access via the import flip.
- Dropped `@usableFromInline` on `runtimeWarn` (no `@inlinable` callers
  in KlaviyoCore; was inadvertently exposing its default args to the
  cross-module surface).

Race surface is unchanged — labeled, not eliminated. Real isolation
lands when the closures inside `KlaviyoEnvironment` get rehomed into
per-feature containers in later chunks.

Out of scope (separate chunks):
- Migrating `environment.X` call sites to `KlaviyoEnv.current.X`.
- The `networkSession` global at KlaviyoEnvironment.swift:300.
- Sibling test apps (klaviyo-{ios,swift}-test-app).
`nonisolated(unsafe)` was added in Swift 5.10 (Xcode 15.3). The package
declares swift-tools-version 5.9 and CI builds against Xcode 15.2, so
the syntax fails to parse there. Gate it with a compiler check; the
Swift 5.9 path is plain `static var current`, which is fine because
strict concurrency isn't enabled on that toolchain anyway.

Also picks up SwiftFormat's reformatting of the holder declaration
(modifier order: `public nonisolated(unsafe) static var`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant