Подтвердить что ты не робот

Зачем выбирать структуру над классом?

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

Рассмотрим следующий пример, демонстрирующий две стратегии обертывания типа данных 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 version заняла 4.17e-08 секунд (в 50 000 000 раз быстрее)

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

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


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

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

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

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


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

(Ran on struct/class с 1 полем, а не 10)

Начиная с версии Swift 1.2, Xcode 6.3.2, запускает выпуск на iPhone 5S, iOS 8.3, усредненный за 5 прогонов

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

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

(Ran on struct/class с 1 полем, а не 10)

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

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

Ответ 3

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

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

И отличия

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

Структуры

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

class Vehicle{
}

class Car : Vehicle{
}

Перейти для класса.

2. Pass By

Структуры 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.

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

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

А как насчет типов моделей? У вас может быть тип User, представляющий пользователя в вашей системе, или тип Crime, представляющий действие, предпринятое пользователем. Они довольно копируемые, поэтому они должны быть типами значений. Тем не менее, вы, вероятно, хотите, чтобы обновления 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, являются структурами, а не классами. Они копируются при назначении и при передаче их в качестве параметров. Это вполне разумный выбор, если копия дешевая, чего Свифт очень старается выполнить....

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

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

Ответ 6

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

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

Ответ 7

Структура намного быстрее, чем класс. Кроме того, если вам нужно наследование, вы должны использовать класс. Наиболее важным моментом является то, что 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 проходит, а не ссылается (тип значения).

Ответ 8

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
  • Функция
  • закрытие

Ответ 9

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

Use a value type [e.g. struct, enum] when:

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

Use a reference type [e.g. class] when:

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

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

Ответ 10

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

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

Ответ 11

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

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

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

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

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

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

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

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

Ответ 12

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

Шаблон создания:

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

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


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


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

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

class Address{
   var street:String
   ...
} 

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

Ответ 13

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

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

Ответ 14

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

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

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

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

Ответ 15

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

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

Ответ 16

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

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

Ответ 18

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

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

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

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

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

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

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