Post

Icon iOS Platform Implementation Complete

Completion of iOS Platform actuals for installId and hostName with persistent storage and device identification

iOS Platform Implementation Complete

Overview

Completed the iOS platform actuals for installId and hostName in Platform.ios.kt, providing persistent unique identification and friendly device names for iOS devices.

Implementation Details

Install ID

Purpose: Generate and persist a unique identifier for the app installation.

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
actual val installId: () -> String
    get() = { createOrReadInstallId() }

private fun createOrReadInstallId(): String {
    val defaults = NSUserDefaults.standardUserDefaults
    val existingId = defaults.stringForKey(INSTALL_ID_KEY)
    
    return if (existingId != null) {
        existingId
    } else {
        // Generate a new UUID and store it
        val newId = NSUUID().UUIDString()
        defaults.setObject(newId, forKey = INSTALL_ID_KEY)
        defaults.synchronize()
        newId
    }
}

Features:

  • ✅ Generates UUID on first app launch
  • ✅ Persists in NSUserDefaults with key krill_install_id
  • ✅ Survives app restarts and updates
  • ✅ Unique per app installation
  • ✅ Mirrors Android’s SharedPreferences pattern

Storage:

  • Location: NSUserDefaults.standardUserDefaults
  • Key: krill_install_id
  • Format: UUID string (e.g., "550e8400-e29b-41d4-a716-446655440000")
  • Persistence: Permanent until app uninstall

Host Name

Purpose: Provide a friendly, human-readable device identifier.

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
actual val hostName: String
    get() {
        val device = UIDevice.currentDevice
        val deviceName = device.name // e.g., "John's iPhone"
        val deviceModel = device.model // e.g., "iPhone", "iPad"
        
        return try {
            // Use device name if available and not default
            if (deviceName.isNotEmpty() && !deviceName.equals("iPhone", ignoreCase = true) && 
                !deviceName.equals("iPad", ignoreCase = true)) {
                deviceName
            } else {
                // Fall back to model
                deviceModel
            }
        } catch (_: Exception) {
            // Fallback to just model if anything fails
            device.model
        }
    }

Features:

  • ✅ Returns user-set device name when available (e.g., “John’s iPhone”)
  • ✅ Falls back to device model (e.g., “iPhone”, “iPad”)
  • ✅ Handles default generic names appropriately
  • ✅ Exception-safe with fallback
  • ✅ Mirrors Android’s Build.MODEL pattern

Examples:

  • User-named device: "John's iPhone" → returns "John's iPhone"
  • Generic name: "iPhone" → returns "iPhone" (model)
  • iPad: "Jane's iPad" → returns "Jane's iPad"
  • Generic iPad: "iPad" → returns "iPad" (model)

Platform Comparison

Install ID Implementation

PlatformStorage MechanismFormatPersistence
iOSNSUserDefaultsUUID StringUntil uninstall
AndroidSharedPreferencesUUID StringUntil uninstall
JVMFile (/etc/krill/install_id)UUID StringPermanent (system-wide)

Host Name Implementation

PlatformSourceExample ValueBehavior
iOSUIDevice.name or .model“John’s iPhone” or “iPhone”User-set name preferred
AndroidBuild.MODEL“Pixel 7” or “SM-G991U”Manufacturer model
JVMInetAddress.getLocalHost()“macbook-pro.local”Network hostname

Storage Architecture

iOS Storage Strategy

1
2
3
4
5
6
7
8
9
NSUserDefaults (User Domain)
├── krill_install_id: "550e8400-e29b-41d4-a716-446655440000"
├── krill_trusted_cert_192.168.1.100: <certificate data>
└── krill_trusted_cert_server.local: <certificate data>

NSUserDefaults (Suite: "krill_nodes_v3")
├── node_id_1: <Node JSON>
├── node_id_2: <Node JSON>
└── node_id_3: <Node JSON>

