Opaque Types in Swift, auch bekannt als Opaque Return Types, wurden mit Swift 5.1 eingeführt. Sie vereinfachen die Verwendung von Protokollen und Generics, insbesondere bei Protokollen mit Associated Types. Dieser Artikel erklärt, was Opaque Types sind und wie man sie verwendet.
Probleme bei der Verwendung von Protokollen und Generics
Wenn eine Funktion ein Protokoll mit einem Associated Type zurückgibt, ist es schwierig, den konkreten Datentyp des Rückgabewerts zu bestimmen. Beispiel:
protocol P {
associatedtype Value
var value: Value { get }
init(value: Value)
}
struct S1 : P {
var value: Int
}
struct S2: P {
var value: String
}
// Fehler: Rückgabetyp kann nicht abgeleitet werden
// func foo() -> P { return S1() }
Die Verwendung von Generics hat ebenfalls Einschränkungen, da der Datentyp für Parameter oder Rückgabewerte der Funktion angegeben werden muss:
func foo<T: P>(value: T.Value) -> T { return T(value: value) }
let s1: S1 = foo(value: 10)
let s2: S2 = foo(value: "ahihi")
Die Lösung mit Opaque Types
Opaque Types, deklariert mit dem Schlüsselwort some
, ermöglichen es einer Funktion, einen „versteckten“ Datentyp zurückzugeben, der nur das Protokoll einhalten muss, ohne konkret definiert zu werden.
func foo() -> some P { return S1(value: 11) }
Im obigen Beispiel steht some P
für einen konkreten Datentyp, der das Protokoll P
erfüllt. Der Compiler leitet diesen Typ automatisch ab.
Opaque Types erlauben jedoch nur die Rückgabe eines einzigen Datentyps innerhalb derselben Funktion:
// Fehler: Es können nicht mehrere verschiedene Datentypen zurückgegeben werden
// func bar(_ x: Int) -> some P {
// if x > 10 { return S1(value: 12) }
// else { return S2(value: "ahihi") }
// }
Opaque Types und PATs (Protocols with Associated Types)
Opaque Types sind besonders nützlich bei der Arbeit mit PATs. Sie ermöglichen die Verwendung von PATs als Rückgabetypen, ohne den konkreten Datentyp kennen zu müssen. Dies macht den Code flexibler und unabhängig von Bibliotheksversionen.
func giveMeACollection() -> some Collection { return [1, 2, 3] }
let collection = giveMeACollection()
print(collection.count) // 3
Opaque Types und konkrete Typen
Wenn eine Funktion mit einem Opaque Type einen konkreten Typ zurückgibt, stellt der Compiler sicher, dass Aufrufe dieser Funktion immer denselben Typ zurückgeben.
func fooo() -> some Equatable { return 5 }
let f1 = fooo()
let f2 = fooo()
print(f1 == f2) // true
Dies gilt jedoch nicht für PATs. Jede Funktion mit einem Opaque Type und PATs gibt einen eigenen Datentyp zurück, selbst wenn beide dasselbe Protokoll erfüllen.
Opaque Types und generische Platzhalter
Die Kombination von Opaque Types mit generischen Platzhaltern ermöglicht die Erstellung von Funktionen, die sowohl vielseitig mit verschiedenen Datentypen umgehen können als auch keinen konkreten Typ zurückgeben müssen.
Warum Opaque Types verwenden?
Opaque Types bieten Flexibilität bei der Arbeit mit Protokollen, insbesondere mit PATs. Sie vermeiden die Notwendigkeit, konkrete Datentypen zu definieren, reduzieren die Abhängigkeit von Bibliotheksversionen und erhöhen die Wiederverwendbarkeit von Code.
Opaque Types in SwiftUI
Opaque Types werden häufig in SwiftUI verwendet. Die Eigenschaft body
des View-Protokolls hat den Typ some View
, wodurch jeder Datentyp zurückgegeben werden kann, der das View-Protokoll erfüllt. Dies vereinfacht die Erstellung von Benutzeroberflächen. Die Verwendung von some View
vermeidet komplexe Datentypen, wenn eine View mehrere verschachtelte Ebenen hat.