TLDR;

Это похоже на замену компилятора :)

ВТТР;

Чтобы понять связанные типы: https://02infinity.medium.com/understanding-swift-protocol-associated-types-ca717d091b56

Мне было трудно понять ключевое слово «some» (появившееся в Swift 5.1). Теперь, когда у меня есть это «А-ха!» В тот момент я подумал, что смогу помочь другим понять с помощью этой статьи.

Начну с простого примера.

Скажем, вам нужны два разных типа чисел и вы хотите их распечатать:

struct IntegerNumber {
    var value: Int
    func printValue() {
        print(“value: \(value)”)
    }
}
struct FloatingPointNumber {
    var value: Float
    func printValue() {
        print(“value: \(value)”)
    }
}

Поскольку у обоих есть общая функция вывода значения, которое оно представляет, мы можем абстрагировать эту функциональность от протокола, используя мощь ассоциированных типов (универсальных шаблонов для протоколов), и привести наши числовые типы в соответствие с ним следующим образом:

protocol Number {
    associatedtype T
    var value: T { get }
    func printValue()
}
struct IntegerNumber: Number {
    var value: Int
    func printValue() {
        print(“value is Integer \(value)”)
    }
}
struct FloatingPointNumber: Number {
    var value: Float
    func printValue() {
        print(“value is Floating point \(value)”)
    }
}

Предположим, мы хотим выбрать только один из них и напечатать его.

Что нам следует сделать? Создать переменную типа Номер протокола????

// ERROR: protocol ‘Number’ can only be used as a generic constraint
// because it has Self or associated type requirements !!!!
var selectedNumber: Number = integerNumber //???????? CANNOT!!!!!

Ошибка компиляции! Потому что номер нашего протокола имеет ассоциированный тип. Почему так id обсуждение для другого раза/статьи.

Чтобы это сработало, нам придется использовать непрозрачные типы Swift.

Непрозрачные типы — это типы, которые компилятор заменяет для нас на основе того, что мы используем справа от символа «=» (RHS).

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

Здесь нам придется использовать

var selectedNumber: some Number = integerNumber // 1
// or
var selectedNumber: some Number = floatingNumber // 2

Компилятор заменяет «некоторое число» на «IntegerNumber» для случая 1 и FloatingPointNumber для случая 2:

var selectedNumber: IntegerNumber = integerNumber // 1
// or
var selectedNumber: FloatingPointNumber = floatingNumber // 2

Теперь мы можем напечатать число, используя метод согласованных протоколов, например:

selectedNumber.printValue()

Поскольку компилятор жестко устанавливает (строго типизирует) selectedNumber либо в IntegerNumber, либо в FloatingPointNumber при первом назначении, мы не можем переназначить его другой переменной, например:

var selectedNumber: some Number = integerNumber
//ERROR! the compiler substituted ‘some Number’ with type IntegerNumber!
selectedNumber = floatingNumber

Точно так же, если вы присвоите floatNumber для selectedNumber, вы не сможете впоследствии присвоить ему integerNumber.

var selectedNumber: sonme Number = floatingNumber
// ERROR! the compiler substituted ‘some Number’ with type IntegerNumber!
selectedNumber = integerNumber

Тип selectedNumber — это тип, которому вы назначаете его первым!

Чтобы подтвердить, что это замена на уровне компилятора, я попытался создать функцию для проверки этого. Эта функция возвращает тип Opaque, в реализации которого я пытался вернуть объекты классов, соответствующих протоколу Number:

//ERROR! Function declares an opaque return type, but the return statements in its body do not have matching underlying types
func createNumber() -> some Number {
    let num = arc4random() % 10
    if num % 2 == 0 {
       return IntegerNumber(value: 11)
    } else {
       return FloatingPointNumber(value: Float(num))
    }
}

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

Теперь это работает, если я возвращаю объекты того же типа в операторах возврата, тем самым подтверждая, что это замена компилятора:

// THIS compiles
func createNumber() -> some Number {
    let num = arc4random() % 10
    if num % 2 == 0 {
        return IntegerNumber(value: 11)
    } else {
        return IntegerNumber(value: Int(num))
    }
}

В терминах C это похоже на расширение макроса препроцессора!

Это также мало чем отличается от прекрасного constexpr C++ (когда условия положительны)!

Надеюсь, что это объяснение и исследование помогут вам понять «некоторые» и непрозрачные типы.