Почему Выберите Struct Над Классом?

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

15 ответов


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

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

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

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

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

  • копирование или сравнение экземпляров не имеет смысла (например, Window)
  • время жизни экземпляра привязано к внешним эффектам (например, TemporaryFile)
  • экземпляры-это просто "приемники" --каналы только для записи во внешнее состояние (e.г. CGContext)

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

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

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

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

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

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

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

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

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


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

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

рассмотрим следующий пример, который демонстрирует 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.17 e-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.159942142 s
  • struct версия заняла 5.83 E-08s (в 37.000.000 раз быстрее)

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


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

(выполняется на struct/class с 1 полем, а не 10)

по состоянию на Swift 1.2, Xcode 6.3.2, запуск выпуска сборки на iPhone 5S, iOS 8.3, в среднем за 5 запусков

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

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

(выполняется на struct/class с 1 полем, а не 10)

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

  • на class версия заняла 1.10082 сек
  • на struct версия заняла 0.02324 сек (в 50 раз быстрее)

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

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

и различия

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

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

class Vehicle{
}

class Car : Vehicle{
}

перейти на класс.

2. Проходите Мимо!--14-->

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

Различия

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

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

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

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

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

теперь, если я попытаюсь изменить x. Его допустимое выражение.

point.x = 5

но если я определил точку, как постоянное.

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

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

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


вот некоторые другие причины считать:

  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")
    

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

  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. по-видимому, неизменяемость против изменяемости-огромная тема, но многие умные люди считают, что неизменяемость-структуры в этом случае-предпочтительнее. изменяемые vs неизменяемые объекты


преимущества:

  • автоматически threadsafe из-за того, что не является общим
  • использует меньше памяти из-за отсутствия isa и refcount (и на самом деле стек выделяется вообще)
  • методы всегда статически отправляются, поэтому могут быть встроены (хотя @final может сделать это для классов)
  • легче рассуждать о (нет необходимости "оборонительно копировать", как это типично для NSArray, NSString и т. д...) по той же причине, что и безопасность резьбы

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

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

на основе сообщение mikeash:

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

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

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

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

коллекции интересный случай. Они включают такие вещи, как массивы и словари, и струны. Они копировать? Очевидно. Есть копирование чего-то, что вы хотите делать легко и часто? Это меньше четкий.

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

Swift сказал "Да", что означает, что такие типы, как Array и Dictionary и Строковые структуры, а не классы. Они копируются при назначении, и о передаче их в качестве параметров. Это вполне разумно. выбор пока копия дешевая, что Swift очень старается выполнять. ...

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

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


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

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)

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

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

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

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

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

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


ответ на вопрос с точки зрения типов значений и ссылочных типов, из это сообщение в блоге Apple казалось бы, очень просто:

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

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

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

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

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


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

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


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

конечно, self неизменен, за исключением мутирующей функции, но это все.

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

реализуйте абстрактные классы как протоколы и конечные классы как структуры.

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

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

пример структуры наследования с немного грязным и простым использованием внизу в функции с именем "пример":

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 был введен новый шаблон программирования, известный как Программирование, ориентированное на протокол.

Creational Pattern:

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

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


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


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

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

class Address{
   var street:String
   ...
} 

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


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

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


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

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


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

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


  • структура и класс-это пользовательские типы данных

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

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

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

  • класс используется для удобства использования re, тогда как структура используется для группировки данные в том же структура

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

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