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

struct ContentView: View {
    var body: some View {
        Текст («Привет, мир»)
    }
}

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

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

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

Half

Ответов: 13

Ответы (13)

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

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

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

func foo(_ 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 {
  return S1() // Implementation chooses Output == S1.
}

In fact, the eventual goal with this feature is to allow reverse generics in this more explicit form, which would also let you add constraints, e.g -> T where T.Element == Int. See this post for more info.

Главное, что нужно вынести из этого, - это то, что функция, возвращающая некоторый 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()
  }
}

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


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

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

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

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

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

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

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

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

//   foo() ->  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 {
func foo() -> some Equatable { 
  return 5 // The opaque result type is inferred to be Int.
}

//   bar() ->  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 возвращают некоторые 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(_ 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.

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

At this point you may be thinking to yourself, why not just write the code as:

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 ().

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

Я думаю, что все ответы на данный момент отсутствуют, так это то, что некоторые полезны в первую очередь в чем-то вроде DSL (предметно-ориентированного языка), например SwiftUI или библиотеке / фреймворке, которые будут иметь пользователи (другие программисты) отличные от вас.

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

Таким образом, в SwiftUI, где вы являетесь пользователем, все , которые вам нужно знать, это что-то какой-то View, в то время как за кулисами могут идти все виды платков. от которого вы защищены. На самом деле этот объект является очень специфическим типом, но вам никогда не понадобится слышать о том, что это такое. Тем не менее, в отличие от протокола, это полноценный тип, потому что где бы он ни появлялся, он всего лишь фасад какого-то определенного полноценного типа.

В будущей версии SwiftUI, где вы ожидаете some View, разработчики могут изменить базовый тип этого конкретного объекта. Но это не сломает ваш код, потому что в вашем коде вообще не упоминается базовый тип.

Таким образом, some фактически делает протокол более похожим на суперкласс. Это почти реальный тип объекта, хотя и не совсем (например, объявление метода протокола не может вернуть some).

Итак, если вы собираетесь использовать some для чего-нибудь, это, скорее всего, будет, если вы писали DSL или фреймворк / библиотеку для использования другими, и вы хотели маскировать детали основного типа. Это упростит использование вашего кода для других и позволит вам изменить детали реализации, не нарушая их код.

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

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

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

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

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

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

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


Обратите внимание на эту возможную ошибку, с которой вы можете столкнуться:

"некоторые" возвращаемые типы доступны только в iOS 13.0.0 или новее

Это означает, что вы должны использовать доступность, чтобы избежать некоторых на iOS 12 и ранее:

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

Ответ Хэмиша довольно хорош и отвечает на вопрос с технической точки зрения. Я хотел бы добавить несколько мыслей о том, почему ключевое слово some используется именно в этом месте в руководствах Apple SwiftUI и почему это хорошая практика.

некоторые не являются обязательными!

Во-первых, вам не нужно объявлять возвращаемый тип bodyкак непрозрачный тип. Вы всегда можете вернуть конкретный тип вместо использования some View.

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

Это тоже будет компилироваться. Когда вы заглянете в интерфейс View, вы увидите, что возвращаемый тип body является связанным типом:

public protocol View : _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 `body` property.
    associatedtype Body : View

    /// Declares the content and behavior of this view.
    var body: Self.Body { get }
}

Это означает, что вы указываете этот тип, аннотируя свойство body конкретным типом по вашему выбору. Единственное требование - этот тип должен реализовывать сам протокол View.

Это может быть тип , реализующий View, например

  • Текст
  • Изображение
  • Круг

или непрозрачный тип, реализующий View, т.е.

  • немного просмотра

Общие просмотры

Проблема возникает, когда мы пытаемся использовать представление стека в качестве возвращаемого типа body, например VStack или HStack:

struct ContentView: View {
    var body: VStack {
        VStack {
            Text("Hello World")
            Image(systemName: "video.fill")
        }
    }
}

Это не скомпилируется, и вы получите сообщение об ошибке:

Ссылка на универсальный тип VStack требует аргументов в <...>

Это потому, что представления стека в SwiftUI являются общими типами! 💡 (То же самое верно для Списки и других типов представления контейнеров.)

Это имеет большой смысл, потому что вы можете подключить любое количество представлений любого типа (при условии, что это соответствует протоколу View). Конкретный тип VStack в приведенном выше теле на самом деле

VStack>

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

VStack>    

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

