Avatar ·

Как определить модель текущего iPhone или устройства iOS в Swift?

Всем привет! Я разрабатываю приложение для iOS на Swift и столкнулся с такой задачей: мне нужно программно определить, на каком именно устройстве запущено приложение. Например, я хочу знать, работает ли приложение на iPhone 14 Pro Max, iPhone SE (3-го поколения) или на iPad Air. Проблема в том, что я не могу найти надёжный способ получить строку с названием модели. Я пробовал использовать `UIDevice.current.model`, но он возвращает только общий тип — "iPhone", "iPad" или "iPod touch". Мне же нужна точная модель, например "iPhone13,4" или "iPhone15,3". Вот что я пробовал: ```swift let device = UIDevice.current print(device.model) // Выводит просто "iPhone" print(device.localizedModel) // Тоже "iPhone" ``` Я слышал, что можно использовать `sysctl` или читать какие-то системные файлы, но не понимаю, как это правильно реализовать на Swift. Также меня интересует, как быть с симулятором — там ведь возвращаются совсем другие идентификаторы. Подскажите, пожалуйста, как правильно и надёжно получить модель устройства? Нужно ли подключать какие-то дополнительные библиотеки или можно обойтись стандартными средствами iOS SDK? Заранее спасибо! <<>>

Решение: получаем точную модель устройства через sysctl

Стандартный UIDevice.current.model действительно возвращает только общий тип. Чтобы получить точный идентификатор модели (например, "iPhone15,3" для iPhone 15 Pro Max), нужно использовать системный вызов sysctl. Этот метод работает на всех реальных устройствах и не требует дополнительных зависимостей.

Шаг 1: Получаем идентификатор модели

Создайте расширение для UIDevice:

import UIKit

extension UIDevice {
    var modelIdentifier: String {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.compactMap { element -> String? in
            guard let value = element.value as? Int8, value != 0 else { return nil }
            return String(UnicodeScalar(UInt8(value)))
        }.joined()
        return identifier
    }
    
    var modelName: String {
        let identifier = modelIdentifier
        return UIDevice.modelName(for: identifier)
    }
    
    static func modelName(for identifier: String) -> String {
        let modelMap: [String: String] = [
            "iPhone1,1": "iPhone 2G",
            "iPhone1,2": "iPhone 3G",
            "iPhone2,1": "iPhone 3GS",
            "iPhone3,1": "iPhone 4 (GSM)",
            "iPhone3,3": "iPhone 4 (CDMA)",
            "iPhone4,1": "iPhone 4S",
            "iPhone5,1": "iPhone 5 (GSM)",
            "iPhone5,2": "iPhone 5 (CDMA)",
            "iPhone5,3": "iPhone 5c",
            "iPhone5,4": "iPhone 5c",
            "iPhone6,1": "iPhone 5s",
            "iPhone6,2": "iPhone 5s",
            "iPhone7,2": "iPhone 6",
            "iPhone7,1": "iPhone 6 Plus",
            "iPhone8,1": "iPhone 6s",
            "iPhone8,2": "iPhone 6s Plus",
            "iPhone8,4": "iPhone SE (1st gen)",
            "iPhone9,1": "iPhone 7",
            "iPhone9,3": "iPhone 7",
            "iPhone9,2": "iPhone 7 Plus",
            "iPhone9,4": "iPhone 7 Plus",
            "iPhone10,1": "iPhone 8",
            "iPhone10,4": "iPhone 8",
            "iPhone10,2": "iPhone 8 Plus",
            "iPhone10,5": "iPhone 8 Plus",
            "iPhone10,3": "iPhone X",
            "iPhone10,6": "iPhone X",
            "iPhone11,2": "iPhone XS",
            "iPhone11,4": "iPhone XS Max",
            "iPhone11,6": "iPhone XS Max",
            "iPhone11,8": "iPhone XR",
            "iPhone12,1": "iPhone 11",
            "iPhone12,3": "iPhone 11 Pro",
            "iPhone12,5": "iPhone 11 Pro Max",
            "iPhone12,8": "iPhone SE (2nd gen)",
            "iPhone13,1": "iPhone 12 mini",
            "iPhone13,2": "iPhone 12",
            "iPhone13,3": "iPhone 12 Pro",
            "iPhone13,4": "iPhone 12 Pro Max",
            "iPhone14,2": "iPhone 13 Pro",
            "iPhone14,3": "iPhone 13 Pro Max",
            "iPhone14,4": "iPhone 13 mini",
            "iPhone14,5": "iPhone 13",
            "iPhone14,6": "iPhone SE (3rd gen)",
            "iPhone14,7": "iPhone 14",
            "iPhone14,8": "iPhone 14 Plus",
            "iPhone15,1": "iPhone 14 Pro",
            "iPhone15,2": "iPhone 14 Pro Max",
            "iPhone15,3": "iPhone 15 Pro Max",
            "iPhone15,4": "iPhone 15 Pro",
            "iPhone15,5": "iPhone 15",
            "iPhone16,1": "iPhone 16 Pro",
            "iPhone16,2": "iPhone 16 Pro Max",
            "iPhone16,3": "iPhone 16",
            // iPad и iPod можно добавить по аналогии
        ]
        return modelMap[identifier] ?? identifier
    }
}

