Site icon AppTractor

Отлаживайте Swift как Senior

В этой статье мы обсудим методы отладки, которые используют 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.

Давайте сначала создадим функцию, которая будет записывать эту информацию в лог:

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 со всеми его свойствами.

Источник

Exit mobile version