Поэкспериментируя со Swift, исходящим из Java, зачем вам выбирать Struct вместо Class? Похоже, что это одно и то же, а Struct предлагает меньше функциональности. Зачем тогда выбирать?
Поэкспериментируя со Swift, исходящим из Java, зачем вам выбирать Struct вместо Class? Похоже, что это одно и то же, а Struct предлагает меньше функциональности. Зачем тогда выбирать?
Ответы:
В соответствии с очень популярным докладом о программировании на языке WWDC 2015 в Swift ( видео , транскрипция ), Swift предоставляет ряд функций, которые во многих случаях делают структуры лучше, чем классы.
Структуры предпочтительнее, если они относительно малы и копируемы, потому что копирование намного безопаснее, чем использование нескольких ссылок на один и тот же экземпляр, как это происходит с классами. Это особенно важно при передаче переменной во многие классы и / или в многопоточной среде. Если вы всегда можете отправить копию своей переменной в другие места, вам никогда не придется беспокоиться о том, что это другое место изменит значение вашей переменной под вами.
В случае Structs гораздо меньше нужно беспокоиться об утечках памяти или множественных потоках, пытающихся получить доступ / изменить один экземпляр переменной. (Для более технически мыслящих, исключение составляет случай захвата структуры внутри замыкания, потому что тогда он фактически захватывает ссылку на экземпляр, если вы явно не пометите его для копирования).
Классы также могут стать раздутыми, потому что класс может наследовать только от одного суперкласса. Это побуждает нас создавать огромные суперклассы, которые включают в себя множество различных способностей, которые слабо связаны между собой. Использование протоколов, особенно с расширениями протоколов, где вы можете предоставлять реализации протоколов, позволяет вам исключить необходимость в классах для достижения такого поведения.
В докладе изложены следующие сценарии, где предпочтение отдается классам:
- Копирование или сравнение экземпляров не имеет смысла (например, Window)
- Время жизни экземпляра связано с внешними эффектами (например, TemporaryFile)
- Экземпляры - это просто «приемники» - каналы только для записи во внешнее состояние (например, CGContext).
Это подразумевает, что структуры должны быть по умолчанию, а классы должны быть запасным вариантом.
С другой стороны, документация по языку программирования Swift несколько противоречива:
Экземпляры структуры всегда передаются по значению, а экземпляры класса всегда передаются по ссылке. Это означает, что они подходят для различных задач. Рассматривая конструкции данных и функциональные возможности, которые необходимы для проекта, решите, следует ли определять каждую конструкцию данных как класс или структуру.
В качестве общего руководства рассмотрите возможность создания структуры, когда выполняется одно или несколько из следующих условий:
- Основной целью структуры является инкапсуляция нескольких относительно простых значений данных.
- Разумно ожидать, что инкапсулированные значения будут копироваться, а не ссылаться на них при назначении или передаче экземпляра этой структуры.
- Любые свойства, хранящиеся в структуре, сами по себе являются типами значений, которые также следует копировать, а не ссылаться на них.
- Структура не должна наследовать свойства или поведение от другого существующего типа.
Примеры хороших кандидатов для структур включают в себя:
- Размер геометрической фигуры, возможно, инкапсулируя свойство width и свойство height, оба типа Double.
- Способ ссылки на диапазоны внутри серии, возможно, инкапсулируя свойство start и свойство length, оба типа Int.
- Точка в трехмерной системе координат, возможно, инкапсулирующая свойства x, y и z, каждый из которых имеет тип Double.
Во всех остальных случаях определите класс и создайте экземпляры этого класса для управления и передачи по ссылке. На практике это означает, что большинство пользовательских конструкций данных должны быть классами, а не структурами.
Здесь утверждается, что мы должны по умолчанию использовать классы и использовать структуры только в определенных обстоятельствах. В конечном счете, вам необходимо понять реальное значение типов значений по сравнению с ссылочными типами, и тогда вы сможете принять обоснованное решение о том, когда использовать структуры или классы. Кроме того, имейте в виду, что эти концепции постоянно развиваются, и документация по Swift Programming Language была написана до того, как была сделана речь о протоколно-ориентированном программировании.
In practice, this means that most custom data constructs should be classes, not structures.
Можете ли вы объяснить мне, как, прочитав это, вы получите, что большинство наборов данных должны быть структурами, а не классами? Они дали определенный набор правил, когда что-то должно быть структурой, и в значительной степени сказали: «Во всех других сценариях класс лучше».
Поскольку экземпляры структуры размещаются в стеке, а экземпляры классов размещаются в куче, структуры иногда могут быть значительно быстрее.
Тем не менее, вы всегда должны измерить его самостоятельно и принять решение на основе вашего уникального варианта использования.
Рассмотрим следующий пример, который демонстрирует 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.159942142sstruct
версия заняла 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.788332333sstruct
версия заняла 0.010532942s (в 900 раз быстрее)СТАРЫЕ РЕЗУЛЬТАТЫ (из неизвестного времени)
(Запускается на структуру / класс с 1 полем, а не 10)
С выпуском сборки на моем MacBook Pro:
class
Версия приняла 1.10082 секstruct
Версия приняла 0.02324 сек ( в 50 раз быстрее)Я создал суть для этого на простых примерах. https://github.com/objc-swift/swift-classes-vs-structures
структуры не могут наследовать быстро. Если хочешь
class Vehicle{
}
class Car : Vehicle{
}
Пойти на урок
Структуры 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 вместо этого, это правильное выражение. Потому что в классе неизменяемой константой является ссылка на сам класс, а не на его переменные экземпляра (если только эти переменные не определены как константы)
Вот еще несколько причин для рассмотрения:
структуры получают автоматический инициализатор, который вам вообще не нужно поддерживать в коде.
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")
Чтобы получить это в классе, вы должны добавить инициализатор и поддерживать инициализатор ...
Основные типы коллекций, такие Array
как структуры. Чем больше вы используете их в своем собственном коде, тем больше вы привыкнете передавать по значению, а не по ссылке. Например:
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
Очевидно, что неизменность и изменчивость - огромная тема, но многие умные люди думают, что неизменность - структуры в этом случае - предпочтительнее. Изменчивые против неизменных объектов
internal
области.
mutating
чтобы вы четко указывали, какие функции изменяют свое состояние. Но их природа как ценностных типов - вот что важно. Если вы объявляете структуру, let
вы не можете вызывать какие-либо изменяющие функции для нее. Видео WWDC 15 о Лучшем программировании через типы значений является отличным ресурсом по этому вопросу.
Предполагая, что мы знаем, что 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, но идея та же
Кроме того, не используйте класс, когда вам нужно переопределить каждый экземпляр функции, то есть они не имеют общей функциональности.
Таким образом, вместо нескольких подклассов класса. Используйте несколько структур, которые соответствуют протоколу.
Другой разумный аргумент для структур - это когда вы хотите выполнить различие между старой и новой моделью. С типами ссылок вы не можете сделать это из коробки. С типами значений мутации не являются общими.
Некоторые преимущества:
Структура намного быстрее, чем класс. Кроме того, если вам нужно наследование, вы должны использовать класс. Наиболее важным моментом является то, что 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 является скорее передачей, чем ссылкой (тип значения).
Here we can see that the FlightB instance is not changed
Structs
есть value type
и Classes
естьreference type
Используйте value
тип, когда:
Используйте reference
тип, когда:
Дополнительную информацию можно также найти в документации Apple
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
Дополнительная информация
Типы значений Swift хранятся в стеке. В процессе каждый поток имеет свое собственное пространство стека, поэтому никакой другой поток не сможет напрямую получить доступ к вашему типу значения. Следовательно, нет условий гонки, блокировок, взаимоблокировок или какой-либо связанной сложности синхронизации потоков.
Типы значений не требуют динамического выделения памяти или подсчета ссылок, которые являются дорогостоящими операциями. При этом методы по типам значений рассылаются статически. Это создает огромное преимущество в пользу типов значений с точки зрения производительности.
В качестве напоминания вот список Swift
Типы значений:
Типы ссылок:
Отвечая на вопрос с точки зрения типов значений по сравнению со ссылочными типами, из этого поста в блоге Apple это будет выглядеть очень просто:
Используйте тип значения [например, struct, enum], когда:
- Сравнение данных экземпляра с == имеет смысл
- Вы хотите, чтобы копии имели независимое состояние
- Данные будут использоваться в коде в нескольких потоках.
Используйте ссылочный тип [например, класс], когда:
- Сравнение идентичности экземпляра с === имеет смысл
- Вы хотите создать общее, изменяемое состояние
Как упоминалось в этой статье, класс без доступных для записи свойств будет вести себя идентично со структурой, с (я добавлю) одним предупреждением: структуры лучше всего подходят для поточно-ориентированных моделей - все более неизбежное требование в современной архитектуре приложения.
С классами вы получаете наследование и передаете по ссылке, структуры не имеют наследования и передаются по значению.
На Swift есть отличные сессии WWDC, на этот конкретный вопрос подробно дан ответ в одном из них. Обязательно смотрите их, так как это поможет вам быстрее освоить руководство по языку или iBook.
Я бы не сказал, что структуры предлагают меньше функциональности.
Конечно, «я» является неизменным, кроме как в мутирующей функции, но это все.
Наследование работает хорошо, пока вы придерживаетесь старой доброй идеи, что каждый класс должен быть абстрактным или окончательным.
Реализуйте абстрактные классы как протоколы, а финальные классы как структуры.
Хорошая вещь о структурах состоит в том, что вы можете сделать ваши поля изменяемыми, не создавая общее изменяемое состояние, потому что копирование при записи позаботится об этом :)
Вот почему все свойства / поля в следующем примере являются изменяемыми, чего я бы не делал в классах 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)")
}
В Swift был введен новый шаблон программирования, известный как протоколно-ориентированное программирование.
Творческий Образец:
В Swift Struct представляет собой типы значений, которые автоматически клонируются. Поэтому мы получаем необходимое поведение для реализации шаблона прототипа бесплатно.
Принимая во внимание, что классы являются ссылочным типом, который не клонируется автоматически во время присваивания. Чтобы реализовать шаблон прототипа, классы должны принять NSCopying
протокол.
Мелкая копия дублирует только ссылку, которая указывает на эти объекты, тогда как глубокая копия дублирует ссылку на объект.
Реализация глубокого копирования для каждого ссылочного типа стала утомительной задачей. Если классы включают дополнительный ссылочный тип, мы должны реализовать шаблон прототипа для каждого из свойств ссылок. И затем мы должны фактически скопировать весь граф объекта путем реализации NSCopying
протокола.
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
Используя структуры и перечисления , мы упростили наш код, поскольку нам не нужно реализовывать логику копирования.
Многие API-интерфейсы Какао требуют подклассов NSObject, что заставляет вас использовать класс. Но кроме этого, вы можете использовать следующие случаи из блога Apple Swift, чтобы решить, использовать ли тип значения struct / enum или ссылочный тип класса.
Один момент, на который не обращают внимания в этих ответах, заключается в том, что переменная, содержащая класс и структуру, может еще некоторое let
время разрешать изменения свойств объекта, в то время как вы не можете сделать это с помощью структуры.
Это полезно, если вы не хотите, чтобы переменная когда-либо указывала на другой объект, но все же нужно изменить объект, то есть в случае наличия множества переменных экземпляра, которые вы хотите обновить одну за другой. Если это структура, вы должны разрешить сброс переменной в другой объект в целом, используя var
для этого, поскольку тип постоянного значения в Swift должным образом допускает нулевую мутацию, в то время как ссылочные типы (классы) не ведут себя таким образом.
Поскольку структура является типом значений, вы можете очень легко создать память, которая хранится в стеке. Struct может быть легко доступен, и после объема работы он легко освобождается из памяти стека посредством всплывающего сообщения с вершины стека. С другой стороны, класс является ссылочным типом, который хранится в куче, и изменения, внесенные в один объект класса, будут влиять на другой объект, так как они тесно связаны и ссылаются на тип. Все члены структуры являются открытыми, тогда как все члены класса являются частными ,
Недостатки структуры в том, что она не может быть унаследована.
Структура и класс являются определяемыми пользователем типами данных
По умолчанию структура является общедоступной, тогда как класс является частным
Класс реализует принцип инкапсуляции
Объекты класса создаются в памяти кучи
Класс используется для повторного использования, в то время как структура используется для группировки данных в одной структуре.
Элементы данных структуры не могут быть инициализированы напрямую, но они могут быть назначены извне структуры
Члены класса данных могут быть инициализированы непосредственно конструктором без параметров и назначены параметризованным конструктором