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 i Dictionary Swift są kolekcjami rodzajowymi. Możesz utworzyć tablicę zawierającą wartości Int lub tablicę zawierającą wartości String , 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.

Źródło: Swift Programming Language firmy Apple

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ólnesymbolami 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")
        }
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow