Топ 3 необычных вопроса на собеседовании iOS разработчика

447879d1b255aa66f1bcae82776544b2

Когда ты устраиваешься на новую работу — собеседование является неотъемлемой частью твоего пути. Ты наверняка прочитал уже много разных ресурсов на эту тему и вопросом value vs reference type тебя не удивить. Если говорим про UI — тут я не буду затрагивать UIKit, эта тема хорошо уже разобрана сотни и тысячи раз, тут мы поговорим про SwiftUI. Ну не будем затягивать, что же интересного я для тебя приготовил…

«Как переписать метод класса родителя, который мы определили в extension?»

Такой вопрос я встретил на одном интервью и сначала он поверг меня в шок, ведь нам всегда говорили обратное, что сделать этого нельзя. Собственно так я и ответил, но интервьюер все же настоял на том, что такое возможно и надо подумать. Я попробовал порассуждать от того, почему же такой код не соберется…

class MyClass { }

extension MyClass {
    func foo() {
        print("1")
    }
}

class MySecodClass: MyClass {
    override func foo() {
        print("2")
    }
}

И тут я вспомнил ту самую тему из Swift, которую не все очень любят, но кто её знает хорошо — гораздо лучше начинает понимать тонкости языка — диспетчеризация. В extension у класса у нас статическая диспетчеризация, что и не дает нам переписывать этот метод где-то ещё, потому что мы однозначно определяем место, откуда будет браться реализация функции.

Как же нам это исправить — сменить метод диспетчеризации. Это можно сделать с помощью ключевого слова @objc. Тем самым мы изменим диспетчеризацию на message dispatch. А она нам позволяет изменять имплементацию метода.

class MyClass { }

extension MyClass {
    @objc func foo() {
        print("1")
    }
}

class MySecodClass: MyClass {
    override func foo() {
        print("2")
    }
}

«Что ты можешь рассказать о непрозрачных типах и чем они отличаются от generic типов?»

Этот вопрос стоит отнести к очень важным, если ты работаешь со SwiftUI. Поэтому наверное я отношу его на данный момент к необычным, так как SwiftUI все же редко спрашивают на данный момент. А там мы используем непрозрачные типы постоянно

struct MyView: View {
        var body: some View {
                 Text("Hello, World!")
             }
     }

Думаю эти строчки узнали все любители SwiftUI)) Что же они обозначают? Начнем с базового действия — прыгнем в определение View — что же это такое

public protocol View {

    /// The type of view representing the body of this view.
    ///
    /// When you create a custom view, Swift infers this type from your
    /// implementation of the required ``View/body-swift.property`` property.
    associatedtype Body : View

    /// The content and behavior of the view.
    ///
    /// When you implement a custom view, you must implement a computed
    /// `body` property to provide the content for your view. Return a view
    /// that's composed of built-in views that SwiftUI provides, plus other
    /// composite views that you've already defined:
    ///
    ///     struct MyView: View {
    ///         var body: some View {
    ///             Text("Hello, World!")
    ///         }
    ///     }
    ///
    /// For more information about composing views and a view hierarchy,
    /// see .
    @ViewBuilder @MainActor var body: Self.Body { get }
}

То есть это у нас «generic protocol», который обязан иметь свойство body тип Body, который в свою очередь обязан быть подписан под протокол View. Так, хорошо, то есть нам нужно реализовать это свойство. Для этого мы как раз и пишем var body: some View … Потому что нам важно, чтобы возвращало просто что-то подписанное под протокол View.

Теперь в чем же разница generic и ключевого слова some. Основная рекомендация такова — some лучше использовать тогда, когда мы хотим просто один раз обозначить, что объект на входе функции или где-то ещё должен быть подписан под протокол Equatable например (то есть мы единожды упоминаем что что-то подписано под такой-то протокол). То есть две функции ниже схожи, просто в первом случае мы работаем с объектом подписанным под протокол, а во втором случае с чем-то удовлетворяющем протоколу Equatable.

func generic(name: T) {
    print("name")
}

func generic(name: some Equatable) {
    print("name")
}

И тут можно ещё уточнить момент про однократное использование. Если мы захотим вернуть объект типа T, то нам достаточно будет дописать логичную запись → T. В случае some надо будет написать → some Equatable, что несколько удлиняет строку и читаемость записи. Особенно странно выглядит запись следующего вида…

func someFunc(name: T) -> some Equatable {
    return 1
}

Тут все же палка о двух концах, что такая запись валидная. Совмещение generic и some мне кажется более логично тогда, когда входной параметр должен быть подписан на два протокола, а выходной только на один к примеру.

Тут кто-то может сказать, что вместо some (в примере с функцией generic ()) мы можем написать и any, но вот такие записи уже не будут равносильны. Потому что у some есть одна особенность. Если мы в функцию с ключевым словом some на вход будем ожидать string и внутри функции будем взаимодействовать как со String, то some нам этого не даст сделать, что на самом деле логично. Потому что если мы ожидаем String, то в типе входного параметра можем указать String, а не some Equatable. А вот any в данном случае позволит нам это сделать, что на мой взгляд менее применимо на практике. То есть firstFunc () скомпилируется, а вот secondFunc () нет.

func firstFunc(name: any Equatable) {
    var temp = name
    temp = ""
}

func secondFunc(name: some Equatable) {
    var temp = name
    temp = ""
}

И на самом деле логика первого кода лично мне непонятна, поэтому запрет у some для такого случая лично для меня вполне логичен. Конечно есть случай, когда any стоит применить вместо some. Например обратимся к коду ниже

var firstMass: [some Equatable] = [1, 2, 3, "Name"]

var secondMass: [any Equatable] = [1, 2, 3, "Name"]

На firstMass наш компилятор поругается, потому что some подразумевает реализацию объектов одного типа, подписанных под протокол Equatable. В случае secondMass ключевое слово пропускает все объекты удовлетворяющие протоколу Equatable.

Наверное на этом моменте стоит остановиться и перейти дальше, потому что тут можно закопаться…

«Что будет напечатано в консоль?»

Понятное дело, что тут зависит от того, что вам показали на экране. Я видел много разных задач на разные темы. Думаю с gcd ты итак уже не один раз встретился и там просто дело практики. Я бы хотел сюда вставить задачу, которую давал сам на публичном мок собесе.

class MyClass: NSObject {
  
    var name: String
    
    override var description: String {
        return name
    }
  
    init(name: String) {
        self.name = name
    }
}

var firstMass: [MyClass] = [MyClass(name: "Andrey"), MyClass(name: "Viktor"), MyClass(name: "Lena")]
var secondMass = firstMass
firstMass.popLast()
firstMass.last?.name = "Ivan"
print(secondMass)

Тут надо понимать один момент, что массив — это value семантика. Это значит, что с самим массивом мы конечно работаем как с value типом, но в данном случае в строчке firstMass.last?.name = «Ivan» мы обратились не к массиву, а к объекту класса и поменяли по ссылке его значение. И ещё тут нужно не пропустить момент, что до этого в firstMass мы удалили последний элемент, следовательно мы обращаемся к Viktor и меняем его имя на Ivan. Но при этом количество элементов во втором массиве осталось равным трем (при ответе на этот вопрос стоит ещё упомянуть механизм COW, потому что на второй строчке var secondMass = firstMass мы копируем ссылку пока что, а не создаем полноценную копию, что очень важно). По итогу выведется [Andrey, Ivan, Lena].

Примерно это, только более простыми словами и подольше я рассказал в видео, но надеюсь и это описание было тебе доступно.

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

© Habrahabr.ru