iOS SDK

Native iOS SDK written in Swift

iOS SDK

The Entrolytics/iOS package provides a native iOS SDK written in Swift for analytics tracking with automatic lifecycle management, crash reporting, and battery optimization.

Installation

# Podfile
pod 'Entrolytics/iOS'

Info.plist Configuration

Add to Info.plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>entrolytics.dev</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

Quick Start

Initialize SDK

// AppDelegate.swift
import UIKit
import Entrolytics

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        // Initialize Entrolytics
        Entrolytics.initialize(
            config: Config.Builder()
                .websiteId("your-website-id")
                .apiKey("your-api-key")
                .debug(isDebug)
                .autoTrackScreens(true)
                .trackCrashes(true)
                .build()
        )

        return true
    }
}

Track Events

// ViewController.swift
import UIKit
import Entrolytics

class ViewController: UIViewController {

    @IBOutlet weak var signupButton: UIButton!
    @IBOutlet weak var featureCard: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupEventTracking()
    }

    private func setupEventTracking() {
        // Track button click
        signupButton.addAction(UIAction { _ in
            Entrolytics.track("signup_button_click", properties: [
                "screen": "ViewController",
                "button_text": "Sign Up"
            ])
        }, for: .touchUpInside)

        // Track feature discovery
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(featureTapped))
        featureCard.addGestureRecognizer(tapGesture)
    }

    @objc private func featureTapped() {
        Entrolytics.track("feature_discovery", properties: [
            "feature_name": "premium_analytics",
            "source": "main_card"
        ])
    }
}

Track Screens

// BaseViewController.swift
import UIKit
import Entrolytics

class BaseViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Track screen view automatically
        Entrolytics.screen(
            name: getScreenName(),
            properties: getScreenProperties()
        )
    }

    // Override in subclasses
    func getScreenName() -> String {
        return String(describing: type(of: self))
    }

    func getScreenProperties() -> [String: Any] {
        return [:]
    }
}

// Usage
class ProfileViewController: BaseViewController {

    override func getScreenName() -> String {
        return "ProfileScreen"
    }

    override func getScreenProperties() -> [String: Any] {
        return [
            "user_authenticated": currentUser != nil,
            "has_premium": currentUser?.isPremium ?? false
        ]
    }
}

Configuration

Configuration Builder

import Entrolytics

let config = Config.Builder()
    .websiteId("your-website-id")           // required
    .apiKey("your-api-key")                 // optional
    .host("https://entrolytics.dev")   // custom host
    .debug(isDebug)                         // debug mode
    .autoTrackScreens(true)                 // auto-track screens
    .trackCrashes(true)                     // track crashes
    .trackAppLifecycle(true)                // track app lifecycle
    .batchSize(50)                          // batch size
    .flushInterval(30.0)                    // flush interval (seconds)
    .timeout(10.0)                          // request timeout (seconds)
    .maxRetries(3)                          // max retry attempts
    .batteryOptimization(true)              // battery optimization
    .offlineSupport(true)                   // offline support
    .build()

Configuration via Plist

<!-- Entrolytics.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>WebsiteId</key>
    <string>your-website-id</string>
    <key>ApiKey</key>
    <string>your-api-key</string>
    <key>Host</key>
    <string>https://entrolytics.dev</string>
    <key>Debug</key>
    <false/>
    <key>AutoTrackScreens</key>
    <true/>
    <key>TrackCrashes</key>
    <true/>
    <key>BatchSize</key>
    <integer>50</integer>
    <key>FlushInterval</key>
    <real>30.0</real>
</dict>
</plist>
// Load from plist
if let path = Bundle.main.path(forResource: "Entrolytics", ofType: "plist"),
   let config = Config.fromPlist(at: path) {
    Entrolytics.initialize(config: config)
}

API Reference

Core Class

public class Entrolytics {

    // Initialization
    public static func initialize(config: Config)
    public static func isInitialized() -> Bool

    // Event tracking
    public static func track(_ event: String, properties: [String: Any]? = nil)
    public static func trackAsync(_ event: String, properties: [String: Any]? = nil)

