Чому вибирають структуру над класом?


476

Граючи зі Свіфтом, що походить з тла Java, чому ви хочете вибрати Структуру замість класу? Здається, що це одне і те ж, а Struct пропонує менший функціонал. Навіщо вибирати саме тоді?


11
Структури завжди копіюються, коли їх передають у коді, і не використовують підрахунок посилань. джерело: developer.apple.com/library/prerelease/ios/documentation/swift/…
holex

4
Я б сказав, що структури більш придатні для зберігання даних, а не логіки. Щоб говорити на Java-мові, уявіть структури як "Ціннісні об'єкти".
Вінсент Гурчі

6
Я вражений в усьому цьому розмові немає прямої згадки про копії при записі аки ледачою копії . Будь-які занепокоєння щодо продуктивності копіювання структури здебільшого суперечать цій конструкції.
Девід Джеймс

3
Вибір структури над класом - це не питання думки. Існують конкретні причини вибору того чи іншого.
Девід Джеймс

Я настійно рекомендую ознайомитись, чому Array не є threadSafe . Це пов'язано, оскільки масиви та структури - це обидва типи значень. У всіх відповідях тут зазначається, що для типів структур / масивів / значень ніколи не буде проблеми з безпекою, але є кутовий випадок, в якому ви будете.
Мед

Відповіді:


548

Відповідно до дуже популярного протоколу програмування протоколів розмов WWDC 2015 у Swift ( відео , стенограма ), Swift надає ряд функцій, які роблять структури краще, ніж класи за багатьох обставин.

Структури є кращими, якщо вони відносно невеликі та копіюються, оскільки копіювання є набагато безпечнішим, ніж наявність кількох посилань на один і той же екземпляр, як це відбувається з класами. Це особливо важливо при переході змінної до багатьох класів та / або в багатопотоковому середовищі. Якщо ви завжди можете надсилати копію змінної в інші місця, вам ніколи не доведеться турбуватися про те, щоб інше місце змінило значення змінної під вами.

З "Структурами" набагато менше потрібно турбуватися про витоки пам'яті або перебіг декількох потоків для доступу / зміни одного екземпляра змінної. (Для більш технічно обґрунтованих виняток є при захопленні структури всередині закриття, оскільки тоді вона фактично фіксує посилання на екземпляр, якщо ви чітко не позначаєте його скопійованим).

Заняття також можуть бути роздутими, оскільки клас може успадковувати лише один клас. Це спонукає нас створити величезні суперкласи, які охоплюють безліч різних здібностей, лише пов’язаних між собою. Використання протоколів, особливо з розширеннями протоколів, де ви можете забезпечити реалізацію протоколів, дозволяє усунути потребу в класах для досягнення такого роду поведінки.

У бесіді викладаються такі сценарії, де переважні класи:

  • Копіювання або порівняння екземплярів не має сенсу (наприклад, Window)
  • Термін експлуатації примірника пов'язаний із зовнішніми ефектами (наприклад, тимчасовий файл)
  • Примірники - це просто "потоплення" - канали, що містять лише запис, до зовнішнього стану (наприклад, CGContext)

Це означає, що структури повинні бути типовими, а класи - резервними.

З іншого боку, документація з мови програмування Swift дещо суперечлива:

Екземпляри структури завжди передаються за значенням, а екземпляри класу завжди передаються посиланням. Це означає, що вони підходять для різних завдань. Розглядаючи конструкції даних та функціональні можливості, які вам потрібні для проекту, вирішіть, чи повинна кожна конструкція даних визначатися як клас або як структура.

Як загальне керівництво, розгляньте створення структури, коли застосовується одна або декілька з цих умов:

  • Основна мета структури - інкапсулювати декілька відносно простих значень даних.
  • Доцільно розраховувати, що інкапсульовані значення будуть скопійовані, а не посилання, коли ви призначаєте або передаєте екземпляр цієї структури.
  • Будь-які властивості, що зберігаються в структурі, самі є типовими значеннями, які, як очікується, будуть скопійовані, а не посилання.
  • Структурі не потрібно успадковувати властивості чи поведінку від іншого існуючого типу.

Приклади хороших кандидатів у структури включають:

  • Розмір геометричної форми, можливо, капсулюючи властивість ширини та властивості висоти, обидва типу Double.
  • Спосіб позначення діапазонів у серії, можливо інкапсулювання властивості початку та властивості довжини, обидва типу Int.
  • Точка в системі 3D координат, можливо, капсулюючи властивості x, y і z, кожен з типів Double.

