English | 日本語
Analytics events for Swift apps that declare how they should be counted, so the rule lives in the event's definition instead of in whoever remembers it at the call site.
This is not diagnostic logging (swift-log / swift-metrics). It records what people did, in a
shape you can analyse later.
Analytics bugs are more often "fires too much" than "does not fire", and neither is caught by types, tests, review, or the dashboard. The usual cause is not that a call was written in two places — it is that the counting rule was written nowhere, so nobody could call the second call site a mistake. This package puts the counting rule in the vocabulary.
- The catalog owns the counting rule. "Once per install" and "once it has been visible for a second" are declared in YAML, not remembered at the call site
- Swift is generated from that catalog. There is no string-based send, so an event cannot be misspelled or fired with parameters nobody declared
- Deduplication is enforced, not remembered.
DedupingAnalyticsapplies the declared scope; call sites never ask "did I already fire this" - Impressions have a definition that closes. 50% of the area, continuously visible for 1.0 second, and the tracker holds no clock — so the rule is pinned in a unit test, with no simulator and no real waiting
- A CI audit that grep cannot do. Declared-but-never-fired events, the same firing appearing in two places, and string sends that bypass the catalog all fail the build
- A first-party pipeline, if you want one.
AnalyticsBatchSinkkeeps events in a file and sends them in batches through a closure you write; the generator'sfirst_partydialect holds the catalog to a receiver's column limits and writes the receiver's copy of the catalog as JSON - An off switch that remembers.
AnalyticsSwitchdrops everything while the person has turned measurement off, keeps that choice across launches, and erases what was buffered - Zero external dependencies. Vendor SDKs (Firebase, PostHog) live in separate packages, because SwiftPM resolves dependencies per package. For your own server, the app writes the URL and the authentication
- An on-device log viewer. Read what was actually sent, with its kind, its counting rule, and a
×2on anything that fired twice — no Mac attached
Declare the event, and how it is counted:
# analytics.yaml
version: 1
dialect: ga4
swift:
event_type: AppEvent
events:
- name: paywall_shown
kind: impression # screen | impression | interaction | outcome
dedup: always # session | install | always
description: The paywall was actually seenGenerate the Swift, and commit the result — a change to analytics is a change to what you decided to measure, and the diff is the only review artifact:
Scripts/analytics-gen.py generate --schema analytics.yaml --out Sources/App/Generated/AppAnalytics.swiftThen the whole call site is one modifier:
import AnalyticsSwiftUI
PaywallView().trackScreen(.paywallShown)API reference and guides — including Getting Started, Counting Rules, and What Not to Send.
The catalog file format — including the first_party dialect, the token parameter type, and the
shape of the --json output — is specified, in Japanese, in Schema/SCHEMA.md.
.package(url: "https://github.com/no-problem-dev/swift-analytics.git", .upToNextMinor(from: "0.2.0"))| Product | Contents | Depends on |
|---|---|---|
AnalyticsCore |
Vocabulary, ports, counting rules | nothing |
AnalyticsSwiftUI |
Firing from views, on-device log viewer | SwiftUI |
AnalyticsBatchSink |
Keeps events on the device and sends them in batches to your own server | AnalyticsCore |
AnalyticsTesting |
Test doubles | nothing |
Vendor adapters are separate packages — see swift-analytics-firebase. Bundling one here would pull a vendor SDK into consumers that only use the vocabulary.
The generator runs on Python 3 alone; there is nothing to install.
- iOS 17.0+ / macOS 14.0+ / tvOS 17.0+ / watchOS 10.0+ / visionOS 1.0+
- Swift 6.2+
- Python 3 (for the catalog generator only)
MIT — see LICENSE.