Post

Reliable Heuristics to Detect iOS Beta Builds

Detect iOS beta builds using production-safe heuristics on ProcessInfo with regex-backed build parsing.

Reliable Heuristics to Detect iOS Beta Builds

Detect whether the device runs an iOS beta build by parsing ProcessInfo.processInfo.operatingSystemVersionString, since no public API exposes an isBeta flag. UIDevice.systemVersion hides beta state; rely on build metadata in the OS version string.

Requirements

  • iOS 14+ (ProcessInfo API stable since early iOS; tested on iOS 17–18 seeds; forward-compatible)
  • Swift 5.9+

References: Apple docs for ProcessInfo and OperatingSystemVersion

  • https://developer.apple.com/documentation/foundation/processinfo
  • https://developer.apple.com/documentation/foundation/operatingsystemversion

Production Heuristic for Beta Detection

  • Match “beta”, “seed”, “rc”/”release candidate” markers in the OS version string.
  • Detect beta-like build identifiers with trailing letter suffixes (e.g., 22A5282m). Final releases typically omit certain suffix patterns.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import Foundation
import UIKit

public enum BetaStatus: Equatable {
    case beta
    case releaseCandidate
    case seed
    case stable
}

public struct IOSBetaDetector {
    // Human-readable full version string, e.g. "Version 26.0 (Build 24A123b)"
    public static var detailedVersionInfo: String {
        ProcessInfo.processInfo.operatingSystemVersionString
    }

    // Simple numeric version string, e.g. "26.0"
    public static var systemVersion: String {
        UIDevice.current.systemVersion
    }

    // Returns a coarse classification using documented strings and build heuristics
    public static func betaStatus() -> BetaStatus {
        let s = detailedVersionInfo.lowercased()

        if s.contains("release candidate") || s.contains(" rc ") || s.hasSuffix(" rc") || s.contains("(rc)") {
            return .releaseCandidate
        }
        if s.contains("beta") {
            return .beta
        }
        if s.contains("seed") {
            return .seed
        }
        if hasBetaBuildSuffix(in: s) {
            return .beta
        }
        return .stable
    }

    // True if OS appears to be a beta, seed, or RC build
    public static var isPotentiallyBeta: Bool {
        switch betaStatus() {
        case .beta, .seed, .releaseCandidate:
            return true
        case .stable:
            return false
        }
    }

    // Use OS availability checks for feature gating; example for iOS 26+
    @inline(__always)
    public static func isAtLeastIOS26() -> Bool {
        if #available(iOS 26, *) { return true }
        return false
    }

    // Detects build tokens like "Build 24A123b)" where trailing [a-z] indicates a pre-release
    // Pattern notes:
    // - Apple build format: <major><letter><number>[letter], e.g., 24A123 (final) vs 24A123b (beta)
    // - We accept trailing lowercase letter before the closing paren as beta-like
    private static func hasBetaBuildSuffix(in versionStringLowercased: String) -> Bool {
        // Use a case-insensitive regex against the original string to preserve capitalization in "Build"
        let original = detailedVersionInfo
        let pattern = #"Build\s+[0-9A-Z]+\d+[a-z]\)"#
        // Examples matched: "Build 24A123b)", "Build 24C45m)"
        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return false }
        let range = NSRange(location: 0, length: original.utf16.count)
        return regex.firstMatch(in: original, options: [], range: range) != nil
    }
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation

// Gate verbose logging and feature flags on beta OS only
if IOSBetaDetector.isPotentiallyBeta {
    // Enable additional diagnostics for beta system builds
    setenv("MYAPP_VERBOSE_LOGGING", "1", 1)
}

// Separate RC from beta if needed
switch IOSBetaDetector.betaStatus() {
case .beta:
    enableExperimentalFlags()
case .releaseCandidate:
    enableStabilityLogging()
case .seed:
    enableQAOverrides()
case .stable:
    break
}

// Guard iOS 26+ features
if IOSBetaDetector.isAtLeastIOS26() {
    // Call iOS 26-only APIs here
}

Notes for Production

  • Heuristic accuracy depends on Apple’s version string and build token conventions; monitor seeds each cycle.
  • Cache the computed status at launch to avoid repeated regex work in hot paths.
  • Prefer build-time configuration for app behavior; use OS beta detection only for diagnostics and logging
This post is licensed under CC BY 4.0 by the author.