Swift'teki (UI) `some` anahtar kelimesi nedir?


260

Yeni SwiftUI öğreticisinde şu kod vardır:

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

İkinci satır kelimesi someve sitelerinde bir anahtar kelime gibi vurgulanır.

Swift 5.1 somebir anahtar kelime olarak görünmüyor someve türün genellikle nereye gittiğine göre, kelimenin başka neler yapabileceğini görmüyorum . Swift'in yeni, duyurulmamış bir versiyonu var mı? Bildiğim şekilde bir tipte kullanılan bir işlev mi?

Anahtar kelime somene işe yarar?


Konu baş dönmesi olanlar için, burada Vadim Bulavin sayesinde çok şifre çözme ve adım adım bir makale. vadimbulavin.com/…
Luc-Olivier

Yanıtlar:


334

some Viewolan opak bir sonuç türü tarafından tanıtılan olarak SE-0244 ve bir "geri" genel yer tutucu olarak bu düşünebiliriz Xcode 11. Sizinle Swift 5.1 mevcuttur.

Arayan tarafından tatmin edilen normal bir genel yer tutucunun aksine:

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.

Opak bir sonuç türü, uygulamadan memnun olan örtük bir genel yer tutucudur , bu yüzden bunu düşünebilirsiniz:

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

şöyle görünüyor:

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

Aslında, bu özelliğin nihai amacı, bu daha açık biçimde ters jeneriklere izin vermektir, bu da sınırlamalar eklemenize izin verir, örn -> <T : Collection> T where T.Element == Int. Daha fazla bilgi için bu gönderiye bakın .

Bundan uzaklaşacak en önemli şey, geri dönen bir işlevin , uygun olan tek bir somut tipin some Pdeğerini döndüren bir işlev olmasıdır . İşlev içinde farklı uygun türleri döndürmeye çalışmak derleyici hatası verir: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()
  }
}

Örtük genel yer tutucu birden çok tip tarafından karşılanamadığından.

Bu, her ikisiniP temsil etmek için kullanılabilen ve keyfi bir uyumluluk değerini temsil ettiği için dönen bir işlevin tersidir : S1S2P

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

Peki, opak sonuç türlerinin -> some Pprotokol dönüş türlerine göre ne gibi faydaları vardır -> P?


1. Opak sonuç tipleri PAT'lerle kullanılabilir

Protokollerin önemli bir akım sınırlaması, PAT'lerin (ilişkili türlerle protokoller) gerçek türler olarak kullanılamamasıdır. Bu, dilin gelecekteki bir versiyonunda kaldırılacak bir kısıtlama olsa da, opak sonuç türleri etkili bir şekilde sadece genel yer tutucular olduğundan, günümüzde PAT'lerle kullanılabilirler.

Bu, aşağıdakileri yapabileceğiniz anlamına gelir:

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

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

2. Opak sonuç türlerinin kimliği vardır

Opak sonuç türleri tek bir beton türü döndürdüğünden, derleyici aynı işleve iki çağrının aynı türden iki değer döndürmesi gerektiğini bilir.

Bu, aşağıdakileri yapabileceğiniz anlamına gelir:

//   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.

Bu yasaldır çünkü derleyici her ikisini de bilir xve yaynı beton tipine sahiptir. Bu, ==her iki tip parametrenin de bulunduğu önemli bir gereksinimdir Self.

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

Bu, beton uyumlu tiple aynı tipte iki değer beklediği anlamına gelir. EquatableBir tür olarak kullanılabilse bile , iki rastgele Equatableuyumlu değeri birbiriyle karşılaştıramazsınız , örneğin:

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.

Derleyici, iki keyfi Equatabledeğerin aynı temel beton tipine sahip olduğunu kanıtlayamaz .

Benzer bir şekilde, başka bir opak tip geri döndürme fonksiyonu getirdiysek:

//   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.

Her iki halde çünkü örnek yasadışı hale gelir foove bargetiri some Equatable, onların jenerik tutucuları "ters" Output1ve Output2farklı türde suretiyle yerine getirilebileceğini.


3. Opak sonuç türleri jenerik yer tutucularla oluşturulur

