Почему стоит выбирать Struct Over Class?

476

Поэкспериментируя со Swift, исходящим из Java, зачем вам выбирать Struct вместо Class? Похоже, что это одно и то же, а Struct предлагает меньше функциональности. Зачем тогда выбирать?

bluedevil2k
источник
11
Структуры всегда копируются, когда они передаются в вашем коде, и не используют подсчет ссылок. Источник: developer.apple.com/library/prerelease/ios/documentation/swift/...
holex
4
Я бы сказал, что структуры более приспособлены для хранения данных, а не логики. Чтобы говорить в терминах Java, представьте себе структуры как «Объекты значения».
Винсент Гверчи
6
Я поражен во всем этом разговоре, что нет прямого упоминания о копировании при записи, иначе как о ленивой копии . Любые опасения по поводу производительности структурного копирования в основном не обсуждаются в связи с этим дизайном.
Дэвид Джеймс
3
Выбор структуры по классу не является вопросом мнения. Есть конкретные причины, чтобы выбрать один или другой.
Дэвид Джеймс
Я настоятельно рекомендую посмотреть, почему Array не является threadSafe . Это связано с тем, что массивы и структуры являются типами значений. Во всех ответах здесь упоминается, что с типами структурами / массивами / значениями никогда не будет проблем с безопасностью потоков, но в некоторых случаях это может иметь место.
Мед

Ответы:

548

В соответствии с очень популярным докладом о программировании на языке WWDC 2015 в Swift ( видео , транскрипция ), Swift предоставляет ряд функций, которые во многих случаях делают структуры лучше, чем классы.

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

В случае Structs гораздо меньше нужно беспокоиться об утечках памяти или множественных потоках, пытающихся получить доступ / изменить один экземпляр переменной. (Для более технически мыслящих, исключение составляет случай захвата структуры внутри замыкания, потому что тогда он фактически захватывает ссылку на экземпляр, если вы явно не пометите его для копирования).

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

В докладе изложены следующие сценарии, где предпочтение отдается классам:

  • Копирование или сравнение экземпляров не имеет смысла (например, Window)
  • Время жизни экземпляра связано с внешними эффектами (например, TemporaryFile)
  • Экземпляры - это просто «приемники» - каналы только для записи во внешнее состояние (например, CGContext).

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

С другой стороны, документация по языку программирования Swift несколько противоречива:

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

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

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

Примеры хороших кандидатов для структур включают в себя:

  • Размер геометрической фигуры, возможно, инкапсулируя свойство width и свойство height, оба типа Double.
  • Способ ссылки на диапазоны внутри серии, возможно, инкапсулируя свойство start и свойство length, оба типа Int.
  • Точка в трехмерной системе координат, возможно, инкапсулирующая свойства x, y и z, каждый из которых имеет тип Double.

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

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

drewag
источник
12
@ElgsQianChen весь смысл этой записи в том, что структура должна быть выбрана по умолчанию, а класс должен использоваться только при необходимости. Структуры намного безопаснее и без ошибок, особенно в многопоточной среде. Да, вы всегда можете использовать класс вместо структуры, но структуры предпочтительнее.
Drewag
16
@drewag Кажется, это полная противоположность тому, что говорится. Он говорил, что по умолчанию должен использоваться класс, а не структура. In practice, this means that most custom data constructs should be classes, not structures.Можете ли вы объяснить мне, как, прочитав это, вы получите, что большинство наборов данных должны быть структурами, а не классами? Они дали определенный набор правил, когда что-то должно быть структурой, и в значительной степени сказали: «Во всех других сценариях класс лучше».
Мэтт
42
В последней строке должно быть сказано: «Мой личный совет противоположен документации:» ... и тогда это отличный ответ!
Дэн Розенстарк
5
В книге Swift 2.2 все еще говорится, что в большинстве ситуаций нужно использовать классы.
Дэвид Джеймс
6
Struct over Class определенно уменьшит сложность. Но что влияет на использование памяти, когда структуры становятся выбором по умолчанию. Когда вещи копируются везде вместо ссылки, это должно увеличить использование памяти приложением. Не должно ли это?
MadNik
164

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

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

Рассмотрим следующий пример, который демонстрирует 2 стратегии переноса Intтипов данных с использованием structи class. Я использую 10 повторяющихся значений, чтобы лучше отражать реальный мир, где у вас есть несколько полей.

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

Производительность измеряется с помощью

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

Код можно найти по адресу https://github.com/knguyen2708/StructVsClassPerformance

ОБНОВЛЕНИЕ (27 марта 2018 года) :

Начиная с Swift 4.0, Xcode 9.2, с выпуском сборки на iPhone 6S, iOS 11.2.6, настройка компилятора Swift -O -whole-module-optimization:

  • class версия заняла 2,06 секунды
  • struct версия заняла 4.17e-08 секунд (в 50 000 000 раз быстрее)

(Я больше не усредняю ​​множественные прогоны, так как отклонения очень малы, менее 5%)

Примечание : разница намного менее существенна без оптимизации всего модуля. Я был бы рад, если бы кто-то мог указать, что на самом деле делает флаг.


ОБНОВЛЕНИЕ (7 мая 2016 года) :

Начиная с Swift 2.2.1, Xcode 7.3, работающий с выпуском сборки на iPhone 6S, iOS 9.3.1, в среднем за 5 запусков, настройка компилятора Swift -O -whole-module-optimization:

  • class версия заняла 2.159942142s
  • struct версия заняла 5.83E-08s (в 37 000 000 раз быстрее)

Примечание : как кто-то упомянул, что в реальных сценариях, вероятно, будет более 1 поля в структуре, я добавил тесты для структур / классов с 10 полями вместо 1. Удивительно, но результаты не сильно различаются.


ОРИГИНАЛЬНЫЕ РЕЗУЛЬТАТЫ (1 июня 2014 г.):

(Запускается на структуру / класс с 1 полем, а не 10)

Начиная с Swift 1.2, Xcode 6.3.2, работающий под управлением Release build на iPhone 5S, iOS 8.3, в среднем за 5 прогонов

  • class версия заняла 9.788332333s
  • struct версия заняла 0.010532942s (в 900 раз быстрее)

СТАРЫЕ РЕЗУЛЬТАТЫ (из неизвестного времени)

(Запускается на структуру / класс с 1 полем, а не 10)

С выпуском сборки на моем MacBook Pro:

  • classВерсия приняла 1.10082 сек
  • structВерсия приняла 0.02324 сек ( в 50 раз быстрее)
Хан Нгуен
источник
27
Да, но кажется, что копирование множества структур вокруг будет медленнее, чем копирование ссылки на один объект. Другими словами, быстрее скопировать один указатель, чем скопировать произвольно большой блок памяти.
Tylerc230
14
-1 Этот тест не является хорошим примером, потому что в структуре есть только одна переменная. Обратите внимание, что если вы добавите несколько значений и один или два объекта, версия struct будет сопоставима с версией класса. Чем больше переменных вы добавляете, тем медленнее становится версия struct.
Джошрл
6
@joshrl получил вашу точку зрения, но пример «хороший» или нет, зависит от конкретной ситуации. Этот код был извлечен из моего собственного приложения, поэтому он является допустимым вариантом использования, и использование структур значительно повысило производительность моего приложения. Вероятно, это не обычный случай использования (ну, в общем случае, для большинства приложений никого не волнует скорость передачи данных, поскольку узкое место возникает где-то еще, например, сетевые подключения, во всяком случае, оптимизация - это не так). критично, когда у вас есть ГГц устройства с ГБ или ОЗУ).
Хан Нгуен
26
Насколько я понимаю, копирование в swift оптимизировано для написания во время WRITE. Это означает, что копия физической памяти не создается, если новая копия не будет изменена.
Матян
6
Этот ответ показывает чрезвычайно тривиальный пример, вплоть до того, что он нереалистичен и, следовательно, неверен во многих случаях. Лучшим ответом будет «это зависит».
Iwasrobbed
60

Сходства между структурами и классами.

Я создал суть для этого на простых примерах. https://github.com/objc-swift/swift-classes-vs-structures

И отличия

1. Наследование

структуры не могут наследовать быстро. Если хочешь

class Vehicle{
}

class Car : Vehicle{
}

Пойти на урок

2. Пройдите мимо

Структуры Swift передаются по значению, а экземпляры классов - по ссылке.

Контекстные различия

Структурная константа и переменные

Пример (используется на WWDC 2014)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Определяет структуру под названием Point.

var point = Point(x:0.0,y:2.0)

Теперь, если я попытаюсь изменить х. Это правильное выражение.

point.x = 5

Но если бы я определил точку как постоянную.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

В этом случае вся точка является неизменной постоянной.

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

MadNik
источник
Вы можете наследовать структуры в Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
самый,
12
Вышеупомянутая суть о том, как мы можем добиться наследования ароматов для struct. Вы увидите синтаксис, как. A: B. Это структура с именем A, реализующая протокол под названием B. В документации Apple четко сказано, что структура не поддерживает чистое наследование и не поддерживает.
MadNik
2
мужик, что последний твой абзац был великолепен. Я всегда знал, что вы можете изменить константы ... но время от времени я видел, где вы не можете, поэтому я был сбит с толку. Это различие сделало это видимым
Мед
28

Вот еще несколько причин для рассмотрения:

  1. структуры получают автоматический инициализатор, который вам вообще не нужно поддерживать в коде.

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")

Чтобы получить это в классе, вы должны добавить инициализатор и поддерживать инициализатор ...

  1. Основные типы коллекций, такие Arrayкак структуры. Чем больше вы используете их в своем собственном коде, тем больше вы привыкнете передавать по значению, а не по ссылке. Например:

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Очевидно, что неизменность и изменчивость - огромная тема, но многие умные люди думают, что неизменность - структуры в этом случае - предпочтительнее. Изменчивые против неизменных объектов

Дэн Розенстарк
источник
4
Это правда, что вы получаете автоматические инициализаторы. Вы также получаете пустой инициализатор, когда все свойства являются необязательными. Но если у вас есть структура в Framework, вам нужно написать сам инициализатор, если вы хотите, чтобы он был доступен вне internalобласти.
Абизерн,
2
@Abizern подтвердил - stackoverflow.com/a/26224873/8047 - и человек, который раздражает.
Дэн Розенстарк
2
@Abizern есть веские причины для всего в Swift, но каждый раз, когда что-то верно в одном месте, а не в другом, разработчик должен знать больше вещей. Я предполагаю, что вот где я должен сказать: «Это интересно работать на таком сложном языке!»
Дэн Розенстарк
4
Могу ли я также добавить, что не неизменность структур делает их полезными (хотя это очень хорошая вещь). Вы можете изменять структуры, но вы должны пометить методы так, mutatingчтобы вы четко указывали, какие функции изменяют свое состояние. Но их природа как ценностных типов - вот что важно. Если вы объявляете структуру, letвы не можете вызывать какие-либо изменяющие функции для нее. Видео WWDC 15 о Лучшем программировании через типы значений является отличным ресурсом по этому вопросу.
Abizern
1
Спасибо @Abizern, я никогда не понимал это прежде, чем прочитал твой комментарий. Для объектов let против var нет особой разницы, но для структур оно огромно. Спасибо за указание на это.
Дэн Розенстарк
27

Предполагая, что мы знаем, что Struct является типом значения, а Class является ссылочным типом .

Если вы не знаете, что такое тип значения и ссылочный тип, посмотрите, в чем разница между передачей по ссылке и передачей по значению?

Основано на посте mikeash :

... Давайте сначала посмотрим на некоторые крайние, очевидные примеры. Целые числа явно копируемые. Они должны быть типами значений. Сетевые сокеты не могут быть разумно скопированы. Они должны быть ссылочными типами. Точки, как в парах x, y, копируются. Они должны быть типами значений. Контроллер, представляющий диск, не может быть разумно скопирован. Это должен быть ссылочный тип.

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

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

А как насчет типов моделей? У вас может быть тип User, представляющий пользователя в вашей системе, или тип Crime, представляющий действие, предпринятое пользователем. Они довольно копируемые, поэтому они должны быть типами значений. Однако вы, вероятно, хотите, чтобы обновления пользовательского преступления, сделанные в одном месте вашей программы, были видны другим частям программы. Это говорит о том, что вашими пользователями должны управлять какой-то пользовательский контроллер, который будет ссылочным типом . например

struct User {}
class UserController {
    var users: [User]

    func add(user: User) { ... }
    func remove(userNamed: String) { ... }
    func ...
}

Коллекции интересный случай. К ним относятся такие вещи, как массивы и словари, а также строки. Они копируемые? Очевидно. Легко и часто ли происходит копирование того, что вы хотите? Это менее понятно.

Большинство языков говорят «нет» на это и делают свои коллекции ссылочными типами. Это верно в Objective-C, Java, Python, JavaScript и почти во всех других языках, которые я могу себе представить. (Одним из главных исключений является C ++ с типами коллекций STL, но C ++ - бред сумасшедшего языкового мира, который делает все странно.)

Свифт сказал «да», что означает, что такие типы, как Array и Dictionary и String, являются структурами, а не классами. Они копируются при назначении и при передаче их в качестве параметров. Это вполне разумный выбор, если копия дешевая, чего Свифт очень старается выполнить. ...

Я лично так не называю свои занятия. Я обычно называю мой UserManager вместо UserController, но идея та же

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

Таким образом, вместо нескольких подклассов класса. Используйте несколько структур, которые соответствуют протоколу.


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

Мед
источник
1
Именно такое объяснение я искал.
Хорошо
Очень полезный пример контроллера
Спросите P
1
@AskP Я сам написал по электронной почте Майку и получил этот дополнительный кусок кода :)
Милый
18

Некоторые преимущества:

  • автоматически потокобезопасен из-за отсутствия общего доступа
  • использует меньше памяти из-за отсутствия isa и refcount (и фактически вообще выделяется стек)
  • методы всегда отправляются статически, поэтому могут быть встроенными (хотя @final может делать это для классов)
  • проще рассуждать (не нужно «защищенно копировать», как это обычно бывает с NSArray, NSString и т. д.) по той же причине, что и безопасность потоков
Catfish_Man
источник
Не уверен, что это выходит за рамки этого ответа, но можете ли вы объяснить (или, я полагаю, ссылку) точку «методы всегда статически отправляются»?
Дэн Розенстарк
2
Конечно. Я также могу прикрепить оговорку к нему. Цель динамической диспетчеризации - выбрать реализацию, если вы заранее не знаете, какую из них использовать. В Swift это может происходить либо из-за наследования (может быть переопределено в подклассе), либо из-за универсальной функции (вы не знаете, какой будет универсальный параметр). Структуры не могут быть унаследованы, а целая модульная оптимизация + универсальная специализация в основном устраняют неизвестные обобщенные элементы, поэтому методы можно просто вызывать напрямую, а не искать, что вызывать. Неспециализированные дженерики все еще выполняют динамическую диспетчеризацию для структур
Catfish_Man
1
Спасибо, отличное объяснение. Таким образом, мы ожидаем большей скорости выполнения или меньшей неопределенности с точки зрения IDE, или и того, и другого?
Дэн Розенстарк
1
В основном первый.
Catfish_Man
Просто обратите внимание, что методы не отправляются статически, если вы ссылаетесь на структуру через протокол.
Кристик
12