У всіх інших випадках визначте клас та створіть екземпляри цього класу, якими слід керувати та передавати посилання. На практиці це означає, що більшість нестандартних конструкцій даних мають бути класами, а не структурами.

Тут стверджується, що нам слід за замовчуванням використовувати класи та використовувати структури лише в конкретних обставинах. Зрештою, вам потрібно зрозуміти реальне значення типів значень проти типів посилань, і тоді ви можете прийняти обгрунтоване рішення про те, коли використовувати структури або класи. Також майте на увазі, що ці поняття постійно розвиваються, і документація щодо мови швидкого програмування була написана до того, як було проведено розмову щодо програмування, орієнтованої на протокол.


12
@ElgsQianChen вся суть цього запису полягає в тому, що структуру слід вибирати за замовчуванням, а клас використовувати тільки тоді, коли це необхідно. Конструкції набагато безпечніші і не містять помилок, особливо в багатопотоковому середовищі. Так, ви завжди можете використовувати клас замість структури, але структури краще.
drewag

16
@drewag Це здається точно протилежним тому, що він говорить. Це говорило про те, що клас, який ви використовуєте, повинен бути типовим типом, а не структурою. In practice, this means that most custom data constructs should be classes, not structures.Чи можете ви пояснити мені, прочитавши це, ви зрозумієте, що більшість наборів даних мають бути структурами, а не класами? Вони дали специфічний набір правил, коли щось повинно бути структурою, і в значній мірі сказали "всі інші сценарії класу краще".
Метт

42
Останній рядок повинен сказати: "Моя особиста порада - це протилежність документації:" ... і тоді це чудова відповідь!
Дан Розенстарк

5
У книзі Swift 2.2 все ще йдеться про використання класів у більшості ситуацій.
Девід Джеймс

6
Структура над класом, безумовно, зменшує складність. Але те, що впливає на використання пам'яті, коли структури стає вибором за замовчуванням. Коли речі копіюються скрізь замість посилань, це повинно збільшити використання пам'яті додатком. Чи не повинен?
MadNik

164

Оскільки структури екземплярів розподіляються на стеці, а екземпляри класів розподіляються на купі, структури іноді можуть бути різко швидшими.

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

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

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

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

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

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

Продуктивність вимірюється за допомогою

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

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

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

    block()

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

Код можна знайти за посиланням https://github.com/knguyen2708/StructVsClassPerformance

ОНОВЛЕННЯ (27 березня 2018) :

Станом на Swift 4.0, Xcode 9.2, запущена версія випуску на iPhone 6S, iOS 11.2.6, налаштуванням компілятора Swift є -O -whole-module-optimization:

  • class версія зайняла 2,06 секунди
  • struct версія зайняла 4,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.159942142s
  • struct версія зайняла 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.788332333s
  • struct версія зайняла 0.010532942s (в 900 разів швидше)

СТАРІ РЕЗУЛЬТАТИ (з невідомого часу)

(Діє на структура / клас з 1 полем, а не 10)

З монтажем випуску на моєму MacBook Pro:

  • classВерсія прийняла 1.10082 сек
  • structВерсія прийняла 0.02324 сек ( в 50 разів швидше)

27
Щоправда, але, здається, що копіювання групи струмків було б повільніше, ніж копіювання посилання на один об'єкт. Іншими словами, швидше скопіювати один покажчик навколо, ніж скопіювати довільно великий блок пам'яті.
Tylerc230

14
-1 Цей тест не є гарним прикладом, оскільки на структурі є лише один вар. Зауважте, що якщо ви додасте кілька значень та об'єкт чи два, версія struct стає порівнянною з версією класу. Чим більше варіантів ви додаєте, тим повільніше виходить версія структури.
joshrl

6
@joshrl отримав ваше бачення, але приклад "хороший" чи не залежить від конкретної ситуації. Цей код було вилучено з мого власного додатка, тому це дійсний випадок використання, і використання структур значно покращило продуктивність мого додатка. Це, мабуть, не звичайний випадок використання (ну, звичайний випадок використання - для більшості додатків ніхто не переймається тим, як швидко можна передавати дані, оскільки вузьке місце трапляється десь в іншому місці, наприклад, підключення до мережі, так чи інакше, оптимізація не така. критично, коли у вас є пристрої з ГГц з ГБ або ОЗУ).
Хан Нгуен

