คำหลัก `some` ใน Swift (UI) คืออะไร


259

บทช่วยสอน SwiftUIใหม่มีรหัสต่อไปนี้:

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

บรรทัดที่สองคือคำsomeและบนเว็บไซต์ของพวกเขาจะถูกเน้นเป็นคำหลัก

Swift 5.1 ไม่ปรากฏว่ามีsomeคำหลักและฉันไม่เห็นสิ่งที่คำอื่นsomeสามารถทำได้ที่นั่นเนื่องจากเป็นประเภทที่มักจะไป Swift รุ่นใหม่ไม่มีการแจ้งล่วงหน้าหรือไม่? มันเป็นฟังก์ชั่นที่ใช้กับประเภทในแบบที่ฉันไม่รู้หรือไม่?

คำหลักsomeทำอะไร


สำหรับผู้ที่มีอาการวิงเวียนศีรษะโดยที่นี่เป็นบทความถอดรหัสและทีละขั้นตอนขอบคุณ Vadim Bulavin vadimbulavin.com/…
Luc-Olivier

คำตอบ:


333

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 ได้

ข้อ จำกัด ในปัจจุบันที่สำคัญของโปรโตคอลคือ PATs (โปรโตคอลที่มีประเภทที่เกี่ยวข้อง) ไม่สามารถใช้เป็นประเภทจริง แม้ว่านี่จะเป็นข้อ จำกัด ที่มีแนวโน้มว่าจะยกระดับขึ้นในภาษาในอนาคตเนื่องจากประเภทผลลัพธ์ที่ทึบแสงเป็นเพียงตัวยึดตำแหน่งทั่วไป แต่ก็สามารถใช้กับ 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()ที่โทร

ดูส่วน Opaque Typesของคู่มือภาษาและข้อเสนอวิวัฒนาการ Swiftสำหรับข้อมูลเพิ่มเติมเกี่ยวกับคุณสมบัตินี้


20
ไม่เกี่ยวข้อง: ณ Swift 5.1 returnไม่จำเป็นต้องใช้ในฟังก์ชั่นนิพจน์เดียว
ielyamani

3
แต่อะไรคือความแตกต่างระหว่าง: func makeP() -> some Pและfunc makeP() -> P? ฉันได้อ่านข้อเสนอและไม่สามารถเห็นความแตกต่างนี้สำหรับตัวอย่างของพวกเขาด้วย
Artem


2
การจัดการชนิด Swifts เป็นระเบียบ ความจำเพาะนี้เป็นสิ่งที่ไม่สามารถจัดการได้ในเวลารวบรวมหรือไม่? ดู C # สำหรับการอ้างอิงมันจัดการกรณีเหล่านี้โดยนัยผ่านทางไวยากรณ์อย่างง่าย การสลับสับเปลี่ยนจำเป็นต้องมีไวยากรณ์ที่ชัดเจนเกี่ยวกับการขนส่งสินค้าอย่างชัดเจนอย่างไร้จุดหมายซึ่งทำให้ภาษาสับสน คุณสามารถอธิบายเหตุผลการออกแบบสำหรับสิ่งนี้ได้ไหม (หากคุณมีลิงก์ไปยังข้อเสนอใน GitHub ที่จะเป็นสิ่งที่ดีเช่นกัน) แก้ไข: เพิ่งสังเกตว่าลิงก์นั้นอยู่ด้านบน
SacredGeometry

2
@Zmaster คอมไพเลอร์จะจัดการกับชนิดส่งคืน opaque สองชนิดซึ่งแตกต่างกันแม้ว่าการนำไปใช้สำหรับทั้งสองประเภทจะส่งคืนชนิดคอนกรีตเดียวกัน กล่าวอีกนัยหนึ่งประเภทคอนกรีตเฉพาะที่เลือกจะถูกซ่อนจากผู้โทร (ฉันมีความหมายที่จะขยายในช่วงครึ่งหลังของคำตอบของฉันเพื่อทำให้สิ่งต่าง ๆ เช่นนี้ชัดเจนขึ้นเล็กน้อย แต่ยังไม่ได้รับมันเลย)
Hamish

52

คำตอบอื่น ๆ ที่ไม่ได้งานที่ดีของการอธิบายด้านเทคนิคของใหม่someคำหลัก แต่คำตอบนี้จะพยายามที่จะได้อย่างง่ายดายอธิบายว่าทำไม


สมมติว่าฉันมีโปรโตคอลสัตว์และฉันต้องการเปรียบเทียบว่าสัตว์สองตัวเป็นพี่น้องกันหรือไม่:

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