Структура намного быстрее, чем класс. Кроме того, если вам нужно наследование, вы должны использовать класс. Наиболее важным моментом является то, что Class является ссылочным типом, тогда как Structure является типом значения. например,

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "first ever flight of Virgin Airlines"
        destination = "london"
        airlines = "Virgin Airlines"
    } 
}

struct Flight2 {
    var id:Int
    var description:String
    var destination:String
    var airlines:String  
}

Теперь давайте создадим экземпляр обоих.

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

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

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "second flight of Virgin Airlines"
    flight.destination = "new york"
    flight.airlines = "Virgin Airlines"
}

также,

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "second flight from virgin airlines" 
}

так,

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

Теперь, если мы напечатаем идентификатор и описание рейса, мы получим

id = 200
description = "second flight of Virgin Airlines"

Здесь мы видим, что идентификатор и описание FlightA изменяются, поскольку параметр, передаваемый методу модификации, фактически указывает на адрес памяти объекта flightA (ссылочный тип).

Теперь, если мы напечатаем идентификатор и описание экземпляра FLightB, мы получим,

id = 100
description = "first ever flight of Virgin Airlines"

Здесь мы видим, что экземпляр FlightB не изменяется, потому что в методе modifyFlight2 фактический экземпляр Flight2 является скорее передачей, чем ссылкой (тип значения).

Маной Карки
источник
2
Вы никогда не создавали экземпляр FLightB
Дэвид
1
тогда почему ты говоришь о FlightB братан? Here we can see that the FlightB instance is not changed
Дэвид Сик
@ManojKarki, отличный ответ. Просто хотел отметить, что вы дважды объявили рейс A, когда я думаю, что вы хотели объявить FlightA, а затем FlightB.
ScottyBlades
11

Structsесть value typeи Classesестьreference type

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

Используйте valueтип, когда:

  • Вы хотите, чтобы копии имели независимое состояние, данные будут использоваться в коде в нескольких потоках

Используйте referenceтип, когда:

  • Вы хотите создать общее, изменяемое состояние.

Дополнительную информацию можно также найти в документации Apple

https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html


Дополнительная информация

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

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

В качестве напоминания вот список Swift

Типы значений:

  • Struct
  • Enum
  • Кортеж
  • Примитивы (Int, Double, Bool и т. Д.)
  • Коллекции (Массив, Строка, Словарь, Набор)

Типы ссылок:

  • Учебный класс
  • Все, что приходит от NSObject
  • функция
  • закрытие
Касильяс
источник
5

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

Используйте тип значения [например, struct, enum], когда:

  • Сравнение данных экземпляра с == имеет смысл
  • Вы хотите, чтобы копии имели независимое состояние
  • Данные будут использоваться в коде в нескольких потоках.

Используйте ссылочный тип [например, класс], когда:

  • Сравнение идентичности экземпляра с === имеет смысл
  • Вы хотите создать общее, изменяемое состояние

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

Дэвид Джеймс
источник
3

С классами вы получаете наследование и передаете по ссылке, структуры не имеют наследования и передаются по значению.

На Swift есть отличные сессии WWDC, на этот конкретный вопрос подробно дан ответ в одном из них. Обязательно смотрите их, так как это поможет вам быстрее освоить руководство по языку или iBook.

Joride
источник
Не могли бы вы предоставить ссылки из того, что вы упомянули? Потому что на WWDC есть довольно много, чтобы выбрать, я хотел бы посмотреть, что говорит на эту конкретную тему
MMachinegun
Для меня это хорошее начало здесь: github.com/raywenderlich/…
MMachinegun
2
Он, вероятно, говорит об этой замечательной сессии: протоколно-ориентированное программирование в Swift. (Ссылки: видео , стенограмма )
Zekel
2