26
Наскільки я розумію, швидке копіювання оптимізоване для WRITE часу. Що означає, що копія фізичної пам'яті не робиться, якщо нова копія не буде змінена.
Матьян

6
Ця відповідь демонструє надзвичайно тривіальний приклад, настільки, що він є нереальним та, таким чином, для багатьох випадків невірним. Кращою відповіддю було б "це залежить".
iwasrobbed

60

Подібність між структурами та класами.

Я створив для цього суть простими прикладами. https://github.com/objc-swift/swift-classes-vs-structures

І відмінності

1. Спадкування.

структури не можуть успадкувати швидко. Якщо хочете

class Vehicle{
}

class Car : Vehicle{
}

Ідіть на заняття.

2. Пройти повз

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

Контекстуальні відмінності

Структура константа та змінні

Приклад (використовується на 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, це правильний вираз. Тому що в класі незмінною константою є посилання на сам клас, а не на його змінні екземпляри (Якщо ті змінні не визначені як константи)


Ви можете успадкувати структури в Swift gist.github.com/AliSoftware/9e4946c8b6038572d678
thatguy

12
Вищенаведений зміст - про те, як ми можемо досягти спадкових смаків для структури. Ви побачите як синтаксис. A: B. Структура називається протоколом реалізації, що називається B. Документація Apple чітко зазначає, що структура не підтримує чистого успадкування, і це не так.
MadNik

2
людина, що останній твій абзац був чудовим. Я завжди знав, що ти можеш змінити константи ... але потім час від часу я бачив, де ти не можеш, тому я був приголомшений. Ця відмінність зробила це помітним
Мед

28

Ось деякі інші причини, які слід врахувати:

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

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

Щоб отримати це в класі, вам доведеться додати ініціалізатор та підтримувати програму ...

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

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. Мабуть, незмінність та незмінність - це величезна тема, але багато розумних людей вважають, що незмінність - структури в цьому випадку - є кращою. Змінні та незмінні об'єкти


4
Це правда, ви отримуєте автоматичні ініціалізатори. Ви також отримуєте порожній ініціалізатор, коли всі властивості необов’язкові. Але, якщо у вас є структура в Framework, вам потрібно насправді написати ініціалізатор самостійно, якщо ви хочете, щоб він був доступним за межами internalобласті.
Абізерн

2
@Abizern підтвердив - stackoverflow.com/a/26224873/8047 - і людини , який дратує.
Дан Розенстарк

2
@Abizern є великі причини для всього в Swift, але кожен раз, коли щось є правдою в одному місці, а не в іншому, розробник повинен знати більше матеріалів. Я думаю, ось де я повинен сказати: "Захоплююче працювати такою складною мовою!"
Дан Розенстарк

4
Чи можу я також додати, що не незмінність структур робить їх корисними (хоча це дуже гарна річ). Ви можете мутувати структури, але ви повинні позначити методи таким mutatingчином, щоб ви мали чітке уявлення про те, які функції змінюють їх стан. Але важливо їхня природа як ціннісні типи . Якщо ви декларуєте структуру, letви не можете викликати на ній жодних мутуючих функцій. Відео WWDC 15 про краще програмування за допомогою типів цінності є чудовим ресурсом для цього.
Абізерн

1
Дякую @Abizern, я ніколи не розумів цього, перш ніж читати твій коментар. Для об'єктів, нехай проти var не є великою різницею, але для структур це величезна кількість. Дякуємо, що вказали на це.
Дан Розенстарк

27

Якщо припустити, що ми знаємо, що 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, але ідея така ж

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

Тож замість того, щоб мати кілька підкласів класу. Використовуйте кілька структур, які відповідають протоколу.


Ще один розумний випадок конструкцій - це коли ви хочете зробити дельта / різницю вашої старої та нової моделі. Що стосується типів посилань, ви не можете цього робити поза межами. З типами значень мутації не поділяються.


1
Саме таке пояснення я шукав. Приємно напишіть :)
androCoder-BD

Дуже корисний приклад контролера
Запитайте П

1
@AskP Я надіслав електронною поштою Майк і отримав додатковий фрагмент коду :)
Мед,

18

Деякі переваги:

  • автоматично захищена нитками через те, що вона не може бути передана
  • використовує менше пам'яті через відсутність ISA та refcount (і фактично стек виділяється загалом)
  • методи завжди розсилаються статично, тому їх можна вводити (хоча @final може це зробити для класів)
  • простіше міркувати про (не потрібно "захисно копіювати", як це характерно для NSArray, NSString тощо) з тієї ж причини, що і безпека потоку