Шаг 2: Использование

Теперь вы можете получить информацию так:

let device = UIDevice.current
print("Идентификатор: \(device.modelIdentifier)") // iPhone15,3
print("Название модели: \(device.modelName)")      // iPhone 15 Pro Max

Обработка симулятора

На симуляторе sysctl вернёт "x86_64" (Intel) или "arm64" (Apple Silicon). Чтобы получить модель, которую симулятор эмулирует, используйте:

extension UIDevice {
    var isSimulator: Bool {
        return modelIdentifier == "x86_64" || modelIdentifier == "arm64"
    }
    
    var simulatorModelIdentifier: String? {
        return ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"]
    }
}

Важные замечания

  • Обновляйте словарь modelMap — Apple регулярно выпускает новые устройства, и их идентификаторы нужно добавлять вручную. Следите за обновлениями на theiphonewiki.com.
  • Для iPad и iPod — расширьте словарь аналогично, используя префиксы "iPad" и "iPod".
  • Не используйте для критической логики — определение модели может сломаться при появлении нового устройства. Для адаптации UI лучше используйте UITraitCollection или UIScreen.
  • Конфиденциальность — не отправляйте идентификатор модели на сервер без явного согласия пользователя.

Альтернатива: фреймворк DeviceKit

Если не хотите поддерживать словарь вручную, используйте CocoaPod/SPM пакет DeviceKit. Он автоматически обновляется и предоставляет удобный API:

import DeviceKit

let device = Device.current
print(device.description) // "iPhone 15 Pro Max"
print(device.isOneOf([.iPhone14Pro, .iPhone15ProMax])) // Проверка конкретных моделей
<<>>

Альтернативный подход: использование Bundle.main.infoDictionary

Если вам нужно только узнать, является ли устройство iPhone, iPad или iPod, и не требуется точная модель, можно обойтись без sysctl. Но для точной модели есть ещё один способ — читать файл /System/Library/CoreServices/SystemVersion.plist или использовать uname() через C-функции напрямую.

Простой метод для iOS 16+

Начиная с iOS 16, Apple добавила свойство modelName в UIDevice, но оно всё ещё возвращает обобщённые названия. Однако для определения конкретной модели можно использовать sysctlbyname с аргументом "hw.machine":

import Foundation

func getHardwareModel() -> String {
    var size = 0
    sysctlbyname("hw.machine", nil, &size, nil, 0)
    var machine = [CChar](repeating: 0, count: size)
    sysctlbyname("hw.machine", &machine, &size, nil, 0)
    return String(cString: machine)
}

let model = getHardwareModel()
print(model) // "iPhone15,3"

Обработка ошибок

Этот код не обрабатывает возможные ошибки sysctl. Для продакшена лучше добавить проверку:

func getHardwareModelSafe() -> String? {
    var size = 0
    guard sysctlbyname("hw.machine", nil, &size, nil, 0) == 0, size > 0 else { return nil }
    var machine = [CChar](repeating: 0, count: size)
    guard sysctlbyname("hw.machine", &machine, &size, nil, 0) == 0 else { return nil }
    return String(cString: machine)
}

Когда это полезно