[Перевод] Топ-5 распространенных практик написания хорошего Swift-кода

Повышаем производительность и читабельность вашего  Swift-кода

0ca586b1d262b4e1f78d8602d43902ff.jpeg

Эффективность при написании кода заключается не только в достижении желаемой функциональности, но и в создании кода, который будет производительным, удобным в сопровождении и легко читаемым. В Swift то, как вы пишете код, может оказывать сильное влияние на общую производительность и user experience ваших приложений. В этой статье мы рассмотрим некоторые ключевые сравнения и практики программирования на Swift, которые могут значительно повысить эффективность вашего кода.

Благодаря этим сравнениям вы получите более глубокое понимание того, как определенные методы написания кода в Swift могут привести к более оптимизированным, элегантным и эффективным решениям. Независимо от того, являетесь ли вы опытным разработчиком или только начинаете свой путь, эти знания помогут вам писать Swift-код, который не только хорошо работает, но и достигает оптимальной производительности, читабельности и удобства сопровождения.

№1: Циклы for и forEach

Описание и синтаксис

Цикл for: Цикл for-in — это классическая конструкция цикла в Swift, используемая для итерации по последовательностям, например массивам, диапазонам или строкам. Вот пример его использования:

let numbers = [1, 2, 3, 4, 5]

for number in numbers {

    print(number)

}

В этом примере цикл for-in проходит по каждому элементу массива numbers.

Замыкание forEach: метод forEach — это функция высшего порядка, которая принимает замыкание и применяет его к каждому элементу коллекции. Пример:

let numbers = [1, 2, 3, 4, 5]

numbers.forEach { number in

    print(number)

}

Это замыкание forEach выдает нам тот же результат, что и цикл for-in выше.

Анализ производительности

В большинстве случаев разница в производительности между циклами for-in и forEach незначительна, особенно для коллекций небольшого или среднего размера. Однако циклы for-in могут иметь небольшое преимущество в производительности за счет прямого доступа к элементам коллекции, в то время как forEach предполагает вызов функции для каждого элемента.

Разница в производительности может стать более заметной в сценариях, предполагающих сложные операции в каждой итерации или при работе с большими наборами данных.

Читабельность и примеры использования

Читабельность:

  • forEach часто более лаконичен, и код с его использованием может выглядеть чище, особенно при использовании парадигм функционального программирования.

  • Циклы for-in представляют из себя классические циклы и могут быть более читабельны для разработчиков, знакомых с императивным стилем программирования.

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

  • Используйте циклы for-in, когда вам нужно больше контроля над итерацией, например, чтобы выйти из цикла раньше или пропустить некоторые элементы.

  • forEach идеально подходит для применения определенной операции к каждому элементу коллекции без необходимости задействовать дополнительную логику с применением управляющих конструкций.

Лучшие практики

  • Если вам нужно выполнить простую операцию над каждым элементом и вам не нужны управляющие конструкции (например, break или continue), отдайте предпочтение forEach.

  • В ситуациях, когда необходимо изменить ход выполнения цикла (например, использовать break, continue или изменить переменную цикла), используйте цикл for-in.

  • Для сложных операций, в которых производительность имеет большое значение, рекомендуется проводить бенчмарки как с for-in, так и с forEach, чтобы принять обоснованное решение.

№2: Проверка диапазона

Описание и синтаксис

Традиционная проверка диапазона: Традиционный способ проверки того, попадает ли значение в диапазон, предполагает использование операторов <= и >=. Вот пример:

let x = 25

if 0 <= x && x <= 30 {

    print("x is within the range 0 to 30")

}

В этом примере мы проверяем x на предмет того, что он больше или равен 0 и меньше или равен 30.

Использование метода .contains: Swift предоставляет более лаконичный синтаксис для проверки того, находится ли значение в диапазоне, с помощью метода .contains:

let x = 25

if (0...30).contains(x) {

    print("x is within the range 0 to 30")

}

Здесь мы также проверяем, попадает ли x в закрытый диапазон от 0 до 30.

Анализ производительности

  • Для простых проверок диапазона разница в производительности между двумя методами обычно минимальна или даже пренебрежимо мала для повседневных сценариев.

  • Однако в критически важных для производительности частях приложения, например, в больших циклах или при обработке больших массивов данных, традиционный метод (if 0 <= x && x <= 30) может иметь небольшое преимущество в производительности благодаря прямому сравнению, что может быть быстрее, чем вызов метода типа .contains.

Читабельность и контекст

  • Метод .contains предлагает более лаконичную и удобочитаемую форму, особенно для тех, кто знаком с синтаксисом диапазонов Swift. Это может облегчить чтение и сделать код понятным с первого взгляда.

  • Традиционный метод может быть более привычным для разработчиков, пришедших из других языков программирования, и может быть более наглядным в своих намерениях, что некоторые могут предпочесть из соображений выразительности.