Не впевнений, чи не за межами цієї відповіді, але чи можете ви пояснити (чи посилання, я думаю), що "методи завжди статично розсилаються"?
Дан Розенстарк

2
Звичайно. Я також можу прикласти до нього застереження. Метою динамічної диспетчеризації є вибір реалізації, коли ви не знаєте наперед, яку саме використовувати. У Swift це може бути пов’язано з успадкуванням (можливо, це буде замінено в підкласі) або через функцію, яка є загальною (ви не знаєте, яким буде загальний параметр). Структури не можуть бути успадковані з цілої модульної оптимізації + загальної спеціалізації здебільшого виключає невідомі генерики, тому методи можна просто викликати безпосередньо, а не шукати, що викликати. Неспеціалізовані дженерики все ще роблять динамічне відправлення для
конструкцій,

1
Дякую, чудове пояснення. Тож ми очікуємо більшої швидкості виконання, або меншої двозначності з точки зору IDE, або обох?
Дан Розенстарк

1
Переважно колишній.
Catfish_Man

Лише зауважте, що методи не розсилаються статично, якщо ви посилаєте структуру через протокол.
Крістік

12

Структура набагато швидша, ніж 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 - це пропуск, а не посилання (тип значення).


2
ви ніколи не створювали примірника FLightB
David Seek

1
Тоді чому ти говориш про FlightB bro? Here we can see that the FlightB instance is not changed
Девід Шукайте

@ManojKarki, чудова відповідь. Просто хотів зазначити, що ви заявляли про політA двічі, коли я думаю, що ви хотіли оголосити FlightA, а потім FlightB.
ScottyBlades

11

Structsє value typeі Classesєreference type

  • Типи значень швидше, ніж еталонні типи
  • Екземпляри типу значення безпечні в багатопотоковому середовищі, оскільки кілька потоків можуть мутувати екземпляр, не турбуючись про перегонові умови або тупикові місця
  • Тип значення не має посилань на відміну від референсного типу; тому витоків пам'яті немає.

Використовуйте valueтип, коли:

  • Ви хочете, щоб копії мали незалежний стан, дані будуть використовуватися в коді в декількох потоках

Використовуйте referenceтип, коли:

  • Ви хочете створити спільний стан, що змінюється.

Додаткову інформацію можна знайти в документації Apple

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


Додаткова інформація

Типи значень швидкості зберігаються в стеку. У процесі кожного потоку є власний простір стеку, тому жоден інший потік не зможе отримати прямий доступ до вашого типу значень. Отже, відсутні умови перегонів, замки, тупики або будь-яка пов'язана з цим складність синхронізації потоків.

Типи значень не потребують динамічного розподілу пам'яті або підрахунку посилань, обидва це дорогі операції. При цьому методи на типи значень розсилаються статично. Вони створюють величезну перевагу на користь типів цінності з точки зору продуктивності.

Як нагадування, ось список Swift

Типи значення:

  • Структуру
  • Енум
  • Кортеж
  • Примітиви (Int, Double, Bool тощо)
  • Колекції (масив, рядки, словник, набір)

Довідкові типи:

  • Клас
  • Все, що надходить з NSObject
  • Функція
  • Закриття

5

Відповідаючи на питання з точки зору типів цінності порівняно з типовими типами, з цього повідомлення в блозі Apple, здавалося б, дуже просто:

Використовуйте тип значення [наприклад, структура, перерахунок], коли:

  • Порівнювати дані екземпляра з == має сенс
  • Ви хочете, щоб копії мали незалежний стан
  • Дані будуть використані в коді в декількох потоках

Використовуйте тип посилання [наприклад, клас], коли:

  • Порівнювати ідентифікатор примірника з === має сенс
  • Ви хочете створити спільний стан, що змінюється

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


3

За допомогою класів ви отримуєте спадщину і передаються шляхом посилання, структури не мають спадщини і передаються за значенням.

На Свіфті проходять чудові сесії WWDC, на це конкретне питання досить детально відповідають в одному з них. Переконайтеся, що ви стежите за ними, оскільки це дозволить вам набрати швидкість значно швидше, ніж посібник з мов або iBook.


Не могли б ви надати кілька посилань із згаданого вами? Тому що на WWDC можна вибрати декілька, я хотів би дивитись того, хто говорить на цю конкретну тему
MMachinegun

Для мене це хороший початок тут: github.com/raywenderlich/…
MMachinegun

2
Він, мабуть, говорить про цю чудову сесію: Програмно-орієнтоване програмування в Swift. (Посилання: відео , стенограма )
zekel

2

Я б не сказав, що структури пропонують меншу функціональність.

Звичайно, самозмінність є незмінною, за винятком функції, що мутує, але це стосується цього.

Успадкування працює добре, якщо ви дотримуєтесь старої доброї ідеї, що кожен клас повинен бути або абстрактним, або остаточним.

Реалізуйте абстрактні класи як протоколи та заключні класи як структури.

Приємна річ у структурах полягає в тому, що ви можете зробити свої поля зміненими, не створюючи загальний стан змін, тому що копія при записі переймається цим :)

Ось чому властивості / поля в наступному прикладі є змінними, що я б не робив у класах Java або C # або swift .

Приклад структури успадкування з трохи брудним та прямим використанням у нижній частині функції, що називається "example":

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

protocol Event
{
    var ts: Int64 { get set }

    func accept(visitor: EventVisitor)
}

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

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

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

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

    func accept(visitor: StatusEventVisitor)
}

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

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

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

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

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

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

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

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

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

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

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

    let visitor = Visitor()

    readEvent(1).accept(visitor)

    print("status: \(visitor.status)")
}

2

У Swift було введено нову схему програмування, відому як протокольне орієнтоване програмування.

Шаблон оголошення:

Швидко, Struct - це типи значень, які автоматично клонуються. Тому ми отримуємо необхідну поведінку, щоб безкоштовно реалізувати зразок прототипу.

Тоді як класи - це еталонний тип, який не клонується автоматично під час виконання завдання. Для реалізації шаблону прототипу класи повинні прийняти NSCopyingпротокол.


Дрібна копія копіює лише посилання, що вказує на ці об'єкти, тоді як глибока копія копіює посилання на об'єкт.


Реалізація глибокої копії для кожного типу довідки стала втомливим завданням. Якщо класи включають додатковий тип посилань, ми повинні реалізувати шаблон прототипу для кожного з властивостей посилань. І тоді ми повинні фактично скопіювати весь графік об’єкта, реалізуючи NSCopyingпротокол.

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

class Address{
   var street:String
   ...
} 

Використовуючи структури та перерахунки , ми спростили наш код, оскільки нам не потрібно реалізовувати логіку копіювання.


1

Для багатьох API какао потрібні підкласи NSObject, що змушує вас використовувати клас. Але крім цього, ви можете використовувати наступні випадки з блогу Swift Apple, щоб вирішити, чи використовувати тип структури / enum значення або тип посилання класу.

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


0

Один момент, що не звертає на себе уваги в цих відповідях, - це те, що змінна, що містить клас проти структури, може тривати letчас, все ж дозволяючи змінити властивості об'єкта, тоді як ви не можете це зробити із структурою.

Це корисно, якщо ви не хочете, щоб змінна колись вказувала на інший об'єкт, але все ж потрібно змінити об'єкт, тобто у випадку наявності багатьох змінних примірників, які ви хочете оновлювати одну за іншою. Якщо це структура, ви повинні дозволити скидженню змінної на інший об'єкт, використовуючи varдля того, щоб це зробити, оскільки тип постійного значення в Swift належним чином дозволяє нульову мутацію, в той час як еталонні типи (класи) не ведуть себе таким чином.


0

Як структури - це типи значень, і ви можете легко створити пам'ять, яка зберігається в стек. Структ може бути легкодоступним, а після обсягу роботи він легко розміщується з пам'яті стека через попс у верхній частині стека. З іншого боку, клас - це еталонний тип, який зберігається в купі, і зміни, внесені в одному об'єкті класу, впливатимуть на інший об'єкт, оскільки вони щільно пов'язані та референтного типу. Усі члени структури є загальнодоступними, тоді як усі члени класу є приватними .

Недоліками структури є те, що вона не може бути успадкована.


-7
  • Структура та клас - це типи даних, які не відповідають користувачеві

  • За замовчуванням структура є загальнодоступною, тоді як клас приватним

  • Клас реалізує принцип інкапсуляції

  • Об'єкти класу створюються в купі пам'яті

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

  • Учасники даних структури не можуть бути ініціалізовані безпосередньо, але вони можуть бути призначені зовні структурою

  • Учасники даних класу можуть бути ініціалізовані безпосередньо конструктором без параметру та присвоєні конструктором, що параметризується


2
Найгірша відповідь коли-небудь!
J. Doe

скопіювати відповідь вставити
jawadAli
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.