iOS SDK (Native Swift)
AppSpacer — Asset OTA updates and crash reporting for native iOS apps, distributed as a Swift Package.
Update model: Apple’s App Store guidelines prohibit hot-swapping executable code in native apps. AppSpacer for iOS delivers asset OTA updates — JSON configs, images, string files, remote content — that your app reads from the OTA cache at runtime.
Requirements
| Minimum | |
|---|---|
| iOS | 14.0 |
| macOS | 12.0 |
| Swift | 5.9 |
| Xcode | 15 |
Installation
Swift Package Manager
In Xcode: File → Add Package Dependencies → enter:
https://github.com/appspacer/appspacer-iosOr add to Package.swift:
dependencies: [
.package(url: "https://github.com/appspacer/appspacer-ios.git", from: "1.0.0")
]Initialization
Call AppSpacer.initialize(config:) in your AppDelegate or @main App struct:
import AppSpacer
@main
struct MyApp: App {
init() {
Task {
await AppSpacer.initialize(config: AppSpacerConfig(
apiKey: "your_api_key",
appVersion: "1.0.0"
))
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}Config Options
| Property | Type | Default | Description |
|---|---|---|---|
apiKey | String | Required | Your AppSpacer API key |
appVersion | String | Required | Current app version |
updateStrategy | UpdateStrategy | .onInit | .onInit, .background, or .manual |
debugLogging | Bool | false | Verbose console logging |
crashReportingEnabled | Bool | true | Capture native crashes |
Reading OTA Assets
Always provide a fallback to your bundled resource:
import AppSpacer
struct ContentView: View {
@State private var config: [String: Any]?
var body: some View {
Text(config?["greeting"] as? String ?? "Hello!")
.task {
config = await AppSpacer.readAssetJSON("config/app.json")
}
}
}// Raw bytes
let imageData = await AppSpacer.readAsset("images/banner.png")
// UTF-8 string
let csvContent = await AppSpacer.readAssetString("data/products.csv")Manual Update Check
let result = await AppSpacer.checkForUpdates()
switch result.status {
case .updated:
print("Updated \(result.updatedCount) asset(s)")
case .upToDate:
print("Already up to date")
case .noNetwork:
print("Offline — will retry on next launch")
case .failed:
print("Error: \(result.error ?? "unknown")")
}Crash Reporting
Crash reporting is enabled by default. Uncaught exceptions are captured via NSSetUncaughtExceptionHandler and pending reports are flushed on next launch.
// Report a handled error
do {
try riskyOperation()
} catch {
await AppSpacer.reportError(error, context: "PaymentFlow")
}
// Breadcrumbs for crash context
AppSpacer.addBreadcrumb("User tapped checkout", category: "navigation")
AppSpacer.addBreadcrumb("Payment API called", category: "network", level: .info)API Reference
| Method | Description |
|---|---|
AppSpacer.initialize(config:) | Initialize — call once at app startup |
AppSpacer.checkForUpdates() | Check and download new assets |
AppSpacer.readAsset(_:) | Read cached asset as Data |
AppSpacer.readAssetString(_:) | Read cached asset as String |
AppSpacer.readAssetJSON(_:) | Read and parse cached JSON asset |
AppSpacer.clearAssetCache() | Delete all cached assets |
AppSpacer.cacheSize() | Total cached bytes |
AppSpacer.reportError(_:context:extra:) | Report a handled error |
AppSpacer.addBreadcrumb(_:category:level:) | Add crash context breadcrumb |
AppSpacer.sessionId | Current session identifier |
Backend Integration
GET /api/sdk/ios/manifest
Headers: x-api-key: <your_api_key>
Query: app_version, current_bundle_id, install_id
POST /api/sdk/ios/report-status
Body: { release_id, device_id, app_version, status, error_message }Send
install_id(a stable per-device ID) on every manifest call — it is required for staged-rollout targeting and monthly active-user metering. Reportinstalled/failedback toreport-statusso egress is billed once per device and failed installs can trigger auto-rollback.
Upload asset bundles from the dashboard with --platform ios.