    // Screen tracking
    public static func screen(name: String, properties: [String: Any]? = nil)
    public static func screenAsync(name: String, properties: [String: Any]? = nil)

    // User identification
    public static func identify(userId: String, traits: [String: Any]? = nil)
    public static func identifyAsync(userId: String, traits: [String: Any]? = nil)

    // User properties
    public static func setUserProperties(_ properties: [String: Any])
    public static func setUserPropertiesAsync(_ properties: [String: Any])

    // Batch operations
    public static func trackBatch(_ events: [Event])
    public static func trackBatchAsync(_ events: [Event])

    // Control
    public static func flush()
    public static func flushAsync(completion: @escaping (Error?) -> Void)
    public static func reset()
    public static func setEnabled(_ enabled: Bool)
    public static func isEnabled() -> Bool

    // Configuration
    public static func getConfig() -> Config
    public static func updateConfig(_ config: Config)

    // User information
    public static func getUserId() -> String?
    public static func getSessionId() -> String
}

Data Structures

public struct Config {
    public let websiteId: String
    public let apiKey: String?
    public let host: String
    public let debug: Bool
    public let autoTrackScreens: Bool
    public let trackCrashes: Bool
    public let trackAppLifecycle: Bool
    public let batchSize: Int
    public let flushInterval: TimeInterval
    public let timeout: TimeInterval
    public let maxRetries: Int
    public let batteryOptimization: Bool
    public let offlineSupport: Bool
}

public struct Event {
    public let event: String
    public let properties: [String: Any]?
    public let timestamp: Date
    public let userId: String?
    public let sessionId: String
}

public struct Builder {
    public func websiteId(_ websiteId: String) -> Builder
    public func apiKey(_ apiKey: String?) -> Builder
    public func host(_ host: String) -> Builder
    public func debug(_ debug: Bool) -> Builder
    public func autoTrackScreens(_ autoTrackScreens: Bool) -> Builder
    public func trackCrashes(_ trackCrashes: Bool) -> Builder
    public func trackAppLifecycle(_ trackAppLifecycle: Bool) -> Builder
    public func batchSize(_ batchSize: Int) -> Builder
    public func flushInterval(_ flushInterval: TimeInterval) -> Builder
    public func timeout(_ timeout: TimeInterval) -> Builder
    public func maxRetries(_ maxRetries: Int) -> Builder
    public func batteryOptimization(_ batteryOptimization: Bool) -> Builder
    public func offlineSupport(_ offlineSupport: Bool) -> Builder
    public func build() -> Config
}

Advanced Usage

View Controller Lifecycle Integration

// BaseViewController.swift
import UIKit
import Entrolytics

class BaseViewController: UIViewController {

    private var viewStartTime: Date?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        viewStartTime = Date()

        // Track screen automatically
        Entrolytics.screen(
            name: getScreenName(),
            properties: getScreenProperties()
        )
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        // Track screen exit
        if let startTime = viewStartTime {
            let duration = Date().timeIntervalSince(startTime)
            Entrolytics.track("screen_exit", properties: [
                "screen_name": getScreenName(),
                "duration_seconds": duration
            ])
        }
    }

    // Override in subclasses
    func getScreenName() -> String {
        return String(describing: type(of: self))
    }

    func getScreenProperties() -> [String: Any] {
        return [:]
    }
}

SwiftUI Integration

// AnalyticsViewModifier.swift
import SwiftUI
import Entrolytics

struct AnalyticsViewModifier: ViewModifier {
    let screenName: String
    let properties: [String: Any]?

    func body(content: Content) -> some View {
        content
            .onAppear {
                Entrolytics.screen(name: screenName, properties: properties)
            }
    }
}

extension View {
    func trackScreen(name: String, properties: [String: Any]? = nil) -> some View {
        modifier(AnalyticsViewModifier(screenName: name, properties: properties))
    }
}

// Usage in SwiftUI
struct ContentView: View {
    var body: some View {
        VStack {
            Text("Welcome to Entrolytics")
            Button("Sign Up") {
                Entrolytics.track("signup_button_tapped", properties: [
                    "source": "main_screen"
                ])
            }
        }
        .trackScreen(name: "MainScreen", properties: [
            "user_authenticated": false
        ])
    }
}