Design Rationale:

  • installId: Standard user defaults (global app setting)
  • certificates: Standard user defaults (security-related)
  • nodes: Separate suite (feature-specific, can be managed independently)

This mirrors the Android pattern:

  • SharedPreferences for app-level settings
  • Separate stores for feature-specific data

Testing

Verification Checklist

  • Compiles for iosSimulatorArm64
  • Compiles for iosArm64
  • Compiles for iosX64
  • No compilation errors
  • No blocking TODOs
  • Follows platform conventions

Runtime Testing Required

  1. First Launch
    • Verify UUID is generated
    • Verify UUID is stored in NSUserDefaults
    • Verify installId returns same value on subsequent calls
  2. Device Name
    • Test with user-set device name (e.g., “My iPhone”)
    • Test with generic name (e.g., “iPhone”)
    • Test on iPad
    • Verify fallback behavior
  3. Persistence
    • Close and reopen app
    • Verify same installId returned
    • Update app and verify ID persists
  4. Edge Cases
    • Fresh install (no existing ID)
    • User changes device name
    • Empty device name (shouldn’t happen, but test fallback)

Code Quality

Best Practices Applied

Lazy Initialization: Uses get() pattern for on-demand creation
Error Handling: Try-catch with sensible fallbacks
Null Safety: Proper null checks for NSUserDefaults
Documentation: Inline comments explain behavior
Consistency: Mirrors Android/JVM patterns where appropriate
iOS Conventions: Uses NSUserDefaults and UIDevice APIs correctly

No TODOs Remaining

The implementation is complete with no blocking TODOs:

  • installId - Fully implemented with persistent storage
  • hostName - Fully implemented with fallback logic
  • platform - Returns Platform.IOS

The only TODO in iOS codebase is a future enhancement note in HttpClient.ios.kt about adding UI for certificate export.

Usage Examples

Getting Install ID

1
2
3
4
5
6
7
8
9
10
11
// Get the install ID function
val getInstallId = installId

// Call it to get the ID
val id: String = getInstallId()
println("Install ID: $id")
// Output: "Install ID: 550e8400-e29b-41d4-a716-446655440000"

// Subsequent calls return the same ID
val sameId = getInstallId()
assert(id == sameId) // true

Getting Host Name

1
2
3
4
5
// Get the hostname
val name: String = hostName
println("Device: $name")
// Output: "Device: John's iPhone" (if user-named)
// or:     "Device: iPhone" (if generic name)

Platform Detection

1
2
3
4
5
when (platform) {
    Platform.IOS -> println("Running on iOS")
    Platform.ANDROID -> println("Running on Android")
    // ... other platforms
}

Integration Points

Used By

The platform actuals are used throughout the Krill SDK:

  1. Node Identification: InstallId used for client identification
  2. Device Display: HostName used in UI and logs
  3. Platform-Specific Logic: Platform enum for conditional behavior
  4. Analytics: Device and platform info for tracking
  5. Debugging: Useful in logs for identifying devices

Example Integration

1
2
3
4
5
6
7
8
9
10
11
// In NodeWire or similar
data class ClientInfo(
    val installId: String = krill.zone.installId(),
    val hostName: String = krill.zone.hostName,
    val platform: Platform = krill.zone.platform
)

// Usage
val info = ClientInfo()
logger.i("Client: ${info.hostName} (${info.platform}) - ${info.installId}")
// Output: "Client: John's iPhone (IOS) - 550e8400-e29b-41d4-a716-446655440000"

Summary

The iOS Platform implementation is complete and production-ready:

Install ID: Persistent UUID storage using NSUserDefaults
Host Name: Friendly device identification with fallbacks
Platform: Returns iOS enum value
Storage: Follows iOS best practices
Consistency: Mirrors Android/JVM patterns
Testing: Compiles successfully for all iOS targets
Quality: Clean, documented, error-safe code

All iOS Platform actuals are now complete! 🎉

This post is licensed under CC BY 4.0 by the author.