Если вы разработчик Swift, вы, вероятно, много раз сталкивались со структурами и классами. Оба они являются мощными способами моделирования пользовательских типов, которые инкапсулируют данные и функциональные возможности. Но знаете ли вы, когда использовать структуру, а когда класс? Знаете ли вы различия и сходства между ними? Знаете ли вы, как они влияют на производительность, управление памятью и читаемость кода?

В этой статье я объясню все, что вам нужно знать о структурах и классах в Swift, и помогу вам решить, какой из них использовать для вашего следующего проекта. Давайте начнем!

Что такое структуры и классы в Swift?

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

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

struct ArticleStruct {
    let title: String
    let url: URL
    var readCount: Int = 0
}

class ArticleClass {
    let title: String
    let url: URL
    var readCount: Int = 0
    init(title: String, url: URL) {
        self.title = title
        self.url = url
    }
}

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

Однако между структурами и классами есть некоторые важные различия, о которых вам необходимо знать.

В чем разница между структурами и классами?

Основное различие между структурами и классами заключается в том, что структуры являются типами значений, а классы — ссылочными типами. Это означает, что при копировании структуры вы получаете две уникальные копии данных. Когда вы копируете класс, вы получаете две ссылки на один экземпляр данных.

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

let articleStruct = ArticleStruct(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!)
let articleStructCopy = articleStruct // Copying a struct creates a new instance
articleStruct.readCount = 10 // Updating the read count on one instance
print(articleStructCopy.readCount) // Prints: 0

let articleClass = ArticleClass(title: "Struct vs Class", url: URL(string: "www.avanderlee.com")!)
let articleClassCopy = articleClass // Copying a class creates a new reference
articleClass.readCount = 10 // Updating the read count on one instance
print(articleClassCopy.readCount) // Prints: 10

Как видите, при копировании структуры создается новый экземпляр с собственной копией данных. Обновление счетчика чтения в одном экземпляре не влияет на другой экземпляр. При копировании класса создается новая ссылка на тот же экземпляр данных. Обновление счетчика чтения для одного экземпляра влияет на все ссылки на этот экземпляр.

Это различие имеет несколько последствий для того, как вы используете структуры и классы в своем коде.

Управление памятью

Одним из следствий этого является то, что структуры и классы имеют разные стратегии управления памятью. Структуры хранятся в стеке, что является быстрым и эффективным способом выделения и освобождения памяти. Классы хранятся в куче, что является более сложным и медленным способом управления памятью.

Swift использует автоматический подсчет ссылок (ARC), чтобы отслеживать количество ссылок на каждый экземпляр класса. Когда больше нет ссылок на экземпляр, ARC освобождает его из памяти. Это означает, что вам не нужно беспокоиться об утечках памяти или ручном управлении памятью при использовании классов.

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

У структур нет этих проблем, потому что они копируются по значению и не делят память с другими экземплярами. Они также более совместимы с парадигмами функционального программирования, которые поддерживают неизменность и чистые функции.

Дополнительные возможности

Другое следствие состоит в том, что у классов есть некоторые дополнительные возможности, которых нет у структур. Это:

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

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

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

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

Итак, когда мне следует использовать структуру, а когда — класс?

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

Некоторые конкретные случаи, когда вы должны использовать структуры:

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

Некоторые конкретные случаи, когда вы должны использовать классы:

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

Конечно, это не жесткие правила, и в зависимости от конкретной ситуации могут быть исключения. Лучший способ решить, использовать ли структуру или класс, — это подумать о том, как должен вести себя ваш тип, как он взаимодействует с другими типами и на какие компромиссы вы готовы пойти.

Заключение

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

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

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

  • Структуры и классы — Swift.org
  • Структура и классы в Swift: объяснение различий — SwiftLee
  • Объяснение структуры и класса в Swift — Appy Pie
  • Структуры и классы в Swift | Код углерода
  • Тип литья | Документация — Swift.org
  • Наследование | Документация — Swift.org