Irbisa · Шпаргалка

Основы Swift

Переменные, optional, типы, control flow, замыкания, value vs reference

Junior-разработчик · 25 вопросов

  1. 01

    В чём разница между let и var в Swift?

    Константе let значение присваивается ровно один раз, а переменную var можно переприсваивать сколько угодно; по умолчанию пишут let и переходят на var, только когда значение действительно должно меняться.

    let name = "Ada"// name = "Grace"          // error: cannot assign to a let constant var attempts = 0attempts += 1              // fine…
  2. 02

    Что такое Optional в Swift, как он устроен и как его безопасно развернуть?

    Optional либо хранит значение, либо хранит nil, поэтому «значения может не быть» проверяет компилятор, а не падение в рантайме; T? служит сокращением для обобщённого enum Optional<T> с кейсами .some(T) и .none.

    var nickname: String? = "Ada" if let nickname {  print(nickname.uppercased())     // runs only when there is a value}…
  3. 03

    В чём разница между if let и guard let, и когда стоит использовать guard?

    Оба разворачивают optional, но if let держит значение внутри своих фигурных скобок, а guard let делает его доступным до конца области видимости и требует, чтобы ветка else из этой области вышла.

    struct User { let name: String; let isActive: Bool } func greet(_ user: User?) {  guard let user, user.isActive else {    print("nothing to greet")    return                      // else has to leave the scope…
  4. 04

    Что выведет этот код и почему в первой строке появляется Optional("Ada")?

    let name: String? = "Ada"let bonus: Int? = 5 print("Hello, \(name)")print("Hello, \(name ?? "guest")")print(bonus ?? 0 + 1)

    Код выведет Hello, Optional("Ada"), затем Hello, Ada, затем 5: интерполяция optional печатает описание обёртки Optional, а ?? связывает слабее, чем +.

  5. 05

    Чем String? отличается от String! и почему @IBOutlet, объявленный как UILabel!, падает с «unexpectedly found nil»?

    String! обозначает implicitly unwrapped optional: внутри тот же Optional<String>, но компилятор вставляет force unwrap везде, где значение используется как неопциональное, поэтому чтение при nil падает точно так же, как !.

  6. 06

    В чём разница между типами-значениями и ссылочными типами в Swift?

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

    struct Point { var x: Int }let a = Point(x: 1)var b = a          // an independent copyb.x = 99print(a.x)         // 1…
  7. 07

    В чём разница между == и === в Swift?

    == спрашивает, равны ли два значения по содержимому, и приходит из протокола Equatable; === спрашивает, указывают ли две ссылки на один и тот же экземпляр класса.

    struct Point: Equatable { var x: Int }print(Point(x: 1) == Point(x: 1))   // true: same content final class Box {}let a = Box()let b = Box()…
  8. 08

    В чём разница между is, as, as? и as!?

    is проверяет тип значения в рантайме, as? пытается сделать даункаст и возвращает optional, as! делает даункаст принудительно и падает при неудаче, а простой as делает апкаст или бриджинг, безопасность которого компилятор может доказать.

    class Animal {}final class Dog: Animal { func bark() { print("woof") } } let pets: [Animal] = [Dog(), Animal()]for pet in pets {  print(pet is Dog)                       // true, then false…
  9. 09

    В чём разница между Any и AnyObject?

    Any может хранить значение любого типа, включая структуры, enum, optional и функции; AnyObject хранит только экземпляры классов, поэтому это ещё и ограничение для протоколов только для классов.

    protocol Delegate: AnyObject {}      // class-only protocol final class Screen {  weak var delegate: Delegate?     // weak needs a class-bound type}…
  10. 10

    В чём разница между Array, Set и Dictionary, и как выбрать нужную коллекцию?

    Выбирайте по задаче: Array хранит порядок и допускает дубликаты, Set хранит уникальные элементы с быстрым поиском, а Dictionary сопоставляет уникальные ключи значениям.

    var tags = ["swift", "ios", "swift"]tags.insert("ui", at: 0)              // O(n): shifts every element let unique = Set(tags)                // 3 elements, order unspecifiedprint(unique.contains("ios"))         // O(1) on average…
  11. 11

    В чём разница между map, compactMap и flatMap в Swift?

    map преобразует каждый элемент один к одному, compactMap преобразует и выбрасывает результаты nil, а flatMap превращает каждый элемент в последовательность и склеивает их все в один массив.

    let raw: [String?] = ["swift", nil, "ios"]let lengths = raw.compactMap { $0?.count }        // [5, 3]let withNils = raw.map { $0?.count }              // [Int?]: 5, nil, 3 let numbers = ["1", "two", "3"].compactMap(Int.init)   // [1, 3]let flat = [[1, 2], [3]].flatMap { $0 }               // [1, 2, 3]…
  12. 12

    Что умеет switch в Swift, чего не умеет switch в стиле C?

    switch в Swift сопоставляет паттерны, а не только целые числа, обязан быть исчерпывающим и никогда не проваливается в следующий кейс, если не написать fallthrough.

    enum Download {  case idle  case running(progress: Double)  case failed(retryIn: Int)}…
  13. 13

    Почему в Swift нельзя сложить Int и Double и чем отличаются Float, Double и CGFloat?

    Swift никогда не преобразует числовые типы неявно, поэтому Int плюс Double даёт ошибку компиляции, пока вы не напишете Double(count); Float 32-битный, Double 64-битный и используется по умолчанию, а CGFloat служит типом, в котором Core Graphics и UIKit хранят размеры.

    let count = 7let total = 10// let ratio = count / total * 100.0   // error: Int and Double do not mixlet ratio = Double(count) / Double(total) * 100   // 70.0 let half = 1 / 2           // 0: both literals default to Int…
  14. 14

    Что такое замыкание в Swift и это тип-значение или ссылочный тип?

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

    func makeCounter() -> () -> Int {  var count = 0  return {                 // captures count and keeps it alive    count += 1    return count  }…
  15. 15

    В чём разница между weak и unowned и когда в замыкании нужен [weak self]?

    Ни одна из них не удерживает объект: weak работает как optional, который становится nil, когда объект освобождён, а unowned неопционален и падает, если обратиться к нему после того, как объект исчез.

    final class ProfileViewModel {  var onUpdate: (() -> Void)?  var title = ""   func start() {    // self owns onUpdate: capturing self strongly would be a retain cycle…
  16. 16

    Как устроена обработка ошибок в Swift: throws, try, try?, try!, do/catch и Result?

    Функция, которая может завершиться ошибкой, помечается throws, каждый её вызов помечается try, а ошибка либо обрабатывается в do/catch, либо передаётся вызывающему, так что возможность сбоя видна прямо в сигнатуре.

    enum AuthError: Error {  case invalidCredentials  case throttled(retryAfter: Int)} struct User { let email: String }…
  17. 17

    Что выведет этот код и почему изменение элемента копии меняет и исходный массив?

    final class Item {  var title: String  init(title: String) { self.title = title }} var original = [Item(title: "A"), Item(title: "B")]var copy = originalcopy[0].title = "Changed"copy.append(Item(title: "C")) print(original[0].title)print(original.count)

    Код выведет Changed, а затем 2: сам массив скопировался, поэтому добавление в copy не трогает original, но оба массива хранят ссылки на одни и те же объекты Item.

  18. 18

    Найдите баг: нажатие «Добавить» на товар, который уже лежит в корзине, никогда не увеличивает его количество.

    struct CartItem {  let name: String  var quantity: Int} final class Cart {  private(set) var items: [CartItem] = []   func add(_ name: String) {    if var existing = items.first(where: { $0.name == name }) {      existing.quantity += 1    } else {      items.append(CartItem(name: name, quantity: 1))    }  }}

    if var existing = items.first(...) привязывает копию структуры, поэтому quantity += 1 меняет эту локальную копию, а в массиве остаётся старое значение: у каждого товара количество так и остаётся 1.

  19. 19

    Что выведет этот код и что меняет список захвата [value]?

    func demo() {  var value = 1  let captured = { print("captured:", value) }  let copied = { [value] in print("capture list:", value) }   value = 2  captured()  copied()} demo()

    Код выведет captured: 2, а затем capture list: 1: обычное замыкание захватывает саму переменную и читает её в момент вызова, а список захвата копирует значение в момент создания замыкания.

  20. 20

    Что выведет этот код и когда именно выполняется блок defer?

    func demo() {  var counter = 0  func next() -> Int {    defer { counter += 1 }    return counter  }  print(next(), counter)} func order() {  defer { print("A") }  defer { print("B") }  print("C")} demo()order()

    Код выведет 0 1, затем C, B, A: блок defer выполняется при выходе из своей области видимости, уже после того, как возвращаемое значение вычислено, а несколько блоков defer выполняются в обратном порядке.

  21. 21

    Что делает inout и чем это отличается от передачи экземпляра класса?

    Параметр inout работает по схеме copy-in, copy-out: функция получает изменяемую копию вашей переменной и записывает её обратно при возврате, поэтому вызывающий видит изменение; аргумент передаётся с &.

    func normalize(_ values: inout [Double]) {  guard let maxValue = values.max(), maxValue > 0 else { return }  for i in values.indices { values[i] /= maxValue }} var samples = [2.0, 4.0, 8.0]…
  22. 22

    Почему в Swift нельзя написать name[3] для String и что такое Substring?

    Character в Swift представляет собой графемный кластер, который может занимать несколько байт, поэтому нет способа за постоянное время перейти к четвёртому символу; у String намеренно нет целочисленного сабскрипта, и по строке перемещаются через String.Index.

    let s = "Cafe\u{301}"               // "Café": 5 scalars, 4 charactersprint(s.count)                       // 4print(s.unicodeScalars.count)        // 5print(s == "Café")                   // true // let c = s[3]                      // error: no Int subscript…
  23. 23

    let rest = items.dropFirst(), а затем rest[0] падает с «Index out of bounds», хотя rest не пустой. Почему?

    dropFirst возвращает ArraySlice, который сохраняет индексы исходного массива, поэтому его первый элемент лежит по индексу 1, а не 0, и rest[0] действительно выходит за границы.

  24. 24

    Три товара по 1.10 дают в чеке сумму 3.3000000000000003. Что пошло не так и в каком типе хранить деньги?

    Double хранит двоичное число с плавающей точкой, и у значений вроде 1.1 или 0.1 нет точного двоичного представления, поэтому каждая операция добавляет крошечную ошибку, которая всплывает при печати результата; храните деньги в целых минорных единицах или в Decimal.

    print(1.1 * 3)                           // 3.3000000000000003print(0.1 + 0.2 == 0.3)                  // falseprint(Int(4.35 * 100))                   // 434, not 435 let priceCents = 110let totalCents = priceCents * 3          // 330, exact…
  25. 25

    В чём разница между assert, precondition и fatalError и какие из них работают в Release-сборке?

    assert проверяется только в Debug-сборке, precondition проверяется и в Debug, и в Release, а fatalError останавливает приложение всегда; выбирайте по тому, можно ли продолжать работу в продакшене, если допущение нарушено.

    assert(index >= 0, "negative index")       // Debug only, not evaluated in Releaseprecondition(!items.isEmpty, "empty cart")  // Debug and Release func make() -> Int {  fatalError("unreachable")                 // returns Never, always traps}