Irbisa · Шпаргалка

Swift: дженерики и продвинутое

Generics, opaque types, property wrappers, result builders, key paths

Middle-разработчик · 24 вопроса

  1. 01

    Что делает Codable и как декодировать JSON, ключи которого не совпадают с именами свойств?

    Codable означает Encodable & Decodable: объявите соответствие, и компилятор сгенерирует код, который по именам переводит хранимые свойства в JSON (или другой формат) и обратно.

    import Foundation struct Article: Codable {  let id: Int  let title: String  let authorName: String…
  2. 02

    Что такое generics в Swift и что может выразить where, чего не могут угловые скобки?

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

    // Generic function: one implementation for every Comparable typefunc clamp<T: Comparable>(_ value: T, to range: ClosedRange<T>) -> T {  min(max(value, range.lowerBound), range.upperBound)}print(clamp(15, to: 0...10))     // 10 (T == Int)print(clamp(0.5, to: 0...1))     // 0.5 (T == Double)…
  3. 03

    Что такое associated type, какие ограничения он накладывает на протокол и что меняют primary associated types?

    Под associated type понимают тип-заполнитель внутри протокола, который каждый соответствующий тип подставляет сам, как Element в Collection.

    protocol Store<Item> {                  // Item is the primary associated type  associatedtype Item: Identifiable  func load(id: Item.ID) async throws -> Item  func save(_ item: Item) async throws}…
  4. 04

    Какая строка этого кода не компилируется и при чём здесь conditional conformance?

    struct Box<Value> {  let value: Value}extension Box: Equatable where Value: Equatable {} struct Token { let raw: String } print(Box(value: 1) == Box(value: 1))print([Box(value: "a")] == [Box(value: "a")])print(Box(value: Token(raw: "a")) == Box(value: Token(raw: "a")))

    Третий print.

  5. 05

    Чем отличаются some и any в Swift и как они связаны с generics?

    some Shape обозначает непрозрачный тип (opaque type): один конкретный тип, который компилятор знает, но скрывает от вызывающего.

    protocol Shape { func area() -> Double }struct Circle: Shape { let r: Double; func area() -> Double { .pi * r * r } }struct Square: Shape { let side: Double; func area() -> Double { side * side } } // some in a return: one hidden concrete type, chosen by the functionfunc unitShape() -> some Shape { Circle(r: 1) }…
  6. 06

    Эта функция не компилируется. Почему и как это исправить?

    protocol Shape { func area() -> Double }struct Circle: Shape { let r: Double; func area() -> Double { .pi * r * r } }struct Square: Shape { let side: Double; func area() -> Double { side * side } } func makeShape(rounded: Bool) -> some Shape {  if rounded {    return Circle(r: 1)  }  return Square(side: 2)}

    some Shape обещает один конкретный тип на всю функцию, а два return возвращают Circle и Square.

  7. 07

    Что означает @escaping и почему параметры-замыкания по умолчанию non-escaping?

    @escaping говорит, что замыкание может быть вызвано после возврата из функции (его сохраняют или передают в асинхронную работу).

    import Foundation final class ImageLoader {  private var handlers: [(Data) -> Void] = []   // Called before the function returns: non-escaping, no annotation needed…
  8. 08

    Что такое key paths в Swift и где их использовать?

    Key path представляет собой проверяемую компилятором ссылку на свойство, записываемую как \User.name.

    struct User {  var name: String  var age: Int} let users = [User(name: "Ada", age: 36), User(name: "Linus", age: 29)]…
  9. 09

    Как работает property wrapper и как написать свой?

    Property wrapper определяется как тип с атрибутом @propertyWrapper, который владеет хранилищем свойства: для @Clamped var volume компилятор хранит скрытое _volume и проводит каждое чтение и запись через его wrappedValue.

    @propertyWrapperstruct Clamped<Value: Comparable> {  private var value: Value  private let range: ClosedRange<Value>   init(wrappedValue: Value, _ range: ClosedRange<Value>) {…
  10. 10

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

    @propertyWrapperfinal class Locked<Value>: @unchecked Sendable {  private let lock = NSLock()  private var value: Value  init(wrappedValue: Value) { value = wrappedValue }   var wrappedValue: Value {    get { lock.withLock { value } }    set { lock.withLock { value = newValue } }  }} final class DownloadStats: @unchecked Sendable {  @Locked var finished = 0  func markFinished() { finished += 1 }  // called from many download tasks}

    finished += 1 выполняется не как одна операция под блокировкой: сначала вызывается геттер (lock, чтение, unlock), потом сеттер (lock, запись, unlock).

  11. 11

    Что такое result builder и почему if или for внутри своего билдера иногда не компилируется?

    Result builder представляет собой тип с атрибутом @resultBuilder, чьи статические методы build… компилятор вызывает, чтобы превратить инструкции замыкания в одно значение.

    struct Step { let title: String } @resultBuilderenum StepsBuilder {  static func buildExpression(_ step: Step) -> [Step] { [step] }        // each line  static func buildBlock(_ parts: [Step]...) -> [Step] { parts.flatMap { $0 } }…
  12. 12

    Чем Sequence отличается от Collection и как заставить свой тип работать в цикле for-in?

    Sequence обещает только, что по элементам можно пройти итератором, возможно, лишь один раз.

    // Sequence: an iterator with a mutating next() is all for-in needsstruct Countdown: Sequence {  let start: Int   func makeIterator() -> Iterator { Iterator(current: start) }…
  13. 13

    Чем отличаются #if, #available и @available в Swift?

    #if проверяет условие во время компиляции: код в ложной ветке вообще не попадает в бинарник.

    import Foundation#if canImport(UIKit)import UIKit                          // only where the SDK has UIKit#endif #if DEBUG…
  14. 14

    Как использовать Objective-C-код из Swift и Swift-код из Objective-C в одном таргете и что не может пересечь эту границу?

    Objective-C доступен в Swift через bridging header (в фреймворке через umbrella header).

    import Foundation // Visible to Objective-C through the generated "App-Swift.h"@objc class Logger: NSObject {  @objc func log(_ message: String) { print(message) }}…
  15. 15

    Что выведет этот код и почему map вызывается чаще, чем можно ожидать?

    let numbers = [1, 2, 3, 4, 5, 6]let squares = numbers.lazy  .map { n -> Int in    print("map \(n)")    return n * n  }  .filter { $0 % 2 == 0 } print("start")print(squares.first!)print(squares.first!)

    Выведет start, затем map 1, map 2, map 2, 4, и то же ещё раз для второго first.

  16. 16

    Что такое type erasure и когда нужен самописный type eraser, если есть any P<T>?

    Стирание типа (type erasure) прячет конкретный тип за обёрткой с фиксированным типом, чтобы разные типы, соответствующие одному протоколу, можно было хранить и передавать как один тип.

    protocol Repository<Item> {  associatedtype Item: Identifiable  func fetch(id: Item.ID) async throws -> Item} struct Order: Identifiable { let id: Int }…
  17. 17

    Как Swift выполняет generic-код и почему параметр any может быть медленнее generic-параметра?

    Оптимизатор может специализировать generic-функцию, создав копию под каждый встреченный конкретный тип, и тогда вызовы прямые и могут инлайниться.

  18. 18

    Что такое phantom type и от какой ошибки он защищает?

    Фантомным типом (phantom type) называют generic-параметр, который тип никогда не хранит; он нужен только для того, чтобы компилятор различал в остальном одинаковые значения.

    struct ID<Tag>: Hashable, Codable, Sendable {  let rawValue: String} enum UserTag {}      // never instantiated: exists only as a typeenum OrderTag {}…
  19. 19

    Что такое typed throws (throws(MyError)) и когда использовать их вместо обычного throws?

    Начиная со Swift 6 функция может объявить точный тип ошибки, throws(ParseError), и тогда catch получает ParseError, а не any Error, и вы можете разобрать его в switch исчерпывающе.

    enum ParseError: Error {  case empty  case notANumber(String)  case outOfRange(Int)}…
  20. 20

    Передача большого Array в Swift выглядит бесплатной. Как работает copy-on-write и как реализовать его для своего типа?

    Буфер общий для всех копий, пока кто-то не начнёт писать; перед записью проверка уникальности решает, нужно ли сначала клонировать буфер.

    final class Storage {                      // final and private to the struct  var pixels: [UInt8]  init(pixels: [UInt8]) { self.pixels = pixels }  func copy() -> Storage { Storage(pixels: pixels) }}…
  21. 21

    Найдите баг: очистка копии этого Playlist опустошает и оригинал.

    struct Playlist {  private final class Storage {    var ids: [Int]    init(ids: [Int]) { self.ids = ids }  }  private var storage = Storage(ids: [])  var ids: [Int] { storage.ids }   mutating func removeAll() { storage.ids.removeAll() }   mutating func append(_ id: Int) {    if !isKnownUniquelyReferenced(&storage) { storage = Storage(ids: storage.ids) }    storage.ids.append(id)  }   mutating func insert(_ id: Int, at index: Int) {    if !isKnownUniquelyReferenced(&storage) { storage = Storage(ids: storage.ids) }    storage.ids.insert(id, at: index)  }}

    removeAll() пишет в общий Storage без проверки уникальности.

  22. 22

    Как weak и unowned устроены в Swift изнутри (side table, три счётчика ссылок), и почему память объекта может пережить его deinit?

    Каждый объект Swift хранит счётчики strong и unowned прямо в заголовке, а первая weak-ссылка переносит счётчики в side table, на которую и указывают weak-ссылки.

    final class Session {  let name: String  init(name: String) { self.name = name }  deinit { print("deinit \(name)") }}…
  23. 23

    Нужна обёртка над сырым файловым дескриптором, которую нельзя скопировать, закрыть дважды или использовать после закрытия. Что даёт ~Copyable?

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

    import Darwin struct FileDescriptor: ~Copyable {  private let fd: Int32   init?(opening path: String) {…
  24. 24

    Когда писать макрос, а не property wrapper, и почему @Observable сделан макросом?

    Property wrapper меняет то, как читается и пишется одно хранимое свойство во время выполнения.

    import Foundationimport Observation @Observablefinal class CartModel {  var items: [String] = []…