Skip to content

Documentation: Error Reporting, Logging & Offline Mode Architecture Deep DiveΒ #998

@RGG-jayoung

Description

@RGG-jayoung

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
  2. Offline Mode & Task Persistence

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:

  • authTokenExpired
  • authTokenGenericError
  • authTokenExpirationInvalid
  • authTokenSignatureInvalid
  • authTokenFormatInvalid
  • authTokenInvalidated
  • authTokenPayloadInvalid
  • authTokenUserKeyInvalid
  • authTokenNull
  • authTokenGenerationError
  • authTokenMissing

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() and canProcess() return false
  • RequestHandler falls 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

  1. OfflineRequestProcessor.sendIterableRequest() called
  2. Creates IterableAPICallRequest with full request data
  3. IterableTaskScheduler.schedule() encodes to JSON and saves to CoreData
  4. Posts .iterableTaskScheduled notification
  5. 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:

  1. NWPathMonitor - Passive OS-level network change detection
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions