Что такое ключевое слово `some` в SwiftUI?




swift5.1 (4)

Новый учебник SwiftUI имеет следующий код:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

Во второй строке слово some , и на их сайте подсвечивается, как будто это ключевое слово.

Swift 5.1, похоже, не содержит some в качестве ключевого слова, и я не вижу, что еще может быть слово, которое some могут использовать, поскольку оно идет туда, где обычно идет тип. Есть ли новая, необъявленная версия Swift? Это функция, которая используется в типе способом, о котором я не знал?

Что делает ключевое слово some ?


Другой ответ хорошо объясняет технический аспект нового ключевого слова some но этот ответ попытается легко объяснить почему .

Допустим, у меня есть протокол Animal, и я хочу сравнить, являются ли два животных братьями и сестрами:

protocol Animal {
    func isSibling(with animal: Self) -> Bool
}

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

Теперь позвольте мне создать пример животного только для справки.

class Dog: Animal {
    func isSibling(with animal: Dog) -> Bool {
        return true // doesn't really matter implementation of this
    }
}

Путь без some T

Теперь допустим, у меня есть функция, которая возвращает животное из «семьи».

func animalFromAnimalFamily() -> Animal {
    return myDog // myDog is just some random variable of type `Dog`
}

Примечание: эта функция на самом деле не скомпилируется. Это связано с тем, что до того, как была добавлена ​​функция «some», вы не можете вернуть тип протокола, если протокол использует «Self» или generics . Но, скажем, вы можете ... притворяться, что это поднимает myDog на абстрактный тип Animal, давайте посмотрим, что произойдет

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

let animal1: Animal = animalFromAnimalFamily()
let animal2: Animal = animalFromAnimalFamily()

animal1.isSibling(animal2) // error

Это выдаст ошибку .

Зачем? Причина в том, что когда вы вызываете animal1.isSibling(animal2) Свифт не знает, являются ли животные собаками, кошками или animal1.isSibling(animal2) то еще. Насколько Свифт знает, animal2 и animal2 могут быть не связанными видами животных . Так как мы не можем сравнивать животных разных типов (см. Выше). Это будет ошибка

Как some T решают эту проблему

Давайте перепишем предыдущую функцию:

func animalFromAnimalFamily() -> some Animal {
    return myDog
}
let animal1 = animalFromAnimalFamily()
let animal2 = animalFromAnimalFamily()

animal1.isSibling(animal2)

animal2 и animal2 не являются Animal , но это класс, который реализует Animal .

Теперь это позволяет вам при вызове animal1.isSibling(animal2) Swift знает, что animal2 и animal2 относятся к одному типу.

Итак, как мне нравится думать об этом:

some T позволяют Swift знать, какая реализация T используется, а пользователь класса - нет.

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


Ключевое слово some из Swift 5.1 (предложение SE-0244 ) используется вместе с протоколом в качестве возвращаемого типа.

Примечания к выпуску Xcode 11 представляют это так:

Функции теперь могут скрывать свой конкретный тип возвращаемого значения, указав, каким протоколам он соответствует, вместо указания точного возвращаемого типа:

func makeACollection() -> some Collection {
    return [1, 2, 3]
}

Код, который вызывает функцию, может использовать интерфейс протокола, но не может видеть базовый тип. ( SE-0244 , 40538331)

В приведенном выше примере вам не нужно указывать, что вы собираетесь вернуть Array . Это позволяет вам даже возвращать универсальный тип, который просто соответствует Collection .

Теперь есть заметная известная проблема при использовании some :

Для объявлений с некоторыми типами возвращаемых протоколов требуется среда выполнения Swift 5.1 в iOS 13, macOS 10.15, watchOS 6 или tvOS 13, но компилятор Swift не обеспечивает этого. Запуск приложения, использующего некоторые типы возвращаемых данных в предыдущих версиях операционной системы, может привести к сбою во время выполнения либо из-за отсутствия символов swift_getOpaqueTypeMetadata, либо из-за сбоя при разборе строки, содержащей подстроку «Qo_». (50731151)

Обходной путь : Разверните двоичные файлы, которые используют некоторые типы возвращаемых данных, для iOS 13, macOS 10.15, watchOS 6 и tvOS 13. Избегайте их в коде, который должен работать в предыдущих версиях операционной системы.

Таким образом, это означает, что вы должны использовать доступность, чтобы избежать some на iOS 12 и раньше:

@available(iOS 13.0, *)
func makeACollection() -> some Collection {
    ...
}

some View - это непрозрачный тип результата, представленный SE-0244 и доступный в Swift 5.1 с Xcode 11. Вы можете думать об этом как об «обратном» родовом заполнителе.

В отличие от обычного родового заполнителя, который удовлетворяет вызывающая сторона:

protocol P {}
struct S1 : P {}
struct S2 : P {}

func foo<T : P>(_ x: T) {}
foo(S1()) // Caller chooses T == S1.
foo(S2()) // Caller chooses T == S2.

