Введення ТІ Ключові слова були введені в Swift 5.1, коли ключове слово було введено в Swift 5.6.Тепер ці ключові слова можуть бути використані в параметрах функції і перед визначенням типу, наступним чином: some any protocol P {} struct S: P {} // 'any P' is an explicit existential type. let p: any P = S() // 'some P' is an opaque type. let p: some P = S() func f(_ p: any P) {} func f(_ p: some P) {} У п'ятій частині, на Починаючи з Swift 6, екзистенційні типи повинні бути прямо написані з Ключове слово any any Непереконливий тип допомагає описати очікуваний тип повернення без визначення конкретного типу.Таким чином, компілятор може отримати доступ до фактичної інформації про тип і потенційно може виконувати оптимізації в цьому контексті. Екзистенціальний тип може зберігати будь-яке значення, що відповідає протоколу, а тип значення може змінюватися динамічно, вимагаючи динамічного розподілу пам'яті. Розуміння деяких ключових слів Неперервний тип результату - це невід'ємний загальний місць, задоволений реалізацією, тому ви можете подумати про це: some protocol P {} struct S1 : P {} func f() -> some P { return S1() } Ключовим аспектом тут є те, що функція, яка виробляє тип конкретно повертає значення унікального, конкретного типу, що дотримується Якщо функція намагається повернути різні типи конфігурації, це призведе до помилки компілятора, оскільки невідповідний загальний місць не може бути задоволений декількома типами. P P struct F1: P {} struct F2: P {} // error: Function declares an opaque return type, but the return // statements in its body do not have matching underlying types. func f(_ value: Bool) -> some P { if value { return F1() } else { return F2() } } Розглянемо переваги, які пропонують непрозорі типи над типами повернення протоколу. Opaque result types can be used with PATs. The main limitation of using protocols is that protocols with associated types cannot be used as actual types. This means that the following code doesn’t compile: func collection() -> Collection { return ["1", "2", "3"] } As for opaque types, they are merely generic placeholders that can be used in such scenarios: // protocol Collection<Element> : Sequence func collection() -> some Collection { return ["1", "2", "3"] } Opaque result types have identity. Because opaque types guarantee that only one type will be returned, the compiler knows that a function must return the same type on several calls: func method() -> some Equatable { return "method" } let x = method() let y = method() print(x == y) // true Opaque result types compose with generic placeholders. Contrary to conventional protocol-typed values, opaque result types integrate effectively with standard generic placeholders. For instance: protocol P { var message: String { get } } struct M: P { var message: String } func makeM() -> some P { return M(message: "message") } func bar<T: P, U: P>(_ p1: T, _ p2: U) -> Bool { return p1.message == p2.message } let m1 = makeM() let m2 = makeM() print(bar(m1, m2)) However, it doesn’t work if make returns different types based on protocol . M() P protocol P { var message: String { get } } struct M: P { var message: String } struct T: P { var message: String } // error: function declares an opaque return type 'some P', but the return statements in its body do not have matching underlying types func makeM() -> some P { if .random() { return M(message: "M message") } else { return T(message: "T message") } } Розуміння будь-якого ключового слова Розглянемо наступний приклад: припустимо, що ми маємо протоколу та двох конкретних реалізацій цього протоколу. Drawable protocol Drawable { func draw() } struct Line: Drawable { let x1: Int let y1: Int let x2: Int let y2: Int func draw() { print("Draw Line") } } struct Point: Drawable { let x: Int let y: Int func draw() { print("Point") } } Ми маємо два структурні об'єкти, та Створюємо змінну і зберігаємо а Об’єкт : Line Point Drawable var p1: any Drawable = Line(x1: 0, y1: 0, x2: 5, y2: 5) // 'any Drawable' is an explicit existential type p1.draw() // print "Draw Line" p1 = Point(x: 0, y: 0) p1.draw() // print "Point" Ми можемо перемикатися між різними реалізаціями під час роботи. Розглянемо інший приклад: let array: [any Drawable] = [ Line(x1: 0, y1: 0, x2: 5, y2: 5), Line(x1: 0, y1: 0, x2: 5, y2: 5), Point(x: 0, y: 0) ] Як ми знаємо, досягнення випадкового доступу до будь-якого елемента в матриці в постійному часі можливо завдяки тому, що кожен елемент має однаковий розмір пам'яті.У нашому прикладі, де ми зберігаємо елементи різного розміру, ви можете запитати, як ми можемо отримати елемент з матриці в такому сценарії. print(MemoryLayout<Line>.size) // 32 print(MemoryLayout<Point>.size) // 16 Це можливо через те, що Екзистенціальний контейнер включає п'ять машинних слів, виділяючи три для зберігання об'єкта або вказівки до об'єкта, один для вказівки до віртуальної таблиці, а інший для вказівки до таблиці свідків. any Екзистенціальний контейнер займає 5 машинних слів (в x64-бітній системі, 5 * 8 = 40): Value buffer is a space for the instance VWT is a pointer to Value Witness Table PWT is a pointer to the Protocol Witness Table Коли в коді використовується значення протоколу, компілятор генерує коробку, яку ми називаємо екзистенційним контейнером. Одна коробка для однієї вартості. Оскільки вони можуть зберігати будь-яке значення, тип якого відповідає протоколу, і тип збереженої вартості може змінюватися динамічно, екзистенційний контейнер вимагає динамічного розподілу пам'яті, якщо значення не досить мале, щоб вписатися в буфер з 3 машинних слів. На додаток до нагромадження і референтного обчислення, кожне використання екзистенціального контейнера включає в себе непрямі вказівки та динамічне відправлення, які не можуть бути оптимізовані. Три слова використовуються або для вкладення значення, якщо воно відповідає трьом машинним словам, або, якщо значення більше трьох машинних слів, створюється коробка з управлінням ARC. Потім значення копіюється в коробку, а покажчик до коробки з управлінням ARC копіюється в перше слово контейнера. Решта два слова не використовуються. Наступні слова використовуються для вказівки на таблицю свідків вартості (VWT) та таблицю свідків протоколу (PWT), відповідно. Таблиця свідків протоколу - це матриця, яка містить один запис для кожного протоколу, статично пов'язаного з екзистенційним типом. Поки у нас є таблиця свідків протоколу (PWT), яка описує дотримання типу протоколу, ми можемо створити екзистенційний контейнер, передати його і використовувати його для динамічного відправлення. P & Q Отже, у нас є екзистенціальний контейнер, і у нас є таблиця свідків протоколу. Однак, ми не знаємо, де ця цінність повинна зберігатися в екзистенційному контейнері — чи вона зберігається безпосередньо в контейнері, або якщо у нас є лише посилання на значення в штабі. Щоб відповісти на ці питання, ми звертаємося до допоміжної таблиці під назвою таблиця свідків вартості (VWT). , , , Кожен тип має таку таблицю, і він бере на себе відповідальність за створення інстанції типу, вирішуючи, де варто зберігати значення - в стеку або в стеку і т.д. allocate copy destruct deallocate Тепер у нас є контейнер фіксованого розміру, який вирішує питання проходження параметрів неоднорідних розмірів, і спосіб переміщення цього контейнера. Ну, принаймні, ми хочемо бути в змозі викликати необхідних членів збереженої вартості в контейнері, як це зазначено в протоколі (ініціалізатори, властивості - зберігаються чи обчислюються, функції і підписки). Однак кожен відповідний тип може реалізувати ці члени по-різному. Наприклад, деякі можуть задовольнити вимогу методу, безпосередньо визначаючи його, в той час як інші, скажімо, клас, можуть задовольнити його, успадкуючи метод від суперкласу. Обробка всієї цієї різноманітності є основною метою таблиці свідків протоколу (PWT). Існує саме одна така таблиця для кожної відповідності протоколу. Хоча функції можуть розташовуватися в різних місцях, місце розташування PWT є унікальним для протоколу, якому він відповідає.В результаті виклик може перевірити PWT контейнера, використовувати його для локалізації реалізації методів протоколу і викликати їх. Важливо знати, що екзистенціальні об'єкти є відносно дорогими для використання, тому що компілятор і час роботи не можуть заздалегідь визначити, скільки пам'яті слід виділити для конкретного об'єкта, який заповнить екзистенційний. Важливо знати, що екзистенціальні об'єкти є відносно дорогими для використання, тому що компілятор і час роботи не можуть заздалегідь визначити, скільки пам'яті слід виділити для конкретного об'єкта, який заповнить екзистенційний. Резюме Ми розглянули відмінності між та З одного боку, він значно покращив синтаксис і читання нашого загального коду; з іншого боку, він запровадив нові шляхи для нас, щоб створити загальний код більш ефективним чином. some any Дякую за читання.