Всем привет!

Рассмотрим 5 вопросов, которые вам могут задать на собеседовании на позицию iOS разработчика. Скорее всего, это вопросы уровня Junior, но т.к. сейчас непонятно кто, что и у кого спрашивает, поэтому не будем разводить холивар :)

Вопрос №1: Можно ли создать необязательные методы в протоколах?

Ответ: Существует два способа создания необязательных методов для протоколов.

  1. Использование ключевого слова optional.

@objc protocol NameProtocol {
    @objc optional func nameMethod()
}

class NameClass: NameProtocol {
}

Плюсы:
- Не нужно указывать реализацию по умолчанию

Минусы:
- Использование ключевого слова optional доступно только для @objc protocol. Это означает, что протоколу могут соответствовать только классы, унаследованные от NSObject. Структуры и перечисления не могут соответствовать протоколу.
- Перед вызовом необходимо проверять реализован ли данный метод.

  1. Реализация по умолчанию

protocol NameProtocol {
    var nameProperty: String { get set }
}

extension NameProtocol {
    func nameMethod() {
        print("default implementation")
    }
}

class NameClass: NameProtocol {
    var nameProperty: String = "name"
}

Плюсы:
- Протоколу могут соответствовать классы, структуры, перечисления.
- Возможность использования Generics.
- Уверенность в том, что существует или собственная реализация метода или реализация по умолчанию.

Минусы:
- Не всегда существует возможность написать универсальную реализация по умолчанию.
- Невозможно отличить реализацию по умолчанию от ее отсутствия.

Вопрос №2: Чем static отличается от class?

Ответ: static и class достаточно похожи, но существуют различия.

Сходство:
- static и class делают свойство/метод, к которому они были применены, свойством/методом типа. Вызывать такое свойство/метод можно напрямую, без создания экземпляра.

class Car {
    static let wheels = 4
}
Car.wheels

Различие:
- class может применяться только к вычисляемым свойствам
- class может применяться только к свойства и методам класса
- class позволяет переопределять свойство/метод

class Car {
    class var wheels: Int {
        4
    }
}

class Mercedes: Car {
    override class var wheels: Int {
        3
    }
}

Вопрос №3: Могут ли ленивые вычисляемые свойства вычисляться больше одного раза?

Ответ: Нет

lazy var - переменная, которая инициализируется только при первом доступе.
Код инициализации выполняется только один раз. Результат сохраняется в переменной.
При последующем обращении будет возвращено сохраненное значение.

class NameClass {
    lazy var lazyProperty: Int = {
        print("lazyProperty")
        return 0
    }()
}

let instance = NameClass()
instance.lazyProperty
instance.lazyProperty
instance.lazyProperty

//Консоль
//lazyProperty

Сообщение "lazyProperty" выведется в консоль только один раз, при инициализации lazyProperty. При последующих обращениях возвращается сохраненное значение.

Более интересный пример:

class NameClass {
    var a: Int
    var b: Int
  
    lazy var lazyProperty: Int = {
        a + b
    }()
  
    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
}

let instance = NameClass(a: 10, b: 2)
instance.lazyProperty
instance.a = 20
instance.b = 15
print(instance.lazyProperty)

//Консоль
//12

В консоль выведется значение 12, рассчитанное и сохраненное при первом обращении.

Вопрос №4: Почему нельзя вызвать memberwise initializer, если он содержит хотя бы одно свойство с уровнем private?

Ответ: При запуске кода, расположенного ниже, возникнет ошибка.

struct NameStruct {
    private let first: Int
}

let nameStruct = NameStruct(first: 1)

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

Такое поведение связано с тем, что memberwise инициализатор устанавливает значения напрямую. Т.к. свойство имеет уровень доступа private, становится невозможным установить значение из вне. Инициализатору присваивается уровень доступа private.

Вопрос №5: Почему классы не обладают memberwise инициализатором как структуры?

Ответ: При ответе на данный вопрос, предлагаю сослаться на доводы Криса Латтнера.

  1. При реализации собственного инициализатора memberwise инициализатор пропадает. Нет простого способа вернуть его.

Пример со структурой:

struct NameStruct {
    let first: String
    let second: String

    init(first: String) {
        self.first = first
        self.second = "two"
    }
}

let nameStruct = NameStruct(first: "1", second: "2") //error
  1. Контроль доступа. Для свойств с уровнем доступа private memberwise инициализатор требует установить значение по умолчанию. Если же хотя бы один из членов инициализатора имеет уровень доступа private, инициализатор также будет иметь уровень доступа private и недоступен для использования. (Вопрос №4)

  2. memberwise инициализатор должен уметь устанавливать значение по умолчанию для переменных.

  3. memberwise инициализатор захватывает ленивые свойства (lazy var)

Комментарии (7)


  1. Bardakan
    20.08.2024 04:59
    +2

    Могут ли ленивые вычисляемые свойства вычисляться больше одного раза?

    могут:

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties/

    If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property hasn’t yet been initialized, there’s no guarantee that the property will be initialized only once.


    1. yaSkazalGorbatiy Автор
      20.08.2024 04:59

      Ценное замечание. Благодарю
      + цитата из WWDC2016: "lazy properties of your classes aren't atomic either."


      1. spiceginger
        20.08.2024 04:59

        Что, на мой личный взгляд, показывает насколько бессмысленно задавать подобные каверзные синтаксические вопросы на собеседованиях.


        1. yaSkazalGorbatiy Автор
          20.08.2024 04:59
          +1

          В целом с вами согласен
          На собеседованиях частенько задаются бесполезные вопросы, которые никогда не пригодятся в работе :) Такова реальность


          1. spiceginger
            20.08.2024 04:59

            Главное не плодить глупости и не повторять этот опыт самим когда собеседцем кого то


  1. Bardakan
    20.08.2024 04:59

    Во втором примере есть еще один способ объявить реализацию по умолчанию - когда в самом протоколе (не только extension) есть func nameMethod() . Разница будет видна в таком коде:

    let test: NameProtocol = NameClass()
    test.nameMethod()


    1. yaSkazalGorbatiy Автор
      20.08.2024 04:59

      Отличный способ перейти к диспетчеризации :)