OS X
iOS
runtime
OS version
development

How do I determine the OS version at runtime in OS X or iOS without using Gestalt?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Gestalt was common in older Apple code, but it has been deprecated for years and should not be part of a modern codebase. The replacement depends on what you are actually trying to do: use availability checks when deciding whether an API may be called, and use ProcessInfo when you need actual runtime version data.

Use Availability Checks for Feature Decisions

If the real question is "can I use this API on the current system," an availability check is the best answer. It is more accurate than manual version parsing because the compiler understands it.

swift
1import Foundation
2
3func configureFeature() {
4    if #available(iOS 17.0, macOS 14.0, *) {
5        print("Use modern implementation")
6    } else {
7        print("Use fallback implementation")
8    }
9}
10
11configureFeature()

This keeps compatibility logic close to the API that needs it. It also prevents a common bug where code checks a version number manually and still calls an unavailable API by accident.

Read the Runtime Version with ProcessInfo

When you need actual version metadata for logging, analytics, or general compatibility rules, ProcessInfo.processInfo.operatingSystemVersion is the usual tool.

swift
1import Foundation
2
3let version = ProcessInfo.processInfo.operatingSystemVersion
4
5print(version.majorVersion)
6print(version.minorVersion)
7print(version.patchVersion)
8print(ProcessInfo.processInfo.operatingSystemVersionString)

The numeric fields are what you should compare in code. The string form is useful for logs and support output, but it is not a good basis for branching logic.

Centralize Comparisons in a Small Helper

If the application performs multiple version comparisons, put that logic in one place rather than scattering it across the project.

swift
1import Foundation
2
3struct OSVersion: Comparable {
4    let major: Int
5    let minor: Int
6    let patch: Int
7
8    static func current() -> OSVersion {
9        let current = ProcessInfo.processInfo.operatingSystemVersion
10        return OSVersion(
11            major: current.majorVersion,
12            minor: current.minorVersion,
13            patch: current.patchVersion
14        )
15    }
16
17    static func < (lhs: OSVersion, rhs: OSVersion) -> Bool {
18        if lhs.major != rhs.major { return lhs.major < rhs.major }
19        if lhs.minor != rhs.minor { return lhs.minor < rhs.minor }
20        return lhs.patch < rhs.patch
21    }
22}
23
24print(OSVersion.current() >= OSVersion(major: 16, minor: 0, patch: 0))

This helper keeps the comparison rules consistent and easy to unit test.

Handle iOS and macOS Separately When Needed

Shared codebases often support more than one Apple platform. Make the platform branch explicit so version checks are unambiguous.

swift
1#if os(iOS)
2if #available(iOS 16.0, *) {
3    print("iOS path")
4}
5#elseif os(macOS)
6if #available(macOS 13.0, *) {
7    print("macOS path")
8}
9#endif

This prevents accidental assumptions that iOS and macOS version numbers map directly onto one another.

Objective-C Uses the Same Modern Pattern

Older Objective-C modules do not need Gestalt either. NSProcessInfo and availability checks solve the same problem.

objective-c
1#import <Foundation/Foundation.h>
2
3NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
4NSLog(@"%ld.%ld.%ld",
5      (long)version.majorVersion,
6      (long)version.minorVersion,
7      (long)version.patchVersion);
8
9if (@available(iOS 16.0, macOS 13.0, *)) {
10    NSLog(@"Modern path");
11} else {
12    NSLog(@"Fallback path");
13}

That makes mixed Swift and Objective-C codebases much easier to reason about because both languages use the same platform model.

Migrate Old Gestalt Code Thoughtfully

If you still have legacy Gestalt calls, migrate them by intent. Replace API-availability checks with if #available, and replace metadata lookups with ProcessInfo. This is also a good time to add tests for any custom comparison helper.

swift
1import XCTest
2
3final class OSVersionTests: XCTestCase {
4    func testComparison() {
5        XCTAssertLessThan(
6            OSVersion(major: 15, minor: 6, patch: 0),
7            OSVersion(major: 16, minor: 0, patch: 0)
8        )
9    }
10}

Testing comparison helpers matters because boundary bugs often appear only when a major or minor version changes.

Common Pitfalls

The most common mistake is using raw version checks when an availability check would express the real intent more directly. If the question is about API support, use if #available first.

Another frequent problem is comparing the human-readable version string rather than numeric components. String formatting is for display, not for reliable semantic comparison.

Finally, teams often validate only the newest path. Compatibility code is only trustworthy if the fallback branch is exercised on the oldest supported runtime as well.

Summary

  • Do not use deprecated Gestalt in modern iOS or macOS projects.
  • Use if #available for API gating and ProcessInfo for runtime version metadata.
  • Centralize custom version comparisons in a helper when the logic appears in multiple places.
  • Keep platform-specific branches explicit in shared Apple-platform code.
  • Test both the latest and fallback paths so compatibility logic remains correct.

Course illustration
Course illustration

All Rights Reserved.