Protokol tipindeki normal değerlerin aksine, opak sonuç türleri düzenli genel yer tutucularla iyi uyum sağlar, örneğin:

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.

İki değer farklı temel beton türlerine sahip olabileceğinden , makePyeni dönmüş olsaydı bu işe yaramazdı P, Pörneğin:

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.

Beton türü üzerinde neden opak bir sonuç türü kullanılmalı?

Bu noktada, kendiniz düşünüyor olabilirsiniz, neden kodu sadece şu şekilde yazmıyorsunuz:

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

Opak bir sonuç türünün kullanılması, Syalnızca sağlanan arabirimi açığa çıkararak P, işleve bağlı herhangi bir kodu kırmadan beton türünü daha sonra çizginin aşağısına değiştirme esnekliği sağlayarak , türü bir uygulama ayrıntısı yapmanıza olanak tanır .

Örneğin, aşağıdakileri değiştirebilirsiniz:

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

ile:

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

çağıran herhangi bir kodu kırmadan makeP().

Bkz Opak Çeşitleri bölümü dil kılavuzun ve Swift evrimi önerisini Bu özellik hakkında daha fazla bilgi için.


20
İlişkisiz: Swift 5.1'den itibaren, returntek ifadeli işlevlerde gerekli değildir
ielyamani

3
Ama arasındaki fark nedir: func makeP() -> some Pve func makeP() -> P? Teklifi okudum ve örnekleri için de bu farkı göremiyorum.
Artem


2
Swifts tipi kullanım bir karışıklıktır. Bu özgüllük derleme zamanında ele alınamayacak bir şey mi? Referans için C # 'a bakın, tüm bu durumları dolaylı olarak basit sözdizimi ile işler. Vardiyaların anlamsızca açık olması gereken kargo-kültçü sözdizimi dili gerçekten şaşırtmaktadır. Bunun için tasarım gerekliliğini de açıklayabilir misiniz? (Eğer github teklif çok iyi olurdu bir bağlantı varsa) Düzenleme: Sadece üst bağlantı olduğunu fark ettim.
SacredGeometry

2
@Zmaster Derleyici, her ikisi için de aynı beton türünü döndürse bile iki opak dönüş türünü farklı olarak ele alacaktır. Başka bir deyişle, seçilen belirli beton türü arayandan gizlenir. (Bu tür şeyleri biraz daha açık hale getirmek için cevabımın ikinci yarısında genişlemek için anlam ifade ettim, ancak henüz bu konuya gelemedim).
Hamish

52

Diğer cevap, yeni someanahtar kelimenin teknik yönünü açıklamak için iyi bir iş çıkarır, ancak bu cevap nedenini kolayca açıklamaya çalışacaktır .


Diyelim ki bir protokol hayvanım var ve iki hayvanın kardeş olup olmadığını karşılaştırmak istiyorum:

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

Bu şekilde, eğer iki hayvan aynı tür hayvan ise kardeş olup olmadığını karşılaştırmak mantıklıdır .


Şimdi sadece referans için bir hayvan örneği oluşturayım.

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

Olmadan yol some T

Şimdi diyelim ki bir 'aileden' bir hayvan döndüren bir fonksiyonum var.

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

Not: Bu işlev aslında derlenmez. Bunun nedeni, 'some' özelliği eklenmeden önce, protokol 'Self' veya jenerikler kullanıyorsa bir protokol türü döndüremezsiniz . Ama diyelim ki ... bu olayları taklit ederek myDog soyut tip Hayvan'a bakalım ne olacak

Şimdi, bunu yapmaya çalışırsam sorun geliyor:

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

animal1.isSibling(animal2) // error

Bu bir hata verecektir .

Neden? Nedeni, animal1.isSibling(animal2)Swift'i aradığınızda , hayvanların köpek, kedi veya başka bir şey olup olmadığını bilmemesi. Swift'in bildiği animal1ve animal2ilgisiz hayvan türleri olabileceği kadarıyla . Farklı türdeki hayvanları karşılaştıramadığımız için (yukarıya bakın). Bu hata olacak

some TBu sorunu nasıl çözer?

Bir önceki işlevi yeniden yazalım:

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

animal1.isSibling(animal2)