Лучшие практики

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

  • В сценариях, где производительность критически важна и каждая миллисекунда на счету, например при обработке больших массивов или сложных алгоритмов, традиционный метод проверки диапазона может оказаться более подходящим.

  • При выборе между этими методами учитывайте общий контекст кода и уровень знания возможностей Swift вашей команды. Последовательность в стиле написания кода также может быть важным фактором для вашей команды.

№3: Использование Map и циклов for для преобразований

Описание и синтаксис

Цикл for и преобразования: Традиционный цикл for можно использовать для преобразования элементов коллекции, перебирая каждый элемент и применяя преобразование (transformation). Вот как это выглядит:

let numbers = [1, 2, 3, 4, 5]

var squaredNumbers = Int

for number in numbers {

    squaredNumbers.append(number * number)

}

В этом примере каждое число в массиве numbers возводится в квадрат и добавляется в массив squaredNumbers.

Использование map для преобразования: Функция map — это функция высшего порядка, которая применяет заданное преобразование к каждому элементу коллекции и возвращает новый массив. Пример:

let numbers = [1, 2, 3, 4, 5]
let squaredNumbers = numbers.map { $0 * $0 }

Эта строка кода достигает того же результата, что и цикл for, но в более лаконичной форме.

Анализ производительности

  • Производительность map и традиционных циклов for в целом сопоставима, особенно для коллекций малого и среднего размера.

  • map может иметь небольшие накладные расходы из-за того, что является функцией высшего порядка, но обычно они незначительны.

  • Пара бенчмарков, на которые я рекомендовал бы вам взглянуть

Читабельность и функциональное программирование

  • map приводит к более лаконичному и читаемому коду, сокращая количество рутинного кода и фокусируясь на операции, выполняемой над каждым элементом.

  • Она хорошо согласуется с принципами функционального программирования, продвигая иммутабельности и stateless-операций.

  • Циклы for могут быть более наглядными и предпочтительнее для более сложных преобразований, где требуется какая-нибудь дополнительная логика.

Лучшие практики

  • map предпочтительнее при применении одного преобразования к каждому элементу коллекции, так как это приводит к более лаконичному и декларативному коду.

  • Используйте циклы for, когда логика преобразования сложна или когда требуется какие-нибудь дополнительные управляющие конструкции (например, break или условные операторы).

  • Учитывайте читаемость и сопровождаемость кода как основные факторы, особенно в команде, где ясность намерений имеет решающее значение.

№4: Ленивые свойства и немедленная инициализация

Описание и концепция

Немедленная инициализация: Немедленная инициализация означает, что свойство класса или структуры полностью инициализируется сразу же после создания экземпляра этого класса или структуры. Пример:

class ImageGallery {
    var images: [Image]

    init() {
        // Предполагается, что loadHighResolutionImages() — это функция, которая загружает изображения с диска или из сети
        images = loadHighResolutionImages() // Изображения загружаются сразу при инициализации
        print("Images are loaded!")
    }

    func loadHighResolutionImages() -> [Image] {
        // Сложная логика загрузки
        // ...
        return [Image(named: "image1"), Image(named: "image2"), Image(named: "image3")]
    }

    // ... остальные свойства и методы ...
}

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

let gallery = ImageGallery() // "Images are loaded!" выводится немедленно

В этом примере изображения загружаются в память, как только создается экземпляр ImageGallery.

Ленивая инициализация: lazy свойства в Swift — это свойства, которые инициализируются только при первом обращении к ним. Это полезно для свойств, изначальные значения которых требуют больших вычислительных затрат или не нужны сразу же после создания экземпляра. Пример:

class ImageGallery {
    lazy var images: [Image] = {
        // Этот блок выполняется только при первом обращении к 'images'.
        let loadedImages = loadHighResolutionImages()
        print("Images are lazily loaded!")
        return loadedImages
    }()

    func loadHighResolutionImages() -> [Image] {
        // Сложная логика загрузки
        // ...
        return [Image(named: "image1"), Image(named: "image2"), Image(named: "image3")]
    }

    // ... остальные свойства и методы ...
}

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

let gallery = ImageGallery() // Изображения в этот момент еще НЕ начинают загружаться
print("Gallery created")
gallery.images // При первом обращении к 'images' происходит загрузка: выводится "Images are lazily loaded!".

Здесь изображения загружаются только при первом обращении к ним, что позволяет экономить ресурсы, если они в конечном итоге окажутся невостребованными.

Анализ производительности

  • Время начальной загрузки: использование ленивых свойств может значительно сократить время начальной загрузки приложения, поскольку дорогостоящие вычисления откладываются до тех пор, пока они не понадобятся.

  • Использование памяти: ленивые свойства могут сократить использование памяти, особенно в случаях, когда свойство может не требоваться немедленно для каждого экземпляра.

  • Общая производительность: Хотя ленивые свойства могут повысить производительность в определенных сценариях, они потенциально могут создавать накладные расходы при первом обращении к ним.