Я бы не сказал, что структуры предлагают меньше функциональности.

Конечно, «я» является неизменным, кроме как в мутирующей функции, но это все.

Наследование работает хорошо, пока вы придерживаетесь старой доброй идеи, что каждый класс должен быть абстрактным или окончательным.

Реализуйте абстрактные классы как протоколы, а финальные классы как структуры.

Хорошая вещь о структурах состоит в том, что вы можете сделать ваши поля изменяемыми, не создавая общее изменяемое состояние, потому что копирование при записи позаботится об этом :)

Вот почему все свойства / поля в следующем примере являются изменяемыми, чего я бы не делал в классах Java, C # или swift .

Пример структуры наследования с небольшим грязным и простым использованием внизу в функции с именем «example»:

protocol EventVisitor
{
    func visit(event: TimeEvent)
    func visit(event: StatusEvent)
}

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

struct TimeEvent : Event
{
    var ts: Int64
    var time: Int64

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }
}

protocol StatusEventVisitor
{
    func visit(event: StatusLostStatusEvent)
    func visit(event: StatusChangedStatusEvent)
}

protocol StatusEvent : Event
{
    var deviceId: Int64 { get set }

    func accept(visitor: StatusEventVisitor)
}

struct StatusLostStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var reason: String

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

struct StatusChangedStatusEvent : StatusEvent
{
    var ts: Int64
    var deviceId: Int64
    var newStatus: UInt32
    var oldStatus: UInt32

    func accept(visitor: EventVisitor)
    {
        visitor.visit(self)
    }

    func accept(visitor: StatusEventVisitor)
    {
        visitor.visit(self)
    }
}

func readEvent(fd: Int) -> Event
{
    return TimeEvent(ts: 123, time: 56789)
}

func example()
{
    class Visitor : EventVisitor
    {
        var status: UInt32 = 3;

        func visit(event: TimeEvent)
        {
            print("A time event: \(event)")
        }

        func visit(event: StatusEvent)
        {
            print("A status event: \(event)")

            if let change = event as? StatusChangedStatusEvent
            {
                status = change.newStatus
            }
        }
    }

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}
йомен
источник
2

В Swift был введен новый шаблон программирования, известный как протоколно-ориентированное программирование.

Творческий Образец:

В Swift Struct представляет собой типы значений, которые автоматически клонируются. Поэтому мы получаем необходимое поведение для реализации шаблона прототипа бесплатно.

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


Мелкая копия дублирует только ссылку, которая указывает на эти объекты, тогда как глубокая копия дублирует ссылку на объект.


Реализация глубокого копирования для каждого ссылочного типа стала утомительной задачей. Если классы включают дополнительный ссылочный тип, мы должны реализовать шаблон прототипа для каждого из свойств ссылок. И затем мы должны фактически скопировать весь граф объекта путем реализации NSCopyingпротокола.

class Contact{
  var firstName:String
  var lastName:String
  var workAddress:Address // Reference type
}

class Address{
   var street:String
   ...
} 

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

Balasubramanian
источник
1

Многие API-интерфейсы Какао требуют подклассов NSObject, что заставляет вас использовать класс. Но кроме этого, вы можете использовать следующие случаи из блога Apple Swift, чтобы решить, использовать ли тип значения struct / enum или ссылочный тип класса.

https://developer.apple.com/swift/blog/?id=10

Акшай
источник
0

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

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

johnbakers
источник
0

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

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

Тапаш Моллик
источник
-7
  • Структура и класс являются определяемыми пользователем типами данных

  • По умолчанию структура является общедоступной, тогда как класс является частным

  • Класс реализует принцип инкапсуляции

  • Объекты класса создаются в памяти кучи

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

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

  • Члены класса данных могут быть инициализированы непосредственно конструктором без параметров и назначены параметризованным конструктором

Ридаа Захра
источник
2
Худший ответ когда-либо!
Дж. Доу
Скопировать