-
Notifications
You must be signed in to change notification settings - Fork 84
Description
Iterable Swift SDK: Architecture Documentation
This issue documents a comprehensive analysis of the error reporting, logging, and offline mode systems in the Iterable Swift SDK. This serves as developer reference documentation.
Table of Contents
1. Error Reporting & Logging System
1.1 Logging Architecture
Log Levels
The SDK defines three log levels in Constants.swift:492:
@objc(IterableLogLevel) public enum LogLevel: Int {
case debug = 1
case info
case error
}Global Logging Functions
Three convenience functions in IterableLogging.swift:
| Function | Level | Emoji Marker |
|---|---|---|
ITBDebug() |
.debug |
π |
ITBInfo() |
.info |
π |
ITBError() |
.error |
β€οΈ |
Each captures file, method, line number, and thread pointer automatically.
Log Message Format
Generated by IterableLogUtil.formatLogMessage:
HH:mm:ss.SSSS:0x7ff8c9c0e:FileName:methodName:123: Your message here
Built-in Log Delegates
| Delegate | Behavior |
|---|---|
DefaultLogDelegate |
Uses os_log for messages >= minLogLevel (default: .info) |
AllLogDelegate |
Prints everything to console via print() |
NoneLogDelegate |
Silent - no output |
Custom Log Delegate Protocol
@objc public protocol IterableLogDelegate: AnyObject {
@objc(log:message:)
func log(level: LogLevel, message: String)
}1.2 Error Type Hierarchy
| Error Type | Location | Purpose |
|---|---|---|
IterableError |
Pending.swift:7 |
Generic SDK errors |
SendRequestError |
RequestSender.swift:9 |
Network request failures |
NetworkError |
NetworkHelper.swift:7 |
Low-level network errors |
IterableTaskError |
IterableTaskError.swift:7 |
Offline task queue errors |
SendRequestError Structure
public struct SendRequestError: Error {
public let reason: String? // Human-readable description
public let data: Data? // Raw response data
public let httpStatusCode: Int? // HTTP status code
public let iterableCode: String? // Iterable-specific error code (401 responses)
public let originalError: Error? // Underlying error
}Authentication Error Types
AuthFailure:
@objcMembers public class AuthFailure: NSObject {
public let userKey: String?
public let failedAuthToken: String?
public let failedRequestTime: Int
public let failureReason: AuthFailureReason
}AuthFailureReason enum:
authTokenExpiredauthTokenGenericErrorauthTokenExpirationInvalidauthTokenSignatureInvalidauthTokenFormatInvalidauthTokenInvalidatedauthTokenPayloadInvalidauthTokenUserKeyInvalidauthTokenNullauthTokenGenerationErrorauthTokenMissing
1.3 Asynchronous Error Propagation
The SDK uses a custom Pending<Value, Failure> Promise implementation:
public class Pending<Value, Failure> where Failure: Error {
func onSuccess(block: @escaping ((Value) -> Void)) -> Pending<Value, Failure>
func onError(block: @escaping ((Failure) -> Void)) -> Pending<Value, Failure>
func map<NewValue>(_ closure: @escaping (Value) -> NewValue) -> Pending<NewValue, Failure>
func flatMap<NewValue>(_ closure: @escaping (Value) -> Pending<NewValue, Failure>) -> Pending<NewValue, Failure>
func mapFailure<NewFailure>(_ closure: @escaping (Failure) -> NewFailure) -> Pending<Value, NewFailure>
}1.4 Retry Mechanisms
Network Retry (NetworkHelper.swift)
- Max retries: 5
- Retry delay: 2 seconds (after first 2 immediate retries)
- Only retries on 5xx server errors
- 4xx errors fail immediately
JWT Auth Token Retry (RetryPolicy)
public class RetryPolicy {
var maxRetry: Int // Default: 10
var retryInterval: Double // Default: 6 seconds
var retryBackoff: BackoffType // .linear or .exponential
}1.5 Health Monitoring & Circuit Breaker
HealthMonitor implements a circuit breaker pattern:
- Tracks database errors
- When tripped,
canSchedule()andcanProcess()returnfalse RequestHandlerfalls back to online-only mode- Max tasks limit: 1000
1.6 Notification-Based Error Events
static let iterableTaskScheduled = Notification.Name("itbl_task_scheduled")
static let iterableTaskFinishedWithSuccess = Notification.Name("itbl_task_finished_with_success")
static let iterableTaskFinishedWithRetry = Notification.Name("itbl_task_finished_with_retry")
static let iterableTaskFinishedWithNoRetry = Notification.Name("itbl_task_finished_with_no_retry")
static let iterableNetworkOffline = Notification.Name("itbl_network_offline")
static let iterableNetworkOnline = Notification.Name("itbl_network_online")2. Offline Mode & Task Persistence
2.1 Architecture Overview
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RequestHandler β
β chooseRequestProcessor() β checks HealthMonitor.canSchedule() β
βββββββββββββββββββ¬βββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ
β OnlineRequestProcessor β β OfflineRequestProcessor β
β (Immediate HTTP) β β β
βββββββββββββββββββββββββββ β IterableTaskScheduler β
β β β
β CoreData (SQLite) β
β β β
β IterableTaskRunner β
β β β
β IterableAPICallTaskProcessor β
βββββββββββββββββββββββββββββββββββ
2.2 Offline Mode Activation
Controlled by remote configuration from Iterable backend:
private func checkRemoteConfiguration() {
requestHandler.getRemoteConfiguration().onSuccess { remoteConfiguration in
self.localStorage.offlineMode = remoteConfiguration.offlineMode
self.requestHandler.offlineMode = remoteConfiguration.offlineMode
}
}2.3 CoreData Schema
Entity: IterableTaskManagedObject
| Attribute | Type | Notes |
|---|---|---|
id |
String | UUID, indexed |
name |
String? | API path |
type |
String | Always "apiCall" |
data |
Binary | JSON-encoded request |
scheduledAt |
Date | Execution time, indexed |
requestedAt |
Date | Original request time |
createdAt |
Date | Record creation |
attempts |
Int64 | Retry count |
processing |
Bool | Lock flag |
failed |
Bool | Permanent failure |
blocking |
Bool | Blocks queue |
2.4 Task Scheduling Flow
OfflineRequestProcessor.sendIterableRequest()called- Creates
IterableAPICallRequestwith full request data IterableTaskScheduler.schedule()encodes to JSON and saves to CoreData- Posts
.iterableTaskSchedulednotification - Returns
Pending<>mapped to taskId
2.5 Task Runner
- Listens for: task scheduled, app foreground, connectivity changes
- Processes queue FIFO by
scheduledAt - Default interval: 1 minute between checks
- Respects
HealthMonitor.canProcess()guard
2.6 Network Connectivity Detection
Dual-layer approach:
- NWPathMonitor - Passive OS-level network change detection
- Active HTTP probing - Checks apple.com and google.com
Polling intervals:
- Offline: 1 minute
- Online: 10 minutes
2.7 Task Result Handling
enum IterableTaskResult {
case success(detail: TaskSuccessDetail?)
case failureWithRetry(retryAfter: TimeInterval?, detail: TaskFailureDetail?)
case failureWithNoRetry(detail: TaskFailureDetail?)
}Retry decision:
- Error contains "offline" β retry later
- HTTP 4xx β no retry, delete task
- HTTP 5xx β no retry (already retried by NetworkHelper)
2.8 Supported Offline Operations
β Queued offline:
updateCart()trackPurchase()trackPushOpen()track(event:)trackInAppOpen/Click/Close/Delivery()track(inboxSession:)inAppConsume()- Embedded message events
β Always online:
register(token:)disableDevice()updateUser()updateEmail()updateSubscriptions()- Remote configuration fetch
- In-app message fetch
2.9 Timing Constants
| Constant | Value |
|---|---|
| Task runner interval | 1 minute |
| Offline polling | 1 minute |
| Online polling | 10 minutes |
| Connectivity timeout | 5 seconds |
| Max queue size | 1000 tasks |
Future Additions
This issue will be updated with additional deep dives into:
- JWT Authentication Flow
- In-App Messaging System
- Unknown User Tracking
- Embedded Messaging
- Request Building & Validation
This documentation was created through code analysis of the Iterable Swift SDK.