Típus Törlés a Swift

Eredeti Mike Ash, kifüggesztett- https://www.mikeash.com/pyblog/friday-qa-2017-12-08-type-erasure-in-swift.html

Lehet, hogy hallott a kifejezés típusa törlését. Lehet, hogy van is használt típus-törölte típusok a standard könyvtár például AnySequence. De mi is pontosan ez a típus törlését, illetve hogyan tedd magad? Ebben a cikkben fogom felfedezni típus törlését, miért akarsz, hogy megtörténjen, egy téma által javasolt Lorenzo Boaro.

Motiváció
Vannak idők, amikor el szeretné rejteni egy mögöttes típus a külső felhasználók számára. Néha csak az a kérdés bujkál a megvalósítás részleteit. Más esetekben ez megakadályozza a statikus típus terjed a codebase, vagy lehetővé teszi különböző típusú tud együttműködni. Típus törlés a folyamat eltávolítása egy adott típusú jegyzet mellett egy általánosabb.

Protokollok, vagy absztrakt superclasses tekinthető egy nagyon egyszerű formája típus törlését. Vegye NSString példaként. Soha nem volt egy egyszerű NSString például; mindig olyan eset, amikor néhány konkrét alosztály, általában magán. Ez többnyire rejtve, de az API az összes munka NSString. A különböző alosztályok lehet használni anélkül, hogy tudom mi az, de anélkül, hogy megszórjuk a kód a típus.

