В этой статье мы обсудим методы отладки, которые используют Senior iOS-разработчики.
1. Выявление логических ошибок на этапе разработки
Утверждения (Assertion) используются для обнаружения логических ошибок во время разработки. В проде они отключены, чтобы избежать влияния на производительность приложения.
Давайте разберемся на примере;
func calculateSquare(x:Double) -> Double {
assert(x != 0, "Number cannot be zero")
return x*x
}
Здесь мы добавили оператор assert, который проверяет, не равен ли x нулю — если это так, приведенный выше код не будет выполнен, и во время разработки будет выдана ошибка.
Это позволяет обнаружить ошибку на этапе разработки.
2. Обогащение отладочных логов контекстной информацией
Swift предлагает различные литералы, которые действительно полезны для отладки. Среди них особенно полезны #file, #line, #column и #function.
#fileуказывает имя файла#lineуказывает номер строки#columnуказывает имя столбца#functionуказывает имя функции
Давайте сначала создадим функцию, которая будет записывать эту информацию в лог:
func log(_ message: String,
file: String = #file,
function: String = #function,
line: Int = #line) {
print("[\(file):\(line)] \(function) — \(message)")
}
Мы создали функцию логирования, которая записывает дополнительную информацию.
Давайте используем эту функцию примерно так:
func processUserInput(_ input: String) {
guard input != "error" else {
log("Invalid user input")
return
}
print("User input processed successfully: \(input)")
}
processUserInput("hello")
//output
`Error: Invalid user input, file:
.../main.swift, line: 19, function: processUserInput(_:)`
Как видите, мы использовали функцию логирования в processUserInput для записи дополнительной информации.
3. Настройка вывода с помощью CustomDebugStringConvertible
CustomDebugStringConvertible — это протокол, который улучшает процесс отладки, позволяя нам определять пользовательское текстовое представление наших типов.
Предположим, у нас есть структура User:
struct User {
let name: String
let age: String
let role: String
}
let user = User(name: "Jayant Kumar", age: 26, role: "Super Admin")
При логировании объекта пользователя мы можем получить не тот формат, который хотели бы:
print(String(reflecting: user)) // output // `User(name: "Jayant Kumar", age: 26, role: "Super Admin")`
Для настройки вывода мы будем использовать протокол CustomDebugStringConvertible:
extension User: CustomDebugStringConvertible {
var debugDescription: String {
"""
User Information:
User: \(name) \
[Role: \(role), Age: \(age)]
"""
}
}
Здесь мы определяем расширение класса User, которое соответствует типу CustomDebugStringConvertible и обеспечивает желаемый кастомный вывод.
Теперь, когда мы печатаем объект User, будет отображаться именно тот вывод, который вы хотите использовать.
4. Анализ свойств и значений экземпляров во время выполнения
Иногда вам нужно увидеть, что на самом деле содержат ваши модели и представления — какие значения они в данный момент хранят в рантайме.
Здесь на помощь приходит Mirror.
В Swift Mirror — это мощный инструмент для рефлексии, который позволяет нам анализировать и исследовать значения во время выполнения.
struct User {
var name: String
var age: Int
}
let user = User(name: "Jayant", age: 26)
Предположим, у нас есть объект User, давайте выведем его значения с помощью Mirror.
Mirror принимает объект и предоставляет список всех его свойств:
func debugView<T>(_ value: T) {
let mirror = Mirror(reflecting: value)
for child in mirror.children {
print("Property: \(child.label ?? "unknown"), Value: \(child.value)")
}
}
Здесь мы создали универсальную функцию, которая принимает любой тип объекта и выводит все его свойства.
Теперь вызовите её следующим образом:
debugView(user)
В консоли вы увидите:
Property: name, Value: Jayant Property: age, Value: 26
Вы так же можете использовать Mirror с любым представлением.
Допустим, у нас есть вот это:
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Add") {
count += 1
}
}
}
}
Давайте отдебажим CounterView и посмотрим, какое значение он содержит:
let counter = CounterView() debugView(counter)
В консоли вы получите:
Property: count, Value: 0
Здесь у нас есть только состояние count в CounterView; если состояний несколько, будут выведены все.
5. Функция dump() для углубленной отладки
Функция dump() в Swift предоставляет подробный обзор свойств и их содержимого. Она используется для отладки сложных структур данных.
Это расширенная версия оператора print, которая выводит не только сам экземпляр, но и его подкомпоненты и вложенные свойства.
Рассмотрим ситуацию, когда мы работаем с вложенной структурой данных:
struct User {
var name: String
var age: Int
var address: Address
}
struct Address {
var street: String
var city: String
var zipCode: Int
}
let user = User(
name: "Jayant Kumar", age: 26,
address: Address(
street: "Block K",
city: "New Delhi", zipCode: 110088)
)
dump(user)
В приведенном выше примере вызов функции dump(user) выведет подробную информацию об объекте User, включая имя, возраст и вложенный объект Address со всеми его свойствами.