Непрозрачный тип результата - это неявный универсальный заполнитель, удовлетворяемый реализацией , поэтому вы можете подумать об этом:

func bar() -> some P {
  return S1() // Implementation chooses S1 for the opaque result.
}

как выглядит так:

func bar() -> <Output : P> Output {
  return S1() // Implementation chooses Output == S1.
}

Фактически, конечная цель этой функции - разрешить обратные обобщенные значения в этой более явной форме, что также позволит вам добавить ограничения, например -> <T : Collection> T where T.Element == Int . Смотрите этот пост для получения дополнительной информации .

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

// error: Function declares an opaque return type, but the return
// statements in its body do not have matching underlying types.
func bar(_ x: Int) -> some P {
  if x > 10 {
    return S1()
  } else {
    return S2()
  }
}

Поскольку неявный родовой заполнитель не может быть удовлетворен несколькими типами.

Это отличается от функции, возвращающей P , которая может использоваться для представления как S1 и S2 поскольку она представляет произвольное значение, соответствующее P :

func baz(_ x: Int) -> P {
  if x > 10 {
    return S1()
  } else {
    return S2()
  }
}

Итак, какие преимущества имеют непрозрачные типы результатов -> some P имеют по сравнению с типами возвращаемых протоколов -> P ?

1. Непрозрачные типы результатов могут использоваться с PAT

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

Это означает, что вы можете делать такие вещи, как:

func giveMeACollection() -> some Collection {
  return [1, 2, 3]
}

let collection = giveMeACollection()
print(collection.count) // 3

2. Непрозрачные типы результатов имеют идентичность

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

Это означает, что вы можете делать такие вещи, как:

//   foo() -> <Output : Equatable> Output {
func foo() -> some Equatable { 
  return 5 // The opaque result type is inferred to be Int.
}

let x = foo()
let y = foo()
print(x == y) // Legal both x and y have the return type of foo.

Это допустимо, потому что компилятор знает, что x и y имеют один и тот же конкретный тип. Это важное требование для == , где оба параметра типа Self .

protocol Equatable {
  static func == (lhs: Self, rhs: Self) -> Bool
}

Это означает, что он ожидает два значения, которые оба имеют тот же тип, что и конкретный соответствующий тип. Даже если Equatable было использовать в качестве типа, вы не смогли бы сравнить два произвольных соответствующих значения Equatable , например:

func foo(_ x: Int) -> Equatable { // Assume this is legal.
  if x > 10 {
    return 0
  } else {
    return "hello world"      
  }
}

let x = foo(20)
let y = foo(5)
print(x == y) // Illegal.

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

Аналогичным образом, если мы ввели другую непрозрачную функцию возврата типа:

//   foo() -> <Output1 : Equatable> Output1 {
func foo() -> some Equatable { 
  return 5 // The opaque result type is inferred to be Int.
}

//   bar() -> <Output2 : Equatable> Output2 {
func bar() -> some Equatable { 
  return "" // The opaque result type is inferred to be String.
}

let x = foo()
let y = bar()
print(x == y) // Illegal, the return type of foo != return type of bar.

Пример становится недопустимым, поскольку, хотя и foo и bar возвращают some Equatable , их «обратные» родовые заполнители Output1 и Output2 могут удовлетворяться разными типами.

3. Непрозрачные типы результатов сочетаются с общими заполнителями

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

protocol P {
  var i: Int { get }
}
struct S : P {
  var i: Int
}

func makeP() -> some P { // Opaque result type inferred to be S.
  return S(i: .random(in: 0 ..< 10))
}

func bar<T : P>(_ x: T, _ y: T) -> T {
  return x.i < y.i ? x : y
}

let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Legal, T is inferred to be the return type of makeP.

Это не сработало бы, если бы makeP только что возвратил P , поскольку два значения P могут иметь разные базовые конкретные типы, например:

struct T : P {
  var i: Int
}

func makeP() -> P {
  if .random() { // 50:50 chance of picking each branch.
    return S(i: 0)
  } else {
    return T(i: 1)
  }
}

let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Illegal.

Зачем использовать непрозрачный тип результата поверх конкретного типа?

В этот момент вы можете подумать, почему бы просто не написать код:

func makeP() -> S {
  return S(i: 0)
}

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

Например, вы можете заменить:

func makeP() -> some P {
  return S(i: 0)
}

с:

func makeP() -> some P { 
  return T(i: 1)
}

без нарушения кода, вызывающего makeP() .

См. Раздел «Непрозрачные типы» в руководстве по языку и SE-0244 для получения дополнительной информации об этой функции.


«Некоторые» означает:

« Это новое ключевое слово Swift 5.1 означает, что вычисляемое свойство может возвращать что угодно, если это что-то хотя бы соответствует протоколу View. Это верно для текста. И в Swift 5.1 нам также не нужно больше добавлять ключевое слово return. последняя строка функции или замыкания будет возвращена автоматически. "





swift5.1