Fejlettebb technikák lesz hasznos, ha foglalkozó Swift generikus, jegyzőkönyvek kapcsolódó típusok. Swift nem teszi lehetővé az ilyen protokollok, mint a konkrét típusok. Például, ha akarsz írni egy kódot, hogy elfogadja, bármilyen Sorrendben az Int értékek, nem tud írni, ezt:

   func f(seq: Sequence<Int>) { ...

Ez nem jogi Swift. Is specializálódtak a generikus típusok, de nem protokollok. Jó ez a generikus:

    func f<S: Sequence>(seq: S) where S.Element == Int { ...

Néha ez remekül működik, de vannak olyan esetek, ahol ez lehet zavaró. Gyakran nem csak hozzá generikus egy helyen: egy általános funkció megköveteli, hogy mások is lehet általános, amelyek előírják még több… Még rosszabb, nem használhatja ezt a visszatérési értéket, vagy tulajdonságok. Ez nem úgy működik, ahogy te akarod:

    func g<S: Sequence>() -> S where S.Element == Int { ...

Keresünk valamit, ahol g vissza megfelelő típus, de ehelyett ez lehetővé teszi, hogy a hívó, hogy melyik típus akar, g, akkor szükséges, hogy megfelelő értéket.

Swift biztosít AnySequence (Bármilyen Sorrendben)  írja, hogy megoldja ezt a problémát. AnySequence (Bármilyen Sorrendben) pakolások tetszőleges Sequence (Sorrendben), majd törli a típus, amely hozzáférést át a AnySequence (Bármilyen Sorrendben) írja be helyette. Segítségével meg tudjuk átírni f es g:

    func f(seq: AnySequence<Int>) { ...

    func g() -> AnySequence<Int> { ...

A generikus eltűnnek, illetve a konkrét típusok vannak még rejtett. Van egy kis kód bonyolultsága, valamint futási költség attól, hogy amibe az értékek AnySequence, de a kód szép tiszta.

A Swift standard könyvtárban van egy csomó Any típusok, mint például a AnyCollection, AnyHashable, AnyIndex. Hasznos lehet, hogy saját végig menni a saját generikus protokollt, vagy használja a technikát, hogy egyszerűsítse a kódot, ha foglalkoznak velük. Fedezzük fel a különböző módon lehet elérni típus törlését.

Típus Törlését Osztályok
Kell befejeznünk egy közös funkció több típusú nélkül mutogatta azokat a típusokat. Ez úgy hangzik, mint egy superclass-alosztály kapcsolat, sőt tudjuk használni alosztályok, hogy készüléktípus törlését. A superclass tegye egy API, hogy vak, hogy az alapul szolgáló végrehajtási típusú, valamint egy alosztály végre, hogy az API, a tudás, a mögöttes típus.

Lássuk, hogy a saját verzióját AnySequence nézne ki ezzel a technikával. Én úgy hívom MAnySequence bele az én nevem:

    class MAnySequence<Element>: Sequence {

Ez az osztály is szükségünk lesz egy iterator írja be, hogy visszatér a makeIterator módszer. Meg kell végre típus törlését kétszer, hogy elrejthessük a mögöttes Sequence típusát, valamint az Iterator típus. Ez a belső Iterator osztály megfelel Iterator Jegyzőkönyv, illetve valósít meg a következő módszer, hogy hívja fatalError. Swift nem beépített támogatása absztrakt osztályok, így ez elég kell, hogy legyen:

        class Iterator: IteratorProtocol {
            func next() -> Element? {
                fatalError("Must override next()")
            }
        }

MAnySequence kap egy hasonló végrehajtása makeIterator. Felhívja fatalError, hogy ösztönözze a alosztály, hogy bírálja felül:

       func makeIterator() -> Iterator {
            fatalError("Must override makeIterator()")
        }
    }

Ez a típus-törölte nyilvános API. A magán-végrehajtási alosztályok. A nyilvános osztály kódokat az elem típus, de a magán-végrehajtási osztály kódokat a sorozat típus befejeződik:

    private class MAnySequenceImpl<Seq: Sequence>: MAnySequence<Seq.Element> {

Ennek az osztálynak szüksége van egy belső alosztály a belső Iterator osztály felülről:

      class IteratorImpl: Iterator {

Befejeződik egy példányát a sorozatok Iterator típus:

            var wrapped: Seq.Iterator

            init(_ wrapped: Seq.Iterator) {
                self.wrapped = wrapped
            }

Végrehajtásakor a next, hogy hívja át, hogy a becsomagolt iterator:

            override func next() -> Seq.Element? {
                return wrapped.next()
            }
        }

Hasonlóképpen, MAnySequenceImpl pakolások egy példányát a sorrend:

        var seq: Seq

        init(_ seq: Seq) {
            self.seq = seq
        }

Végrehajtásakor makeIterator, hogy egy iterator a becsomagolt sorrend, majd csomagolja iterator a IteratorImpl:

        override func makeIterator() -> IteratorImpl {
            return IteratorImpl(seq.makeIterator())
        }

    }

Szükségünk van egy módja annak, hogy valóban létre ezeket a dolgokat. Statikus módszer a MAnySequence létrehoz egy példányát MAnySequenceImpl értéket a hívó, mint egy MAnySequence:

    extension MAnySequence {
        static func make<Seq: Sequence>(_ seq: Seq) -> MAnySequence<Element> where Seq.Element == Element {
            return MAnySequenceImpl<Seq>(seq)
        }
    }

Gyártási kód, akkor talán azt akarja, hogy tiszta, hogy ez egy kicsit segítségével egy extra szintű indirekciónak, így MAnySequence nyújthat egy initializer helyett.

Próbáljuk ki:

    func printInts(_ seq: MAnySequence<Int>) {
        for elt in seq {
            print(elt)
        }
    }

    let array = [1, 2, 3, 4, 5]
    printInts(MAnySequence.make(array))
    printInts(MAnySequence.make(array[1 ..< 4]))

Működik!

Típus Törlés A Funkciók
Azt akarjuk, hogy tegye ki funkcionalitás több típusú nélkül mutogatta azokat a típusokat. Egy természetes megközelítés ez tárolni funkciók, akinek az aláírás csak azokat a típusokat akarjuk leplezni. A funkció szervek létre lehet hozni egy olyan környezetben, ahol az alapul szolgáló végrehajtási típusok ismert.

Nézzük meg, hogy MAnySequence néz, ez a megközelítés. Úgy kezdődött, hasonló az előző végrehajtását, bár ez lehet egy struct inkább, mint egy osztály, mert ez csak egy buta, konténer, nincs örökség:

    struct MAnySequence<Element>: Sequence {

Mint korábban, szüksége van egy Iterator, hogy visszatérhet. Ez is egy struktúra, valamint tartalmaz egy tárolt vagyon, amely egy olyan funkció, hogy nem veszi paramétereket, majd visszatér egy Elem?, ami az aláírás használható a következő módszer IteratorProtocol. Akkor végre IteratorProtocol, hogy hívja ezt a funkciót:

        struct Iterator: IteratorProtocol {
            let _next: () -> Element?

            func next() -> Element? {
                return _next()
            }
        }

MAnySequence maga hasonló: tartalmaz egy tárolt vagyon, amely egy olyan funkció, hogy nem veszi érveit, majd visszatér egy Iterator. Sorrendben ezután által végrehajtott hív át, hogy ezt a funkciót:

        let _makeIterator: () -> Iterator

        func makeIterator() -> Iterator {
            return _makeIterator()
        }

MAnySequence esetben az, ahol a varázslat történik. Vesz, tetszőleges Sequence, mint a paramétert:

        init<Seq: Sequence>(_ seq: Seq) where Seq.Element == Element {

Akkor van szüksége, hogy lezárja a funkció ez a sorozat a funkció:

            _makeIterator = {

Honnan tudjuk, hogy egy iterator itt? Kezdjük azzal, hogy megkérdezem seq, hogy:

                var iterator = seq.makeIterator()

Akkor csomagolja iterator a Iterator. A _next a funkció csak hívj iterator a következő módszert:

                return Iterator(_next: { iterator.next() })
            }
        }

    }

Itt van egy kis kódot hogy használja:

    func printInts(_ seq: MAnySequence<Int>) {
        for elt in seq {
            print(elt)
        }
    }

    let array = [1, 2, 3, 4, 5]
    printInts(MAnySequence(array))
    printInts(MAnySequence(array[1 ..< 4]))

Ez működik is!

Ez a funkció alapú megközelítés típus törlését lehet különösen szép, ha kell csomagolni egy kis mennyiségű a funkciója, mint az része egy nagyobb típus, nem kell külön osztályok végrehajtási a teljes funkció bármilyen típusú vagy törlése.

Például, tegyük fel, hogy szeretnék írni egy kódot, hogy működik a különböző gyűjtemény típusú, de valójában szüksége van arra, hogy azok a gyűjtemények, egy gróf, egy nulla-alapú egész alsó indexet. Például ez lehet egy táblázat adatainak forrása. Lehet, hogy akkor így néz ki:

    class GenericDataSource<Element> {
        let count: () -> Int
        let getElement: (Int) -> Element

        init<C: Collection>(_ c: C) where C.Element == Element, C.Index == Int {
            count = { c.count }
            getElement = { c[$0 - c.startIndex] }
        }
    }

Akkor a többi kódot GenericDataSource könnyen hívás count() getElement() műveletet végez rajta, hogy elmúlt-gyűjtemény, anélkül, hogy a gyűjtemény típusú szennyező GenericDataSource általános paraméterek.

Következtetés
Típus törlésének egy hasznos technika megállítani a vírus terjedését generikus be a kódot, vagy csak tartja felületek egyszerű. Ez megvalósítható a csomagolás az alapul szolgáló típus, olyan módon, amely elválasztja az API a funkciót. Ezt meg lehet tenni egy absztrakt nyilvános superclass, valamint egy privát alosztály, vagy meg lehet csinálni, a csomagolás az API funkciók. Típus törlés a funkció különösen hasznos, egyszerű esetekben, ahol csak egy pár darab működését.

A Swift standard könyvtár biztosítja több típusú törölni fajta, hogy kihasználják. Például, AnySequence pakolások a Sorrend, ahogy a neve is jelzi, lehetővé teszi, hogy halad át a sorozatot, anélkül, hogy tudom, hogy a típus. AnyIterator a társa, hogy ez a típus, amely egy típus-törölte iterator. AnyHashable nyújt típus-törölte hozzáférést Hashable típusok. Van még néhány a különböző protokollok gyűjteménye. Keresés a dokumentáció Bármilyen látni azokat. A standard könyvtár is használ, írja be törlését részeként a Codable API: KeyedEncodingContainer, KeyedDecodingContainer vagy típus-törölte papír körül, a megfelelő konténer protokollokat használják, hogy lehetővé teszik, Kódoló es Dekódoló megvalósítások biztosítani konténerek, anélkül, hogy bele a konténer típusok be az API.

 

Vissza a főoldalra

Leave a Reply

Your email address will not be published. Required fields are marked *