Примеры использования и управление памятью

  • Ресурсоемкие свойства: Используйте lazy для свойств, инициализация которых требует значительных ресурсов, например, для загрузки больших наборов данных, изображений или сложных вычислений.

  • Необязательные свойства: lazy также полезно для свойств, которые могут использоваться не в каждом сеансе или экземпляре, например, необязательные компоненты пользовательского интерфейса.

  • Управление памятью: Поскольку ленивые свойства инициализируются только при обращении к ним, они могут помочь более эффективно управлять памятью, особенно в средах с ограниченным объемом памяти.

Лучшие практики

  • Используйте ленивые свойства, когда стоимость инициализации высока и не все экземпляры класса нуждаются в этом свойстве.

  • Избегайте использования дескриптора lazy для свойств, которые требуются немедленно или для всех экземпляров, так как это добавляет ненужную сложность.

  • Будьте внимательны к потокобезопасности при обращении к ленивым свойствам из нескольких потоков; при необходимости рассмотрите механизмы синхронизации.

  • Оцените, соответствует ли использование дескриптора lazy для конкретного свойства жизненному циклу приложения и потребностям пользователей.

№5: Структуры и классы: Выбираем между ссылочными и значимыми типами

Описание и синтаксис

Struct (значимый тип): В Swift struct — это значимый тип. Когда вы присваиваете значимый тип переменной, константе или передаете его в функцию, он копируется. Вот пример:

struct Point {
    var x: Int
    var y: Int
}

var point1 = Point(x: 0, y: 0)
var point2 = point1 // point2 является копией point1
point2.x = 5 // Изменение point2 не влияет на point1

В этом примере point2 является отдельной копией point1. Изменения в point2 не влияют на point1.

Класс (ссылочный тип): class — это ссылочный тип. В отличие от значимых типов, ссылочные типы не копируются при присвоении переменной или константе, а также при передаче в функцию. Вместо этого используется ссылка на один и тот же существующий экземпляр. Пример:

class Box {
    var width: Int
    var height: Int

    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}

var box1 = Box(width: 10, height: 20)
var box2 = box1 // box2 ссылается на тот же экземпляр, что и box1
box2.width = 50 // Изменение box2 также влияет на box1

Здесь box2 и box1 ссылаются на один и тот же экземпляр, поэтому изменения в box2 отражаются и в box1.

Анализ производительности

  • Управление памятью: структуры, как правило, более эффективны с точки зрения управления памятью, поскольку они выделяются в стеке, в то время как классы выделяются в куче.

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

  • Поведение при копировании: Для больших структур копирование может стать дорогостоящим. В таких случаях классы могут быть более эффективными, поскольку они передают ссылки, а не копируют целые структуры.

Мутабельность и безопасность потоков

  • Мутабельность: В структурах каждый экземпляр владеет своими данными, поэтому изменения не влияют на другие экземпляры, что делает код более предсказуемым. Классы же разделяют один экземпляр, поэтому изменения в одном месте могут повлиять на другие части вашей программы.

  • Потокобезопасность: структуры по своей природе более безопасны в параллельной среде, поскольку они не разделяют память. Для обеспечения потокобезопасности классам могут потребоваться дополнительные механизмы синхронизации (например, блокировки).

Лучшие практики

  • Предпочитайте структуры для небольших, простых структур данных, которые копируются, а не используются совместно.

  • Используйте классы, когда вам нужно наследование, идентичность (например, синглтоны) или когда вы имеете дело с большими и сложными данными, которые не должны копироваться.

  • Рассмотрите возможность использования структур для повышения потокобезопасности и производительности при выполнении параллельных операций.

  • Оцените, какая семантика — семантика ссылок (предлагаемая классами) или семантика значений (предлагаемая структурами) — больше подходит для вашего конкретного сценария использования.

Заключение: Повышение качества вашего Swift-кода

В этом разборе азов написания кода на Swift мы увидели, как выбор правильных инструментов и подходов может значительно повысить производительность, читабельность и удобство сопровождения кода. От понимания нюансов циклов for и forEach до эффективного использования map, ленивых свойств и выбора между структурами и классами — каждое решение играет ключевую роль в создании эффективного и элегантного кода. Продолжая свой путь в разработке на Swift, помните об этих знаниях. Это не просто интересные приемы, а ступеньки на пути к овладению искусством написания элегантного Swift-кода.

В заключение приглашаем на вебинар, на котором мы напишем небольшое приложений с использованием MusicKit.

На WWDC 2023 Apple представили новые интерактивные виджеты с поддержкой расширенного функционала по работе с сервисами Apple. Виджеты теперь не только красивые, но и действительно полезные.

В процессе нашего вебинара мы создадим приложение с использованием музыкального сервиса MusicKit и интерактивного виджета к нему. У вас будет возможность ознакомиться со следующими аспектами: SwiftUI для создания виджетов, WidgetKit и AppIntents для обеспечения интерактивности. Урок состоится уже сегодня вечером — успевайте записаться на странице онлайн-курса OTUS «iOS Developer. Professional».

© Habrahabr.ru