User Authentication Integration

// AuthenticationManager.swift
import Foundation
import Entrolytics

class AuthenticationManager: ObservableObject {

    func login(email: String, password: String) async throws -> User {
        // Track login attempt
        Entrolytics.track("login_attempt", properties: [
            "method": "email",
            "email_hash": email.hashValue.description
        ])

        do {
            let user = try await apiService.login(email: email, password: password)

            // Identify user in analytics
            Entrolytics.identify(userId: user.id, traits: [
                "email": user.email,
                "name": user.name,
                "role": user.role,
                "plan": user.subscriptionPlan
            ])

            // Track successful login
            Entrolytics.track("login_success", properties: [
                "user_id": user.id,
                "method": "email"
            ])

            // Update user properties
            Entrolytics.setUserProperties([
                "login_count": user.loginCount + 1,
                "last_login": Date().timeIntervalSince1970,
                "is_premium": user.isPremium
            ])

            return user

        } catch {
            // Track login failure
            Entrolytics.track("login_failed", properties: [
                "method": "email",
                "error_type": String(describing: type(of: error)),
                "error_message": error.localizedDescription
            ])

            throw error
        }
    }

    func logout() {
        // Track logout
        Entrolytics.track("logout", properties: [
            "method": "explicit"
        ])

        // Reset user session
        Entrolytics.reset()

        // Clear local data
        clearUserData()
    }
}

E-commerce Tracking

// ProductViewController.swift
import UIKit
import Entrolytics

class ProductViewController: UIViewController {

    private var product: Product!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Track product view
        Entrolytics.track("product_view", properties: [
            "product_id": product.id,
            "product_name": product.name,
            "category": product.category,
            "price": product.price,
            "currency": product.currency,
            "in_stock": product.inStock,
            "source": getNavigationSource()
        ])

        setupUI()
    }

    private func setupUI() {
        // Track add to cart
        addToCartButton.addAction(UIAction { [weak self] _ in
            guard let self = self else { return }

            Entrolytics.track("add_to_cart", properties: [
                "product_id": self.product.id,
                "product_name": self.product.name,
                "price": self.product.price,
                "quantity": self.quantityPicker.value,
                "currency": self.product.currency
            ])

            self.addToCart(product: self.product, quantity: self.quantityPicker.value)
        }, for: .touchUpInside)

        // Track wishlist
        addToWishlistButton.addAction(UIAction { [weak self] _ in
            guard let self = self else { return }

            Entrolytics.track("add_to_wishlist", properties: [
                "product_id": self.product.id,
                "product_name": self.product.name,
                "category": self.product.category
            ])

            self.addToWishlist(product: self.product)
        }, for: .touchUpInside)

        // Track share
        shareButton.addAction(UIAction { [weak self] _ in
            guard let self = self else { return }

            Entrolytics.track("product_share", properties: [
                "product_id": self.product.id,
                "product_name": self.product.name,
                "method": "native_share"
            ])

            self.shareProduct(self.product)
        }, for: .touchUpInside)
    }
}

// CheckoutViewController.swift
class CheckoutViewController: UIViewController {

    private func trackPurchase(order: Order) {
        Entrolytics.track("purchase", properties: [
            "order_id": order.id,
            "total": order.total,
            "currency": order.currency,
            "payment_method": order.paymentMethod,
            "items": order.items.map { item in
                [
                    "product_id": item.productId,
                    "product_name": item.productName,
                    "quantity": item.quantity,
                    "price": item.price
                ]
            }
        ])

        // Update user properties
        Entrolytics.setUserProperties([
            "total_orders": getCurrentUser().orderCount + 1,
            "total_spent": getCurrentUser().totalSpent + order.total,
            "last_purchase": Date().timeIntervalSince1970
        ])
    }
}

Performance Monitoring

// PerformanceTracker.swift
import Foundation
import Entrolytics

class PerformanceTracker {

