Avatar ·

Swift: print() vs println() vs NSLog() — что выбрать для логирования?

Всем привет! Я начинающий iOS-разработчик и пишу своё первое приложение на Swift. Столкнулся с тем, что для вывода сообщений в консоль можно использовать несколько разных функций: `print()`, `println()` и `NSLog()`. Я уже видел, что `println()` вроде как устарел, но в старых туториалах он всё ещё встречается. Сейчас я использую `print()` для отладки, но заметил, что когда я пишу: ```swift print("User logged in") ``` В консоль выводится просто строка. А когда коллега показал `NSLog`: ```swift NSLog("User logged in") ``` Там появляется дата, время, название процесса и PID. Это удобно для продакшена? Или лучше вообще не использовать `NSLog` из-за производительности? Ещё я слышал про `os_log` из фреймворка `os.log`, но не понимаю, зачем он нужен, если `print()` и так работает. Подскажите, пожалуйста, в чём разница между этими подходами и какой из них лучше использовать в современных проектах? Мне важно понять не только синтаксис, но и когда какой инструмент применять: для отладки, для логирования в продакшене, для анализа ошибок. Заранее спасибо! <<>>

Полный разбор: print(), println() и NSLog() в Swift

Отличный вопрос! Давайте разберём каждый инструмент по порядку, а затем я покажу, как правильно организовать логирование в современном Swift-проекте.

1. println() — история

println() существовал только в самых ранних версиях Swift (1.x) и был полностью удалён. В современном Swift (5.x) этот код даже не скомпилируется. Забудьте про него — используйте только print().

2. print() — для быстрой отладки

print() — это базовая функция для вывода в стандартный поток (stdout). Она:

  • Не добавляет никаких метаданных (время, процесс).
  • Работает синхронно и быстро.
  • Выводит объекты, реализующие протокол CustomStringConvertible.
  • Поддерживает интерполяцию строк: print("Value: \(x)").

Пример:

let name = "Alice"
print("Hello, \(name)!") // Вывод: Hello, Alice!

Когда использовать: Только для локальной отладки во время разработки. Никогда не оставляйте print() в продакшен-коде — это создаёт нагрузку на консоль и не даёт никакой структуры для анализа.

3. NSLog() — устаревший, но всё ещё работает

NSLog() — это функция из Foundation, которая:

  • Добавляет timestamp, имя процесса, PID и номер потока.
  • Автоматически добавляет символ новой строки.
  • Является потоко-безопасной (thread-safe).
  • Работает медленнее, чем print().
  • В iOS симуляторе выводит в консоль Xcode, на реальном устройстве — в системный лог (через syslogd).

Пример:

NSLog("User tapped button %@", buttonID) // Вывод: 2025-01-15 10:30:45.123 MyApp[1234:56789] User tapped button btn_ok

Когда использовать: Только если вы поддерживаете очень старый код (iOS 9 и ниже) или вам нужно совместимость с macOS logging tools. В новых проектах NSLog() считается устаревшим.

4. os_log() — современный стандарт (рекомендую)

Начиная с iOS 10/macOS 10.12, Apple представила фреймворк os.log (теперь часть OSLog). Это правильный инструмент для логирования в продакшене:

  • Категоризация: можно разделять логи по подсистемам (subsystem) и категориям (category).
  • Уровни важности: default, info, debug, error, fault.
  • Производительность: логи не формируются, если уровень логирования не активен (ленивая оценка).
  • Безопасность: можно скрыть чувствительные данные (privacy).
  • Интеграция: работает с Console.app, Instruments и Unified Logging.

Пример использования:

import os.log

let logger = Logger(subsystem: "com.myapp", category: "network")

// Разные уровни
logger.debug("Connecting to server...")
logger.info("Connection established")
logger.error("Failed to fetch data: \(error.localizedDescription)")

// Скрытие чувствительных данных
let userEmail = "user@example.com"
logger.info("User email: \(userEmail, privacy: .private)")

5. Сравнение производительности

Чтобы вы понимали разницу:

  • print() — ~0.001 мс на вызов (очень быстро).
  • NSLog() — ~0.05-0.1 мс (медленнее из-за форматирования).
  • os_log() — ~0.001 мс, если уровень не активен, и ~0.01 мс, если активен (оптимизировано системой).

В цикле из 10 000 вызовов NSLog() может вызвать заметные лаги, а os_log() — нет.

Рекомендация

В современных проектах используйте такую стратегию:

  • Для быстрой отладки в Xcode: print() — можно оставить временно, но перед коммитом удалить.
  • Для продакшен-логирования: Logger из os.log — структурированно, безопасно и производительно.
  • Для форматированного вывода сложных объектов: используйте dump() — он показывает иерархию объекта.

Пример правильного подхода:

import os.log

struct AppLogger {
    static let ui = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "UI")
    static let network = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Network")
    static let data = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Data")
}

// Использование
AppLogger.ui.debug("Button pressed")
AppLogger.network.error("Request failed: \(statusCode)")

Так вы получаете чистый, структурированный лог, который можно анализировать в Console.app или через сторонние инструменты (например, Datadog, Sentry).

<<>>

Альтернативный подход: обёртка для логирования

Если вы хотите универсальное решение, которое легко переключать между print() и os_log(), можно написать простую обёртку:

enum LogLevel: String {
    case debug, info, warning, error
}

func log(_ message: String, level: LogLevel = .debug, file: String = #file, function: String = #function, line: Int = #line) {
    #if DEBUG
    // В debug-сборке используем print для скорости
    print("[\(level.rawValue.uppercased())] \(file.components(separatedBy: "/").last ?? ""):\(line) - \(function) -> \(message)")
    #else
    // В релизной сборке используем os_log
    let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.app", category: level.rawValue)
    switch level {
    case .debug: logger.debug("\(message)")
    case .info: logger.info("\(message)")
    case .warning: logger.warning("\(message)")
    case .error: logger.error("\(message)")
    }
    #endif
}

// Использование
log("User signed in", level: .info)
log("Database error: \(error)", level: .error)

Плюсы:

  • Единый API для всех уровней.
  • Автоматически переключается между print() (для отладки) и os_log() (для релиза).
  • Добавляет имя файла и номер строки для быстрого поиска.

Минусы:

  • Не поддерживает приватность данных (privacy) — нужно дорабатывать.
  • В debug-режиме нет категоризации по subsystem.

Этот подход хорош для небольших проектов или когда вы только начинаете внедрять логирование. Для серьёзных приложений лучше сразу использовать Logger напрямую.