Граючи зі Свіфтом, що походить з тла Java, чому ви хочете вибрати Структуру замість класу? Здається, що це одне і те ж, а Struct пропонує менший функціонал. Навіщо вибирати саме тоді?
Граючи зі Свіфтом, що походить з тла Java, чому ви хочете вибрати Структуру замість класу? Здається, що це одне і те ж, а Struct пропонує менший функціонал. Навіщо вибирати саме тоді?
Відповіді:
Відповідно до дуже популярного протоколу програмування протоколів розмов WWDC 2015 у Swift ( відео , стенограма ), Swift надає ряд функцій, які роблять структури краще, ніж класи за багатьох обставин.
Структури є кращими, якщо вони відносно невеликі та копіюються, оскільки копіювання є набагато безпечнішим, ніж наявність кількох посилань на один і той же екземпляр, як це відбувається з класами. Це особливо важливо при переході змінної до багатьох класів та / або в багатопотоковому середовищі. Якщо ви завжди можете надсилати копію змінної в інші місця, вам ніколи не доведеться турбуватися про те, щоб інше місце змінило значення змінної під вами.
З "Структурами" набагато менше потрібно турбуватися про витоки пам'яті або перебіг декількох потоків для доступу / зміни одного екземпляра змінної. (Для більш технічно обґрунтованих виняток є при захопленні структури всередині закриття, оскільки тоді вона фактично фіксує посилання на екземпляр, якщо ви чітко не позначаєте його скопійованим).
Заняття також можуть бути роздутими, оскільки клас може успадковувати лише один клас. Це спонукає нас створити величезні суперкласи, які охоплюють безліч різних здібностей, лише пов’язаних між собою. Використання протоколів, особливо з розширеннями протоколів, де ви можете забезпечити реалізацію протоколів, дозволяє усунути потребу в класах для досягнення такого роду поведінки.
У бесіді викладаються такі сценарії, де переважні класи:
- Копіювання або порівняння екземплярів не має сенсу (наприклад, Window)
- Термін експлуатації примірника пов'язаний із зовнішніми ефектами (наприклад, тимчасовий файл)
- Примірники - це просто "потоплення" - канали, що містять лише запис, до зовнішнього стану (наприклад, CGContext)
Це означає, що структури повинні бути типовими, а класи - резервними.
З іншого боку, документація з мови програмування Swift дещо суперечлива:
Екземпляри структури завжди передаються за значенням, а екземпляри класу завжди передаються посиланням. Це означає, що вони підходять для різних завдань. Розглядаючи конструкції даних та функціональні можливості, які вам потрібні для проекту, вирішіть, чи повинна кожна конструкція даних визначатися як клас або як структура.
Як загальне керівництво, розгляньте створення структури, коли застосовується одна або декілька з цих умов:
- Основна мета структури - інкапсулювати декілька відносно простих значень даних.
- Доцільно розраховувати, що інкапсульовані значення будуть скопійовані, а не посилання, коли ви призначаєте або передаєте екземпляр цієї структури.
- Будь-які властивості, що зберігаються в структурі, самі є типовими значеннями, які, як очікується, будуть скопійовані, а не посилання.
- Структурі не потрібно успадковувати властивості чи поведінку від іншого існуючого типу.
Приклади хороших кандидатів у структури включають:
- Розмір геометричної форми, можливо, капсулюючи властивість ширини та властивості висоти, обидва типу Double.
- Спосіб позначення діапазонів у серії, можливо інкапсулювання властивості початку та властивості довжини, обидва типу Int.
- Точка в системі 3D координат, можливо, капсулюючи властивості x, y і z, кожен з типів Double.
У всіх інших випадках визначте клас та створіть екземпляри цього класу, якими слід керувати та передавати посилання. На практиці це означає, що більшість нестандартних конструкцій даних мають бути класами, а не структурами.
Тут стверджується, що нам слід за замовчуванням використовувати класи та використовувати структури лише в конкретних обставинах. Зрештою, вам потрібно зрозуміти реальне значення типів значень проти типів посилань, і тоді ви можете прийняти обгрунтоване рішення про те, коли використовувати структури або класи. Також майте на увазі, що ці поняття постійно розвиваються, і документація щодо мови швидкого програмування була написана до того, як було проведено розмову щодо програмування, орієнтованої на протокол.
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,17е-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-08 (швидкість в 37 000 000 разів)Зауважте : як хтось згадував, що в реальних сценаріях, ймовірно, буде більше 1 поля в структурі, я додав тести для структур / класів з 10 полями замість 1. Дивно, але результати не сильно відрізняються.
ОРИГІНАЛЬНІ РЕЗУЛЬТАТИ (1 червня 2014 р.):
(Діє на структура / клас з 1 полем, а не 10)
Станом на Swift 1.2, Xcode 6.3.2, запущена версія випуску на 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{
}
Ідіть на заняття.
Швидкі структури передаються за значенням, а екземпляри класів передаються за посиланням.
Структура константа та змінні
Приклад (використовується на 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 пар, копіюються. Вони повинні бути типовими значеннями. Контролер, який представляє диск, неможливо копіювати. Це має бути еталонний тип.
Деякі типи можна скопіювати, але це може бути не те, що ви хочете постійно. Це говорить про те, що вони повинні бути еталонними типами. Наприклад, концептуально можна скопіювати кнопку на екрані. Копія буде не зовсім ідентичною оригіналу. Клацання на копію не активує оригінал. Копія не буде займати те саме місце на екрані. Якщо ви передасте кнопку навколо або введете її в нову змінну, ви, ймовірно, захочете посилатися на оригінальну кнопку, і ви хочете зробити копію лише тоді, коли це буде чітко запитано. Це означає, що тип вашої кнопки повинен бути еталонним.
Контролери перегляду та вікон - подібний приклад. Вони можуть бути скопійовані, можливо, але це майже ніколи не хотілося б зробити. Вони повинні бути еталонними типами.
Що з типами моделей? У вас може бути тип користувача, який представляє користувача у вашій системі, або тип злочину, який представляє дії, здійснені користувачем. Вони досить копіюються, тому вони, мабуть, мають бути типовими значеннями. Однак, ймовірно, ви хочете, щоб оновлення злочину користувача, зроблені в одному місці вашої програми, були видимими для інших частин програми. Це говорить про те, що вашим Користувачам повинен керувати якийсь контролер користувача, який був би еталонним типом . напр
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. Крім того, якщо вам потрібна спадщина, ви повинні використовувати 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)
Тепер, якщо ми надрукуємо ідентифікатор і опис польотуA, ми отримаємо
id = 200
description = "second flight of Virgin Airlines"
Тут ми можемо побачити ідентифікатор і опис FlightA змінено, оскільки параметр, переданий методу модифікації, фактично вказує на адресу пам'яті об’єкта польотуA (тип посилання).
тепер, якщо ми друкуємо ідентифікатор та опис екземпляра 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
Типи значення:
Довідкові типи:
Відповідаючи на питання з точки зору типів цінності порівняно з типовими типами, з цього повідомлення в блозі Apple, здавалося б, дуже просто:
Використовуйте тип значення [наприклад, структура, перерахунок], коли:
- Порівнювати дані екземпляра з == має сенс
- Ви хочете, щоб копії мали незалежний стан
- Дані будуть використані в коді в декількох потоках
Використовуйте тип посилання [наприклад, клас], коли:
- Порівнювати ідентифікатор примірника з === має сенс
- Ви хочете створити спільний стан, що змінюється
Як уже згадувалося в цій статті, клас, який не має властивостей для запису, буде поводитись однаково зі структурою, з (додам) одним застереженням: структури найкращі для безпечних для потоків моделей - все більш неминуча вимога в сучасній архітектурі додатків.
За допомогою класів ви отримуєте спадщину і передаються шляхом посилання, структури не мають спадщини і передаються за значенням.
На Свіфті проходять чудові сесії 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 було введено нову схему програмування, відому як протокольне орієнтоване програмування.
Шаблон оголошення:
Швидко, Struct - це типи значень, які автоматично клонуються. Тому ми отримуємо необхідну поведінку, щоб безкоштовно реалізувати зразок прототипу.
Тоді як класи - це еталонний тип, який не клонується автоматично під час виконання завдання. Для реалізації шаблону прототипу класи повинні прийняти NSCopying
протокол.
Дрібна копія копіює лише посилання, що вказує на ці об'єкти, тоді як глибока копія копіює посилання на об'єкт.
Реалізація глибокої копії для кожного типу довідки стала втомливим завданням. Якщо класи включають додатковий тип посилань, ми повинні реалізувати шаблон прототипу для кожного з властивостей посилань. І тоді ми повинні фактично скопіювати весь графік об’єкта, реалізуючи NSCopying
протокол.
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
Використовуючи структури та перерахунки , ми спростили наш код, оскільки нам не потрібно реалізовувати логіку копіювання.
Для багатьох API какао потрібні підкласи NSObject, що змушує вас використовувати клас. Але крім цього, ви можете використовувати наступні випадки з блогу Swift Apple, щоб вирішити, чи використовувати тип структури / enum значення або тип посилання класу.
Один момент, що не звертає на себе уваги в цих відповідях, - це те, що змінна, що містить клас проти структури, може тривати let
час, все ж дозволяючи змінити властивості об'єкта, тоді як ви не можете це зробити із структурою.
Це корисно, якщо ви не хочете, щоб змінна колись вказувала на інший об'єкт, але все ж потрібно змінити об'єкт, тобто у випадку наявності багатьох змінних примірників, які ви хочете оновлювати одну за іншою. Якщо це структура, ви повинні дозволити скидженню змінної на інший об'єкт, використовуючи var
для того, щоб це зробити, оскільки тип постійного значення в Swift належним чином дозволяє нульову мутацію, в той час як еталонні типи (класи) не ведуть себе таким чином.
Як структури - це типи значень, і ви можете легко створити пам'ять, яка зберігається в стек. Структ може бути легкодоступним, а після обсягу роботи він легко розміщується з пам'яті стека через попс у верхній частині стека. З іншого боку, клас - це еталонний тип, який зберігається в купі, і зміни, внесені в одному об'єкті класу, впливатимуть на інший об'єкт, оскільки вони щільно пов'язані та референтного типу. Усі члени структури є загальнодоступними, тоді як усі члени класу є приватними .
Недоліками структури є те, що вона не може бути успадкована.
Структура та клас - це типи даних, які не відповідають користувачеві
За замовчуванням структура є загальнодоступною, тоді як клас приватним
Клас реалізує принцип інкапсуляції
Об'єкти класу створюються в купі пам'яті
Клас використовується для повторного використання, тоді як структура використовується для групування даних в одній структурі
Учасники даних структури не можуть бути ініціалізовані безпосередньо, але вони можуть бути призначені зовні структурою
Учасники даних класу можуть бути ініціалізовані безпосередньо конструктором без параметру та присвоєні конструктором, що параметризується