    func trackOperation<T>(
        operationName: String,
        properties: [String: Any] = [:],
        operation: () throws -> T
    ) rethrows -> T {
        let startTime = Date()

        do {
            let result = try operation()
            let duration = Date().timeIntervalSince(startTime)

            Entrolytics.track("performance_operation", properties: [
                "operation": operationName,
                "duration_seconds": duration,
                "success": true
            ].merging(properties) { _, new in new })

            return result
        } catch {
            let duration = Date().timeIntervalSince(startTime)

            Entrolytics.track("performance_operation", properties: [
                "operation": operationName,
                "duration_seconds": duration,
                "success": false,
                "error_type": String(describing: type(of: error))
            ].merging(properties) { _, new in new })

            throw error
        }
    }

    func trackOperationAsync<T>(
        operationName: String,
        properties: [String: Any] = [:],
        operation: () async throws -> T
    ) async rethrows -> T {
        let startTime = Date()

        do {
            let result = try await operation()
            let duration = Date().timeIntervalSince(startTime)

            await MainActor.run {
                Entrolytics.track("performance_operation", properties: [
                    "operation": operationName,
                    "duration_seconds": duration,
                    "success": true
                ].merging(properties) { _, new in new })
            }

            return result
        } catch {
            let duration = Date().timeIntervalSince(startTime)

            await MainActor.run {
                Entrolytics.track("performance_operation", properties: [
                    "operation": operationName,
                    "duration_seconds": duration,
                    "success": false,
                    "error_type": String(describing: type(of: error))
                ].merging(properties) { _, new in new })
            }

            throw error
        }
    }
}

// Usage
class DataManager {
    private let performanceTracker = PerformanceTracker()

    func loadUserData(userId: String) async throws -> User {
        return try await performanceTracker.trackOperationAsync(
            operationName: "load_user_data",
            properties: ["user_id": userId]
        ) {
            try await apiService.getUser(id: userId)
        }
    }
}

Error and Crash Tracking

// ErrorTracker.swift
import Foundation
import Entrolytics
import UIKit

class ErrorTracker {

    static func setup() {
        // Set up crash handler
        NSSetUncaughtExceptionHandler { exception in
            ErrorTracker.trackCrash(exception)
        }

        // Set up signal handlers
        SignalHandler.setup()
    }

    static func trackCrash(_ exception: NSException) {
        Entrolytics.track("app_crash", properties: [
            "error_type": String(describing: type(of: exception)),
            "error_reason": exception.reason ?? "Unknown",
            "stack_trace": exception.callStackSymbols.joined(separator: "\n"),
            "device_info": getDeviceInfo(),
            "app_version": getAppVersion(),
            "build_type": isDebug ? "debug" : "release"
        ])
    }

    static func trackHandledError(_ error: Error, context: String = "") {
        Entrolytics.track("handled_error", properties: [
            "error_type": String(describing: type(of: error)),
            "error_message": error.localizedDescription,
            "context": context,
            "stack_trace": Thread.callStackSymbols.joined(separator: "\n")
        ])
    }

    private static func getDeviceInfo() -> [String: String] {
        let device = UIDevice.current
        return [
            "model": device.model,
            "system_name": device.systemName,
            "system_version": device.systemVersion,
            "name": device.name
        ]
    }

    private static func getAppVersion() -> String {
        let bundle = Bundle.main
        let version = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
        let build = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
        return "\(version) (\(build))"
    }
}

// SignalHandler.swift
import Foundation

class SignalHandler {

    static func setup() {
        signal(SIGABRT, crashHandler)
        signal(SIGILL, crashHandler)
        signal(SIGSEGV, crashHandler)
        signal(SIGFPE, crashHandler)
        signal(SIGBUS, crashHandler)
        signal(SIGPIPE, crashHandler)
    }

    private static func crashHandler(signal: Int32) {
        ErrorTracker.trackCrash(NSException(
            name: NSExceptionName(rawValue: "Signal \(signal)"),
            reason: "App crashed with signal \(signal)",
            userInfo: nil
        ))

        // Call previous handler
        let previousHandler = signal(signal, SIG_DFL)
        if previousHandler != SIG_DFL {
            previousHandler(signal)
        }
    }
}

Testing

Unit Tests

