Swift Language
Generics
Szukaj…
Uwagi
Kod ogólny umożliwia pisanie elastycznych funkcji i typów wielokrotnego użytku, które mogą współpracować z dowolnym typem, z zastrzeżeniem określonych wymagań. Możesz pisać kod, który unika powielania i wyraża swoją intencję w jasny, abstrakcyjny sposób.
Generics to jedna z najpotężniejszych funkcji Swift, a duża część standardowej biblioteki Swift jest zbudowana z ogólnego kodu. Na przykład typy
Array
iDictionary
Swift są kolekcjami rodzajowymi. Możesz utworzyć tablicę zawierającą wartościInt
lub tablicę zawierającą wartościString
, a nawet tablicę dla dowolnego innego typu, który można utworzyć w Swift. Podobnie można utworzyć słownik do przechowywania wartości dowolnego określonego typu i nie ma ograniczeń co do tego, jaki może być ten typ.
Ograniczanie ogólnych typów symboli zastępczych
Możliwe jest wymuszenie parametrów typu klasy ogólnej do wdrożenia protokołu , na przykład Equatable
class MyGenericClass<Type: Equatable>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func valueEquals(anotherValue: Type) -> Bool{
return self.value == anotherValue
}
}
Ilekroć tworzymy nową MyGenericClass
, parametr type musi implementować protokół Equatable
(zapewniając, że parametr type można porównać z inną zmienną tego samego typu za pomocą ==
)
let myFloatGeneric = MyGenericClass<Double>(value: 2.71828) // valid
let myStringGeneric = MyGenericClass<String>(value: "My String") // valid
// "Type [Int] does not conform to protocol 'Equatable'"
let myInvalidGeneric = MyGenericClass<[Int]>(value: [2])
let myIntGeneric = MyGenericClass<Int>(value: 72)
print(myIntGeneric.valueEquals(72)) // true
print(myIntGeneric.valueEquals(-274)) // false
// "Cannot convert value of type 'String' to expected argument type 'Int'"
print(myIntGeneric.valueEquals("My String"))
Podstawy generyczne
Ogólne są symbolami zastępczymi dla typów, umożliwiając pisanie elastycznego kodu, który można zastosować do wielu typów. Zaletą używania generycznych w porównaniu z Any
jest to, że wciąż pozwalają one kompilatorowi na wymuszanie silnego bezpieczeństwa typu.
Ogólny symbol zastępczy jest zdefiniowany w nawiasach kątowych <>
.
Funkcje ogólne
W przypadku funkcji ten symbol zastępczy jest umieszczony po nazwie funkcji:
/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
return arc4random_uniform(2) == 0 ? a : b
}
W tym przypadku ogólnym symbolem zastępczym jest T
Kiedy przychodzisz wywołać tę funkcję, Swift może wywnioskować dla ciebie typ T
(ponieważ po prostu działa jako symbol zastępczy dla rzeczywistego typu).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
Tutaj przekazujemy dwie liczby całkowite do funkcji. Dlatego Swift wnioskuje o T == Int
- stąd sygnatura funkcji jest wywnioskowana jako (Int, Int) -> Int
.
Ze względu na silne bezpieczeństwo typu oferowane przez generyczne - zarówno argumenty, jak i zwracana funkcja muszą być tego samego typu. Dlatego następujące elementy nie zostaną skompilowane:
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
Rodzaje ogólne
Aby używać ogólnych z klasami , strukturami lub wyliczeniami , możesz zdefiniować ogólny symbol zastępczy po nazwie typu.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
Ta ogólna zastępczy będzie wymagać typ, gdy przyjdziesz do korzystania z klasy Bar
. W takim przypadku można to wywnioskować z inicjalizatora init(baz:T)
.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
Tutaj zakłada się, że ogólny symbol zastępczy T
jest typu String
, tworząc w ten sposób instancję Bar<String>
. Możesz także wyraźnie określić typ:
let bar = Bar<String>(baz: "a string")
W przypadku użycia z typem podany ogólny symbol zastępczy zachowa swój typ przez cały okres istnienia danej instancji i nie można go zmienić po inicjalizacji. Dlatego podczas uzyskiwania dostępu do właściwości baz
zawsze będzie ona typu String
dla danego wystąpienia.
let str = bar.baz // of type String
Przekazywanie typów ogólnych
Kiedy przychodzi ci do przekazania ogólne typy, w większości przypadków musisz wyraźnie powiedzieć o ogólnym typie obiektu zastępczego, którego oczekujesz. Na przykład jako dane wejściowe funkcji:
func takeABarInt(bar:Bar<Int>) {
...
}
Ta funkcja akceptuje tylko Bar<Int>
. Próba przekazania w instancji Bar
której ogólnym typem zastępczym nie jest Int
spowoduje błąd kompilatora.
Ogólne nazewnictwo symboli zastępczych
Ogólne nazwy zastępcze nie ograniczają się tylko do pojedynczych liter. Jeśli dany symbol zastępczy reprezentuje sensowną koncepcję, należy nadać mu opisową nazwę. Na przykład Array
Swift ma ogólny symbol zastępczy o nazwie Element
, który określa typ elementu danej instancji Array
.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
Przykłady klas ogólnych
Klasa ogólna z parametrem Type
class MyGenericClass<Type>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func setValue(value: Type){
self.value = value
}
}
Możemy teraz tworzyć nowe obiekty za pomocą parametru type
let myStringGeneric = MyGenericClass<String>(value: "My String Value")
let myIntGeneric = MyGenericClass<Int>(value: 42)
print(myStringGeneric.getValue()) // "My String Value"
print(myIntGeneric.getValue()) // 42
myStringGeneric.setValue("Another String")
myIntGeneric.setValue(1024)
print(myStringGeneric.getValue()) // "Another String"
print(myIntGeneric.getValue()) // 1024
Generyczne można również tworzyć z wieloma parametrami typu
class AnotherGenericClass<TypeOne, TypeTwo, TypeThree>{
var value1: TypeOne
var value2: TypeTwo
var value3: TypeThree
init(value1: TypeOne, value2: TypeTwo, value3: TypeThree){
self.value1 = value1
self.value2 = value2
self.value3 = value3
}
func getValueOne() -> TypeOne{return self.value1}
func getValueTwo() -> TypeTwo{return self.value2}
func getValueThree() -> TypeThree{return self.value3}
}
I używane w ten sam sposób
let myGeneric = AnotherGenericClass<String, Int, Double>(value1: "Value of pi", value2: 3, value3: 3.14159)
print(myGeneric.getValueOne() is String) // true
print(myGeneric.getValueTwo() is Int) // true
print(myGeneric.getValueThree() is Double) // true
print(myGeneric.getValueTwo() is String) // false
print(myGeneric.getValueOne()) // "Value of pi"
print(myGeneric.getValueTwo()) // 3
print(myGeneric.getValueThree()) // 3.14159
Ogólne dziedziczenie klas
Klasy ogólne można dziedziczyć:
// Models
class MyFirstModel {
}
class MySecondModel: MyFirstModel {
}
// Generic classes
class MyFirstGenericClass<T: MyFirstModel> {
func doSomethingWithModel(model: T) {
// Do something here
}
}
class MySecondGenericClass<T: MySecondModel>: MyFirstGenericClass<T> {
override func doSomethingWithModel(model: T) {
super.doSomethingWithModel(model)
// Do more things here
}
}
Użycie ogólnych w celu uproszczenia funkcji macierzy
Funkcja, która rozszerza funkcjonalność tablicy, tworząc obiektową funkcję usuwania.
// Need to restrict the extension to elements that can be compared.
// The `Element` is the generics name defined by Array for its item types.
// This restriction also gives us access to `index(of:_)` which is also
// defined in an Array extension with `where Element: Equatable`.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
if let index = self.index(of: element ) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}
Stosowanie
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
Użyj funkcji, aby usunąć element bez potrzeby indeksowania. Wystarczy przekazać obiekt do usunięcia.
myArray.remove(2)
print(myArray)
// Prints [1,3]
Użyj ogólnych, aby zwiększyć bezpieczeństwo typu
Weźmy ten przykład bez użycia ogólnych
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
Deklaracja protokołu wydaje się w porządku, chyba że faktycznie z niej korzystasz.
let myTestObject = TestObject.from(myJson) as? TestObject
Dlaczego musisz rzutować wynik na TestObject
? Ze względu na Any
typ zwrotu w deklaracji protokołu.
Używając ogólnych, możesz uniknąć tego problemu, który może powodować błędy w czasie wykonywania (i nie chcemy ich mieć!)
protocol JSONDecodable {
associatedtype Element
static func from(_ json: [String: Any]) -> Element?
}
struct TestObject: JSONDecodable {
static func from(_ json: [String: Any]) -> TestObject? {
}
}
let testObject = TestObject.from(myJson) // testObject is now automatically `TestObject?`
Zaawansowane ograniczenia typu
Możliwe jest określenie kilku ograniczeń typów dla rodzajów ogólnych za pomocą klauzuli where
:
func doSomething<T where T: Comparable, T: Hashable>(first: T, second: T) {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
Prawidłowe jest również wpisanie klauzuli where
po liście argumentów:
func doSomething<T>(first: T, second: T) where T: Comparable, T: Hashable {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
Rozszerzenia można ograniczyć do typów spełniających warunki. Ta funkcja jest dostępna tylko dla instancji, które spełniają warunki typu:
// "Element" is the generics type defined by "Array". For this example, we
// want to add a function that requires that "Element" can be compared, that
// is: it needs to adhere to the Equatable protocol.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
// We could also use "self.index(of: element)" here, as "index(of:_)"
// is also defined in an extension with "where Element: Equatable".
// For the sake of this example, explicitly make use of the Equatable.
if let index = self.index(where: { $0 == element }) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}