animal1ve animal2vardır değil Animal , ama onlar sınıf olduğunu uygular Hayvan vardır .

Bunun şimdi yapmanıza izin verdiği şey, aradığınızda animal1.isSibling(animal2), Swift bunu biliyor animal1ve animal2aynı tipte.

Bu yüzden bunu düşünmeyi seviyorum:

some TSwift'in hangi uygulamanın Tkullanıldığını bilmesini sağlar ancak sınıfın kullanıcısı bunu bilmez .

(Kendini tanıma reddi) Bu yeni özellik hakkında biraz daha derinlemesine bir blog yazısı yazdım (burada olduğu gibi aynı örnek)


2
Yani fikriniz, arayan, ne tür olduğunu bilmese de, işleve yapılan iki çağrının aynı türden dönmesi gerçeğinden yararlanabilir mi?
matt

1
@matt aslında yup. Alanlar, vb. İle kullanıldığında aynı konsept - arayan kişiye, dönüş türünün her zaman aynı tür olacağı ancak türün tam olarak ne olduğunu göstermediği garantisi verilir.
Downgoat

@Downgoat mükemmel mesaj ve cevap için çok teşekkür ederim. Anladığım gibi somedönüş türü, işlev gövdesi için kısıtlama olarak çalışır. Bu nedenle some, tüm işlev gövdesinde yalnızca bir beton türünün döndürülmesi gerekir. Örneğin: varsa return randomDogdiğer tüm iadeler sadece ile çalışmalıdır Dog. Tüm faydalar bu kısıtlamadan gelir: animal1.isSibling(animal2)derlemenin kullanılabilirliği ve faydası func animalFromAnimalFamily() -> some Animal(çünkü şimdi Selfbaşlık altında tanımlanmaktadır). Doğru mu?
Artem

5
İhtiyacım olan tek şey buydu, animal1 ve animal2 Animal değil, ancak Animal'i uygulayan sınıflar, şimdi hepsi mantıklı!
aross

29

Hamish'in cevabı oldukça harika ve soruyu teknik bir perspektiften cevaplıyor. Anahtar kelimenin someApple'ın SwiftUI eğitimlerinde neden bu özel yerde kullanıldığına ve neden takip edilmesi iyi bir uygulama olduğuna dair bazı düşünceler eklemek istiyorum .

some bir gereklilik değil!

Her şeyden önce, 's dönüş türünü opak bir tür olarak bildirmeniz gerekmezbody . Beton tipini kullanmak yerine her zaman iade edebilirsiniz some View.

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

Bu da derlenecek. View'Arayüzüne baktığınızda , dönüş türünün bodyilişkili bir tür olduğunu görürsünüz :

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 }
}

Bu araçlar bu size açıklayarak bu türünü belirtmek bodyistediğiniz bir türüyle özelliği. Tek şart, bu türün Viewprotokolün kendisini uygulaması gerektiğidir .

Bu, örneğin uygulayan belirli bir tür olabilir.View

  • Text
  • Image
  • Circle
  • ...

veya opak bir tip View, yani

  • some View

Genel Görünümler

Sorun, bir yığın görünümü body's dönüş türü olarak kullanmaya çalıştığımızda ortaya çıkar VStackveya HStack:

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

Bu derlenmez ve hatayı alırsınız:

'VStack' genel tipine başvurmak için <...>

Yıllardan İçerdeydi yığın görünümleri nedeniyle SwiftUI olan jenerik tipleri! 💡 (Aynı şey Listeler ve diğer kapsayıcı görünüm türleri için de geçerlidir .)

Bu çok mantıklıdır çünkü herhangi bir türden herhangi bir sayıda görünümü ekleyebilir ( Viewprotokole uyduğu sürece ). VStackYukarıdaki gövdedeki beton türü aslında

VStack<TupleView<(Text, Image)>>

Daha sonra yığına bir görünüm eklemeye karar verdiğimizde, beton türü değişir. İlk metinden sonra ikinci bir metin eklersek,

VStack<TupleView<(Text, Text, Image)>>    

Küçük bir değişiklik yapsak bile, metin ile resim arasına boşluk bırakma gibi ince bir şey bile, yığının türü değişir:

VStack<TupleView<(Text, _ModifiedContent<Spacer, _FrameLayout>, Image)>>

Söyleyebileceğim kadarıyla, olduğunu sebebi Apple her zaman kullanmak kendi eğitici önerir neden some View, tüm görünümler olarak, tatmin En genel opak türü body'ın dönüş türü. Dönüş türünü her seferinde manuel olarak değiştirmeden, özel görünümünüzün uygulamasını / düzenini değiştirebilirsiniz.


Ek:

Opak sonuç türlerini daha sezgisel bir şekilde anlamak istiyorsanız, yakın zamanda okumaya değer bir makale yayınladım:

Swift SwiftUI'de bu “bazıları” nedir?


2
Bu. Teşekkürler! Hamish'in cevabı çok eksiksizdi, ama seninki bana tam olarak bu örneklerde neden kullanıldığını anlatıyor.
Chris Marshall

"Bazı" fikrini seviyorum. Herhangi bir fikir "bazı" kullanarak derleme zamanı hiç etkiler?
Tofu Savaşçısı

@Mischa öyleyse nasıl jenerics görüntüler yapmak diğer davranışları soir görünümleri içeren bir protokol ile?
theMouk

27

Şimdiye kadar tüm cevapların eksik olduğunu düşünüyorum some, öncelikle SwiftUI gibi bir DSL (alana özgü dil) veya kullanıcıların (diğer programcıların) kendinizden farklı olmasını sağlayacak bir kütüphane / çerçeve gibi bir şeyde yararlı olduğunu düşünüyorum .

Muhtemelen somenormal uygulama kodunuzda asla kullanmazsınız , ancak genel bir protokolü bir tür olarak (yalnızca bir tür kısıtlaması yerine) kullanılabilmesi için kaplayabildiği sürece. Ne someyapar, önünde bir üst tip cephe koyarken derleyici, belirli tip bir şeydir şeyin bilgisine tutmak izin vermektir.

Böylece, kullanıcı olduğunuz SwiftUI'de, bilmeniz gereken tek şey bir şeydir, some Viewperde arkasında ise korunmuş olduğunuz her tür hantal-panky devam edebilir. Bu nesne aslında çok özel bir tür, ama ne olduğunu asla duymanız gerekmeyecek. Yine de, bir protokolün aksine, tam teşekküllü bir tiptir, çünkü göründüğü her yerde sadece belirli bir tam teşekküllü tip için bir cephedir.

SwiftUI'nin gelmesini beklediğiniz gelecekteki bir sürümünde some View, geliştiriciler söz konusu nesnenin temel türünü değiştirebilir. Ancak bu kodunuzu kırmaz, çünkü kodunuz ilk etapta altta yatan türden hiç bahsetmedi.

Böylece, someaslında bir protokol daha üst sınıf gibi yapar. Tam olarak olmasa da neredeyse gerçek bir nesne türüdür (örneğin, bir protokolün yöntem bildirimi a döndüremez some).

Kullanmak gittiğini Yani someher şey için, eğer büyük olasılıkla olurdu Eğer başkaları tarafından kullanılmak üzere DSL veya çerçeve / kitaplık yazmaya alındığını ve ayrıca altta yatan tip ayrıntıları maskelemek istedi. Bu, kodunuzun başkalarının kullanmasını kolaylaştırır ve kodunu kırmadan uygulama ayrıntılarını değiştirmenize olanak tanır.

Ancak, kendi kodunuzda, kodunuzun bir bölgesini, kodunuzun başka bir bölgesine gömülü uygulama ayrıntılarından korumanın bir yolu olarak da kullanabilirsiniz.


23

someSwift 5.1 (anahtar kelime hızlı evrim öneri ) dönüş türü Protokol ile bağlantılı olarak kullanılır.

Xcode 11 sürüm notları şu şekilde sunulur :

İşlevler artık kesin dönüş türünü belirtmek yerine hangi protokollere uyduğunu bildirerek somut dönüş türlerini gizleyebilir:

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

İşlevi çağıran kod, protokolün arabirimini kullanabilir, ancak altta yatan türün görünürlüğü yoktur. ( SE-0244 , 40538331)

Yukarıdaki örnekte, bir geri döneceğinizi söylemenize gerek yoktur Array. Bu, sadece uygun olan genel bir türü bile döndürmenizi sağlar Collection.


Karşılaşabileceğiniz olası bu hataya da dikkat edin:

'bazı' dönüş türleri yalnızca iOS 13.0.0 veya daha yeni sürümlerde kullanılabilir

Bu some, iOS 12'de ve öncesinde kaçınmak için kullanılabilirliği kullanmanız gerektiği anlamına gelir :

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

1
Bu odaklanmış cevap ve Xcode 11 beta derleyici sorunu için çok teşekkürler
brainray

1
someİOS 12'de ve öncesinde kaçınmak için kullanılabilirliği kullanmanız gerekiyor . Yaptığın sürece iyi olmalısın. Sorun sadece derleyicinin bunu yapmanız için sizi uyarmamasıdır.
matt

2
Cœur, tam da belirttiğiniz gibi, özlü Apple açıklaması her şeyi açıklıyor: İşlevler artık kesin dönüş türünü belirtmek yerine hangi protokollere uyduğunu bildirerek somut dönüş türlerini gizleyebilir. Ve sonra kodu çağıran fonksiyon protokol arayüzünü kullanabilir. Düzgün ve sonra bazı.
Fattie

Bu (somut geri dönüş türünü gizlemek), "bazı" anahtar kelimesini kullanmadan zaten mümkündür. Yöntem imzasına "bazı" ifadesinin eklenmesinin etkisini açıklamaz.
Vince O'Sullivan

@ VinceO'Sullivan someSwift 5.0 veya Swift 4.2'de verilen bu kod örneğindeki anahtar sözcüğü kaldırmak mümkün değildir . Hata: " Protokol 'Koleksiyonu" yalnızca Öz veya ilişkili tür gereksinimleri olduğundan genel bir kısıtlama olarak kullanılabilir "
Cœur

2

'bazıları' opak tip anlamına gelir. SwiftUI'de, Görünüm bir protokol olarak bildirilir

@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 }
}

Görünümünüzü Yapılar olarak oluşturduğunuzda, Görünüm protokolüne uyup var gövdesinin Görünüm Protokolü'nü onaylayacak bir şey döndüreceğini söylersiniz. Bu, Beton Tipini Tanımlamak zorunda olmadığınız genel bir Protokol soyutlaması gibi.


2

Bunu çok temel pratik örnekle cevaplamaya çalışacağım (bu opak bir sonuç türü nedir )

İlişkili türle protokolünüz olduğunu ve bunu uygulayan iki yapının olduğunu varsayarsak:

protocol ProtocolWithAssociatedType {
    associatedtype SomeType
}

struct First: ProtocolWithAssociatedType {
    typealias SomeType = Int
}

struct Second: ProtocolWithAssociatedType {
    typealias SomeType = String
}

Swift 5.1'den önce, aşağıdaki ProtocolWithAssociatedType can only be used as a generic constrainthata nedeniyle yasa dışıdır :

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

Ancak Swift 5.1'de bu iyi ( someeklendi):

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

Yukarıda SwiftUI için yaygın olarak kullanılan pratik kullanımdır some View.

Ama var bir önemli sınırlama - tipi ihtiyaçlarını dönen vererek işe yaramaz bu yüzden aşağıda yine derleme zamanında biliyorum olmak Function declares an opaque return type, but the return statements in its body do not have matching underlying typeshatayı:

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

0

Akla gelen basit bir kullanım örneği, sayısal türler için genel işlevler yazmaktır.

/// Adds one to any decimal type
func addOne<Value: FloatingPoint>(_ x: Value) -> some FloatingPoint {
    x + 1
}

// Variables will be assigned 'some FloatingPoint' type
let double = addOne(Double.pi) // 4.141592653589793
let float = addOne(Float.pi) // 4.141593

// Still get all of the required attributes/functions by the FloatingPoint protocol
double.squareRoot() // 2.035090330572526
float.squareRoot() // 2.03509

// Be careful, however, not to combine 2 'some FloatingPoint' variables
double + double // OK 
//double + float // error

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.