// EntrolyticsTests.swift
import XCTest
@testable import Entrolytics

class EntrolyticsTests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Reset SDK before each test
        Entrolytics.reset()
    }

    func testTrackEvent() {
        // Given
        let eventName = "test_event"
        let properties = ["property": "value"]

        // When
        Entrolytics.track(eventName, properties: properties)

        // Then
        // In real tests, you would verify the event was queued
        XCTAssertTrue(Entrolytics.isInitialized())
    }

    func testIdentifyUser() {
        // Given
        let userId = "user-123"
        let traits = ["email": "user@example.com"]

        // When
        Entrolytics.identify(userId: userId, traits: traits)

        // Then
        XCTAssertEqual(Entrolytics.getUserId(), userId)
    }

    func testScreenTracking() {
        // Given
        let screenName = "TestScreen"
        let properties = ["test": true]

        // When
        Entrolytics.screen(name: screenName, properties: properties)

        // Then
        // Verify screen was tracked
        XCTAssertTrue(true) // Placeholder assertion
    }
}

UI Tests

// EntrolyticsUITests.swift
import XCTest

class EntrolyticsUITests: XCTestCase {

    var app: XCUIApplication!

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()
    }

    func testButtonTracking() {
        // Given
        let button = app.buttons["Sign Up"]

        // When
        button.tap()

        // Then
        // In real UI tests, you might verify analytics were called
        XCTAssertTrue(button.exists)
    }

    func testScreenNavigation() {
        // Given
        let profileButton = app.buttons["Profile"]

        // When
        profileButton.tap()

        // Then
        let profileTitle = app.staticTexts["Profile"]
        XCTAssertTrue(profileTitle.waitForExistence(timeout: 5))
    }
}

Performance Optimization

Battery Optimization

// BatteryAwareTracker.swift
import UIKit
import Entrolytics

class BatteryAwareTracker {

    func trackWithBatteryAwareness(
        event: String,
        properties: [String: Any] = [:]
    ) {
        let batteryLevel = UIDevice.current.batteryLevel

        if batteryLevel < 0.2 {
            // Low battery mode - reduce tracking frequency
            if Double.random(in: 0...1) < 0.1 { // Only track 10% of events
                Entrolytics.track(event, properties: properties.merging([
                    "battery_saver": true,
                    "battery_level": batteryLevel
                ]) { _, new in new })
            }
        } else {
            // Normal tracking
            Entrolytics.track(event, properties: properties.merging([
                "battery_level": batteryLevel
            ]) { _, new in new })
        }
    }
}

Memory Management

// MemoryEfficientTracker.swift
import Foundation
import Entrolytics

class MemoryEfficientTracker {

    private var eventQueue: [Event] = []
    private let maxQueueSize = 100
    private let queue = DispatchQueue(label: "analytics.queue", qos: .utility)

    func addEvent(_ event: Event) {
        queue.async { [weak self] in
            guard let self = self else { return }

            if self.eventQueue.count >= self.maxQueueSize {
                // Remove oldest events to prevent memory issues
                self.eventQueue.removeFirst()
            }
            self.eventQueue.append(event)

            // Flush if queue is getting full
            if self.eventQueue.count >= Int(Double(self.maxQueueSize) * 0.8) {
                self.flushEvents()
            }
        }
    }

    private func flushEvents() {
        guard !eventQueue.isEmpty else { return }

        let events = eventQueue
        eventQueue.removeAll()

        Entrolytics.trackBatch(events)
    }

    func flush() {
        queue.async { [weak self] in
            self?.flushEvents()
        }
    }
}

Troubleshooting

Best Practices

Migration Guide

From Other Analytics SDKs

// Before - Other analytics
import OtherAnalytics

OtherAnalytics.track("event_name", properties: ["property": "value"])

// After - Entrolytics
import Entrolytics

Entrolytics.track("event_name", properties: ["property": "value"])

From Version 1.x

// Before - v1.x
Entrolytics.shared().track("event", properties: properties)

// After - v2.x
Entrolytics.track("event", properties: properties)

iOS SDK for Entrolytics - First-party growth analytics for the edge