ด้วยวิธีนี้มันจึงสมเหตุสมผลที่จะเปรียบเทียบว่าสัตว์สองตัวเป็นพี่น้องกันหรือไม่หากพวกเขาเป็นสัตว์ประเภทเดียวกัน


ตอนนี้ให้ฉันสร้างตัวอย่างของสัตว์เพื่อใช้อ้างอิง

class Dog: Animal {
    func isSibling(_ 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`
}

หมายเหตุ: ฟังก์ชั่นนี้จะไม่ได้รวบรวมจริง นี้เพราะก่อนที่ 'บาง' คุณสมบัติถูกเพิ่มเข้ามาคุณจะไม่สามารถกลับมาชนิดโปรโตคอลถ้าใช้โปรโตคอล 'ตัวเอง' หรือยาชื่อสามัญ แต่สมมติว่าคุณทำได้ ... ทำท่า myDog นี้ให้แกล้งทำเป็นสัตว์ประเภทนามธรรมลองดูว่าเกิดอะไรขึ้น

ตอนนี้ปัญหามาคือถ้าฉันพยายามทำ:

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

animal1.isSibling(animal2) // error

นี้จะโยนความผิดพลาด

ทำไม? เหตุผลก็คือเมื่อคุณโทรหาanimal1.isSibling(animal2)สวิฟท์ไม่รู้ว่าสัตว์เป็นสุนัขแมวหรืออะไรก็ตาม เท่าที่สวิฟท์รู้animal1และanimal2อาจจะไม่เกี่ยวข้องกับสัตว์ชนิด เนื่องจากเราไม่สามารถเปรียบเทียบสัตว์ประเภทต่าง ๆ (ดูด้านบน) ข้อผิดพลาดนี้จะเกิดขึ้น

วิธีsome Tแก้ปัญหานี้

ลองเขียนฟังก์ชั่นก่อนหน้านี้ใหม่:

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

animal1.isSibling(animal2)

animal1และanimal2มีไม่ได้ Animal , แต่ พวกเขามีคลาสที่ใช้สัตว์

สิ่งนี้ช่วยให้คุณทำตอนนี้ได้เมื่อคุณโทรanimal1.isSibling(animal2)Swift รู้animal1และanimal2เป็นประเภทเดียวกัน

ดังนั้นวิธีที่ฉันชอบคิดเกี่ยวกับมัน:

some Tให้สวิฟท์รู้ว่าการใช้งานแบบTใด แต่ผู้ใช้ของคลาสไม่ได้ใช้

(ข้อจำกัดความรับผิดชอบในการโปรโมตตนเอง) ฉันได้เขียนโพสต์บล็อกที่มีความลึกเพิ่มขึ้นเล็กน้อย (เช่นเดียวกับที่นี่) ในคุณลักษณะใหม่นี้


2
ดังนั้นแนวคิดของคุณคือผู้โทรสามารถใช้ประโยชน์จากความจริงที่ว่าการเรียกสองครั้งไปยังฟังก์ชันส่งคืนชนิดเดียวกันแม้ว่าผู้โทรจะไม่ทราบว่าเป็นประเภทใด
แมตต์

1
@ แมทเป็นหลัก yup แนวคิดเดียวกันเมื่อใช้กับฟิลด์ ฯลฯ ผู้เรียกจะได้รับการรับประกันว่าประเภทที่ส่งคืนจะเป็นประเภทเดียวกันเสมอ แต่ไม่เปิดเผยว่าเป็นประเภทใด
Downgoat

@ Downgoat ขอบคุณมากสำหรับการโพสต์และคำตอบที่สมบูรณ์แบบ ดังที่ฉันเข้าใจsomeในรูปแบบผลตอบแทนว่าเป็นข้อ จำกัด ต่อส่วนของฟังก์ชัน ดังนั้นจึงsomeจำเป็นต้องส่งคืนชนิดคอนกรีตเพียงหนึ่งประเภทในร่างกายของฟังก์ชันทั้งหมด ตัวอย่างเช่นถ้ามีreturn randomDogแล้วผลตอบแทนอื่น ๆ Dogทั้งหมดต้องทำงานเฉพาะกับ ผลประโยชน์ทั้งหมดมาจากข้อ จำกัด นี้: ความพร้อมใช้งานanimal1.isSibling(animal2)และประโยชน์ของการรวบรวมfunc animalFromAnimalFamily() -> some Animal(เพราะตอนนี้Selfกลายเป็นที่กำหนดไว้ภายใต้ประทุน) ถูกต้องหรือไม่
Artem

5
บรรทัดนี้คือทั้งหมดที่ฉันต้องการสัตว์ 1 และสัตว์ 2 ไม่ใช่สัตว์ แต่พวกเขาเป็นคลาสที่ใช้สัตว์ตอนนี้ทุกอย่างเข้าท่าแล้ว!
aross

29

คำตอบของ Hamishนั้นยอดเยี่ยมมากและตอบคำถามจากมุมมองทางเทคนิค ฉันต้องการเพิ่มความคิดเกี่ยวกับสาเหตุที่คำหลักsomeถูกนำมาใช้ในสถานที่นี้โดยเฉพาะในแบบฝึกหัด SwiftUIของ Apple และทำไมจึงควรปฏิบัติตาม

some ไม่ใช่ข้อกำหนด!

ก่อนอื่นคุณไม่จำเป็นต้องประกาศ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ตัวอย่างเช่น

  • Text
  • Image
  • Circle
  • ...

หรือประเภททึบแสงที่ใช้Viewเช่น

  • some View

มุมมองทั่วไป

ปัญหาเกิดขึ้นเมื่อเราพยายามที่จะใช้มุมมองสแต็คเป็นbodyของประเภทกลับเหมือนVStackหรือHStack:

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

สิ่งนี้จะไม่รวบรวมและคุณจะได้รับข้อผิดพลาด:

การอ้างอิงถึงประเภททั่วไป 'VStack' จำเป็นต้องมีอาร์กิวเมนต์ใน <... >

นั่นเป็นเพราะมุมมองสแต็กในSwiftUIเป็นประเภททั่วไป ! 💡 (และเช่นเดียวกันกับรายการและประเภทมุมมองคอนเทนเนอร์อื่น ๆ )

มันสมเหตุสมผลมากเพราะคุณสามารถเสียบมุมมองจำนวนเท่าใดก็ได้ในประเภทใดก็ได้ (ตราบใดที่มันสอดคล้องกับViewโปรโตคอล) ชนิดที่เป็นรูปธรรมของVStackร่างกายในด้านบนเป็นจริง

VStack<TupleView<(Text, Image)>>

เมื่อเราตัดสินใจเพิ่มมุมมองในสแต็กในภายหลังการเปลี่ยนแปลงประเภทที่เป็นรูปธรรม หากเราเพิ่มข้อความที่สองหลังจากข้อความแรกเราจะได้รับ

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

แม้ว่าเราจะทำการเปลี่ยนแปลงเล็กน้อยสิ่งที่ลึกซึ้งเช่นการเพิ่มเว้นวรรคระหว่างข้อความและรูปภาพการเปลี่ยนแปลงประเภทของสแต็ก:

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

จากสิ่งที่ฉันสามารถบอกได้นั่นคือเหตุผลที่ Apple แนะนำในแบบฝึกหัดของพวกเขาที่จะใช้เสมอsome Viewประเภททึบแสงทั่วไปที่ทุกมุมมองพึงพอใจในฐานะที่bodyเป็นประเภทส่งคืน คุณสามารถเปลี่ยนการใช้งาน / เค้าโครงของมุมมองที่กำหนดเองของคุณโดยไม่ต้องเปลี่ยนประเภทการส่งคืนทุกครั้ง


เสริม:

หากคุณต้องการความเข้าใจที่เข้าใจง่ายยิ่งขึ้นเกี่ยวกับประเภทผลลัพธ์ที่ทึบแสงฉันเพิ่งเผยแพร่บทความที่อาจคุ้มค่ากับการอ่าน:

🔗 อะไรคือ“ บางส่วน” ใน SwiftUI


2
นี้. ขอบคุณ! คำตอบของ Hamish นั้นสมบูรณ์มาก แต่คุณบอกฉันอย่างชัดเจนว่าทำไมมันถึงใช้ในตัวอย่างเหล่านี้
Chris Marshall

ฉันชอบความคิดของ "บางคน" มีความคิดใดบ้างไหมหากใช้ "บางอย่าง" มีผลต่อเวลาในการรวบรวมเลย?
Tofu Warrior

@Mischa ดังนั้นวิธีการทำให้มุมมองทั่วไป? กับโปรโตคอลที่มีมุมมองและพฤติกรรมอื่น ๆ ?
theMouk

27

ฉันคิดว่าคำตอบทั้งหมดที่หายไปคือสิ่งที่someมีประโยชน์เป็นหลักในบางสิ่งบางอย่างเช่น DSL (ภาษาเฉพาะโดเมน) เช่น SwiftUI หรือไลบรารี / กรอบงานซึ่งจะมีผู้ใช้ (โปรแกรมเมอร์อื่น ๆ ) แตกต่างจากตัวคุณเอง

คุณอาจจะไม่เคยใช้someในรหัสแอปปกติของคุณยกเว้นบางทีตราบเท่าที่มันสามารถห่อโปรโตคอลทั่วไปเพื่อให้สามารถใช้เป็นประเภท (แทนที่จะเป็นข้อ จำกัด ประเภท) อะไรคือสิ่งsomeที่ทำให้คอมไพเลอร์เก็บความรู้เกี่ยวกับประเภทของสิ่งที่เฉพาะเจาะจงในขณะที่วางด้านหน้าซูเปอร์ไทป์ไว้ด้านหน้า

ดังนั้นใน SwiftUI ที่ซึ่งคุณเป็นผู้ใช้สิ่งที่คุณต้องรู้ก็คือมีบางอย่างsome Viewในขณะที่อยู่เบื้องหลังทุกประเภทของผ้าเช็ดหน้าที่ไม่สามารถไปจากที่คุณได้รับการป้องกัน อันที่จริงวัตถุนี้เป็นประเภทที่เฉพาะเจาะจงมาก แต่คุณไม่จำเป็นต้องฟังเกี่ยวกับสิ่งที่มันเป็น แต่ไม่เหมือนกับโปรโตคอลมันเป็นประเภทที่เต็มเปี่ยมเนื่องจากที่ใดก็ตามที่ปรากฏว่ามันเป็นเพียงส่วนหน้าสำหรับประเภทเต็มรูปแบบเฉพาะบางอย่าง

ในรุ่นอนาคตของ SwiftUI ที่คุณคาดหวังsome Viewนักพัฒนาสามารถเปลี่ยนชนิดพื้นฐานของวัตถุนั้น แต่นั่นจะไม่ทำลายรหัสของคุณเพราะรหัสของคุณไม่เคยพูดถึงประเภทพื้นฐานในสถานที่แรก

ดังนั้นsomeผลทำให้โพรโทคอลเหมือนซุปเปอร์คลาสมากขึ้น มันเกือบจะเป็นประเภทวัตถุจริง แต่ไม่มาก (ตัวอย่างเช่นการประกาศวิธีการของโปรโตคอลไม่สามารถส่งคืนได้some)

ดังนั้นหากคุณกำลังจะใช้someเพื่ออะไรมันจะเป็นไปได้มากที่สุดถ้าคุณกำลังเขียน DSL หรือเฟรมเวิร์ก / ไลบรารีเพื่อการใช้งานโดยผู้อื่นและคุณต้องการปกปิดรายละเอียดประเภทของต้นแบบ สิ่งนี้จะทำให้รหัสของคุณง่ายขึ้นสำหรับผู้อื่นในการใช้และจะช่วยให้คุณสามารถเปลี่ยนแปลงรายละเอียดการใช้งานโดยไม่ทำให้รหัสของพวกเขาพัง

อย่างไรก็ตามคุณอาจใช้ในรหัสของคุณเองเพื่อป้องกันพื้นที่หนึ่งของรหัสของคุณจากรายละเอียดการใช้งานที่ฝังอยู่ในภูมิภาคอื่นของรหัสของคุณ


23

someคำหลักจากสวิฟท์ 5.1 ( รวดเร็ววิวัฒนาการข้อเสนอ ) ถูกนำมาใช้ร่วมกับโพรโทคอเป็นประเภทผลตอบแทนที่

บันทึกย่อประจำรุ่น Xcode 11 นำเสนอเหมือน:

ขณะนี้ฟังก์ชั่นสามารถซ่อนประเภทการส่งคืนที่เป็นรูปธรรมของตนได้โดยการประกาศโปรโตคอลที่สอดคล้องกับมันแทนที่จะระบุประเภทการส่งคืนที่แน่นอน:

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

รหัสที่เรียกใช้ฟังก์ชันสามารถใช้อินเทอร์เฟซของโปรโตคอล แต่ไม่สามารถมองเห็นได้ในประเภทพื้นฐาน ( SE-0244 , 40538331)

Arrayในตัวอย่างข้างต้นคุณไม่จำเป็นต้องบอกได้ว่าคุณกำลังจะกลับ ที่ช่วยให้คุณสามารถคืนประเภททั่วไปที่เพิ่งเป็นไปCollectionได้


โปรดทราบข้อผิดพลาดที่เป็นไปได้เช่นนี้ซึ่งคุณอาจเผชิญ:

ประเภทการคืน 'บาง' นั้นมีเฉพาะใน iOS 13.0.0 หรือใหม่กว่า

หมายความว่าคุณควรใช้ความพร้อมเพื่อหลีกเลี่ยงsomeใน iOS 12 และก่อนหน้านี้:

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

1
ขอบคุณมากสำหรับคำตอบที่มุ่งเน้นนี้และปัญหาคอมไพเลอร์ใน Xcode 11 beta
brainray

1
คุณควรใช้ความพร้อมใช้งานเพื่อหลีกเลี่ยงsomeใน iOS 12 และก่อนหน้านี้ ตราบใดที่คุณทำคุณควรจะสบายดี ปัญหาคือคอมไพเลอร์ไม่เตือนคุณให้ทำเช่นนี้
ด้าน

2
ดังนั้น, ตามที่คุณระบุ, คำอธิบายที่กระชับของ Apple อธิบายได้ทั้งหมด: ตอนนี้ฟังก์ชั่นสามารถซ่อนประเภทการส่งคืนที่เป็นรูปธรรมได้โดยการประกาศโปรโตคอลที่สอดคล้องกับมันแทนที่จะระบุประเภทการส่งคืนที่แน่นอน แล้วเรียกรหัสฟังก์ชั่นสามารถใช้ส่วนต่อประสานโปรโตคอล เรียบร้อยแล้วบางส่วน
Fattie

สิ่งนี้ (ซ่อนประเภทการส่งคืนที่เป็นรูปธรรม) เป็นไปได้อยู่แล้วโดยไม่ใช้คำหลัก "บางส่วน" ไม่ได้อธิบายถึงผลกระทบของการเพิ่ม "บางอย่าง" ลงในลายเซ็นวิธีการ
Vince O'Sullivan

@ VinceO'Sullivan ไม่สามารถลบsomeคำหลักในตัวอย่างรหัสที่กำหนดใน Swift 5.0 หรือ Swift 4.2 ข้อผิดพลาดจะเป็น: " พิธีสารเก็บ 'เท่านั้นที่สามารถนำมาใช้เป็นข้อ จำกัด ทั่วไปเพราะมันมีตัวเองหรือความต้องการประเภทที่เกี่ยวข้อง "
Cœur

2

'some' หมายถึงชนิดทึบแสง ใน SwiftUI มุมมองถูกประกาศเป็นโปรโตคอล

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

เมื่อคุณสร้างมุมมองเป็นโครงสร้างคุณจะต้องปฏิบัติตามโปรโตคอลดูและบอกว่าเนื้อความ var จะส่งคืนบางสิ่งที่จะยืนยันกับโปรโตคอลดู มันเป็นเหมือนนามธรรมโปรโตคอลทั่วไปที่คุณไม่จำเป็นต้องกำหนดประเภทที่เป็นรูปธรรม


2

ฉันจะพยายามตอบคำถามนี้พร้อมกับตัวอย่างที่ใช้งานได้จริงขั้นพื้นฐาน (นี่คือผลลัพธ์แบบทึบ )

สมมติว่าคุณมีโปรโตคอลที่มีประเภทที่เกี่ยวข้องและสอง structs ใช้มัน:

protocol ProtocolWithAssociatedType {
    associatedtype SomeType
}

struct First: ProtocolWithAssociatedType {
    typealias SomeType = Int
}

struct Second: ProtocolWithAssociatedType {
    typealias SomeType = String
}

ก่อน Swift 5.1 ด้านล่างผิดกฎหมายเนื่องจากProtocolWithAssociatedType can only be used as a generic constraintข้อผิดพลาด:

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

แต่ใน Swift 5.1 นี่ใช้ได้ ( someเพิ่มเติม):

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

ข้างต้นคือการใช้งานในทางปฏิบัติใช้อย่างกว้างขวางใน SwiftUI some Viewสำหรับ

แต่มีข้อ จำกัด ที่สำคัญอย่างหนึ่ง - ประเภทที่กลับมาจะต้องรู้ในเวลารวบรวมดังนั้นด้านล่างจะไม่ทำงานFunction declares an opaque return type, but the return statements in its body do not have matching underlying typesผิดพลาด:

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

0

กรณีการใช้งานที่เรียบง่ายที่ให้ความสนใจคือการเขียนฟังก์ชั่นทั่วไปสำหรับประเภทตัวเลข

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.