VStack, Image)>>

Насколько я могу судить, это причина, по которой Apple рекомендует в своих руководствах всегда использовать some View, наиболее общий непрозрачный тип, которому удовлетворяют все представления, например * 100006 Тип возвращаемого значения * body. Вы можете изменить реализацию / макет своего пользовательского представления, не меняя каждый раз вручную тип возвращаемого значения.


Дополнение:

Если вы хотите получить более интуитивное представление о непрозрачных типах результатов, я недавно опубликовал статью, которую стоит прочитать:

🔗 Что это за «немного» в SwiftUI?

Простой способ понять, например, kindOf в Objc

Вы можете использовать как generic в swift.

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

https://www.vadimbulavin.com/opaque-return-types-and-the-some-keyword-in-swift/

В моем понимании (может и не так)

Звонок, который у меня был

Protocol View{}

 class Button: View { // subclass of View } 

 //this class not a subclass of View
 class ButtonBuilder where T:View { //using T as View here   } 

Затем

var body: View = Button() // ok
var body: View = ButtonBilder() //not ok
var body: some View = ButtonBilder() //ok

Так

какой-то протокол

Может обрабатывать общий класс, который использует этот протокол как общий в своем собственном коде как подкласс протокола

В приведенном выше сообщении Миши (извините, я пока не могу напрямую добавить комментарий) говорится, что some не является обязательным, если вы не используете общие типы как VStack и т. Д. И это потому, что some является самый общий непрозрачный тип, которому удовлетворяют все представления. Таким образом, его использование здесь помогает устранить ошибку компиляции.

Кажется, некоторые очень близки к тому, что делает метод Combine eraseToAnyPublisher ().

для упрощения, если вы знаете разницу между

var x = 5

против

int x = 5

Тогда вы будете знать какие-то. Компилятор это знает, и вы это знаете. Минимальные усилия, чтобы заявить, что вы что-то соблюдаете, без указания специфики (общих типов, которые он использует)

Я попытаюсь ответить на это на очень простом практическом примере (что это непрозрачный тип результата примерно)

Предполагая, что у вас есть протокол со связанным типом и две структуры, реализующие его:

protocol ProtocolWithAssociatedType {
    associatedtype SomeType
}

struct First: ProtocolWithAssociatedType {
    typealias SomeType = Int
}

struct Second: ProtocolWithAssociatedType {
    typealias SomeType = String
}

До Swift 5.1 приведенное ниже недопустимо, так как ProtocolWithAssociatedType может использоваться только как общее ограничение ошибка:

func create() -> ProtocolWithAssociatedType {
    return First()
}

Но в Swift 5.1 это нормально (некоторые добавлены):

func create() -> some ProtocolWithAssociatedType {
    return First()
}

Выше указано практическое использование, широко используемое в SwiftUI для некоторых View.

Но есть одно важное ограничение - возвращаемый тип должен быть известен во время компиляции, поэтому ниже снова не будет работать, давая Функция объявляет непрозрачный тип возвращаемого значения, но операторы возврата в своем тело не имеет соответствующих базовых типов ошибка:

func create() -> some ProtocolWithAssociatedType {
    if (1...2).randomElement() == 1 {
        return First()
    } else {
        return Second()
    }
}

«какой-то» означает непрозрачный тип. В SwiftUI View объявлен как протокол

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
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 `body` property.
    associatedtype Body : View

    /// Declares the content and behavior of this view.
    var body: Self.Body { get }
}

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

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


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

протокол Животное {
    func isSibling (_ животное: Я) -> Bool
}

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


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

класс Собака: Животное {
    func isSibling (_ животное: Собака) -> Bool {
        return true // не имеет значения реализация этого
    }
}

Способ без какой-то Т

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

func animalFromAnimalFamily () -> Animal {
    return myDog // myDog - это просто некоторая случайная переменная типа `Dog`
}

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

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

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

animal1.isSibling (animal2) // ошибка

Это вызовет ошибку.

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

Как какой-нибудь T решает эту проблему

Перепишем предыдущую функцию:

func animalFromAnimalFamily () -> some Animal {
    вернуть myDog
}
let animal1 = animalFromAnimalFamily ()
let animal2 = animalFromAnimalFamily ()

animal1.isSibling (животное2)

animal1 и animal2 - это не Animal, , но это класс, реализующий Animal.

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

Так я думаю об этом:

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

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

2022 WebDevInsider