Gradimo Swift Obavijesti

glider-thumbMike Ash
A programmer at Plausible Labs by night,
and a glider pilot by day
Alumnus of the University of Wisconsin-Milwaukee and the Université d’Orléans

NSNotificationCenter je koristan API koji je sveprisutan u Appleov okvirima, a često i puno upotrebe vidi unutar vlastitog koda.I ranije istraživao izgradnju NSNotificationCenter u Objective-C. Danas, želim da ga graditi u Swift, ali ne samo još jedan reimplementacija iste ideje. Umjesto toga, ja ću uzeti API i čine ga brže, bolje, jače, i iskoristiti sve lijepo stvari Swift je da nam ponudi.

NSNotifications
NSNotifications su oba jednostavna i moćna, zbog čega se pojave tako često u okvirima. Naime, oni imaju nekoliko različite prednosti:

  1. Labave spojnica između pošiljatelja obavijesti i prijemnika.
  2. Podrška za više prijemnika za jednu obavijest.
  3. Podrška za prilagođene podatke o obavijest koristeći userInfo imovine.

Postoje neke nedostatke kao i:

  1. Slanje i registracije za obavijesti uključuje interakciju sa jednoplodnom instance bez jasan odnos sa svojim klasama.
  2. To nije uvijek jasno što obavijesti su dostupne za određenu klasu.
  3. Za obavijesti koje koriste userInfo to nije uvijek jasno šta ključevi su dostupni u rječniku.
  4. userInfo tipke su dinamički otkucao i potrebna saradnja između odašiljača i prijemnika koji se ne može izraziti na jeziku, i neuredan boks / unboxing za vrste koje nisu objekata.
  5. Uklanjanje registracija obavijest zahtijeva izričitu uklanjanje poziv.
  6. Teško je uvid koji objekti su registrirani za bilo koju obavijest, što može učiniti je teško debug.

Moj cilj u novu viziju toga obavještenja u Swift je za otklanjanje tih problema.

API Sketch
Javno lice API je klasa se zove ObserverSet Jedan ObserverSet primjer ima set promatrača zainteresirani za određenu obavijest poslao određenog objekta. Umjesto da se proglasi string konstante i posredovanje kroz singleton, postojanje obavijest je samo javne imovine klase:

  class ExampleNotificationSender {
        public let exampleObservers = ObserverSet<Void>()

U Void je tip podataka koji se šalje uz obavijest. Void označava čist obavijest bez dodatnih podataka. Slanja podataka je jednostavan kao pružanje tip:

        public let newURLObservers = ObserverSet<NSURL>()

Ovo je obavijest da pruža NSURL svim posmatračima svaki put je to poslao.

Višestruki komada podaci ne predstavljaju problem sa magijom tuples:

        public let newItemObservers = ObserverSet<(String, Int)>()

Ovo pruža svaki posmatrač sa imenom i indeks nove stavke. Ako želite da se ovo više eksplicitnim, možete čak dati parametre imena:

        public let newItemObservers = ObserverSet<(name: String, index: Int)>()

Da bi se registrovali posmatrač, dodajte povratni poziv na posmatrača set:

  object.exampleObservers.add{ println("Got an example notification") }
    object.newURLObservers.add{ println("Got a new URL: \($0)") }

U add metod vraća znak koji se može koristiti za uklanjanje posmatrač:

    let token = object.newItemObservers.add{ println("Got a new item named \($0) at index \($1)") }
    ...
    object.newItemObservers.remove(token)

Zajednički slučaj za obavijesti je da ih primi sa metodom, i odjavi obaveštenja kada se deallocated instance. Ovo je lako ostvariti pomoću varijanta add metod:

    object.newItemObservers.add(self, self.dynamicType.gotNewItem)

    func gotNewItem(name: String, index: Int) {
        println("Got a new item: \(name) \(index)")
    }

U self.dynamicType sintaksa je malo preopširan i suvišan, ali podnošljivo. Posmatrač skup održat će slaba referenca za self i automatski ukloniti posmatrača kada se deallocated instance, au međuvremenu će se pozivati gotNewItem kad god se šalje obavijest.

Slanje obavijest uključuje pozivanje notify i donošenje odgovarajuće parametre:

   exampleObservers.notify()
    newURLObservers.notify(newURL)
    newItemObservers.notify(name: newItemName, index: newItemIndex)

Ovo čini stvarno dobar API. Prolazeći kroz nedostatke gore navedenih:

  1. Nema singleton uključeni. Svaki (object, notification) par je zastupljena sa zasebnim posmatrač postaviti primjer.
  2. Sve obavijesti na raspolaganju za klasu su javne imovine te klase.
  3. Eksplicitne parametri se koriste za prolaz podacima promatrača. Oni mogu biti imenovani u kod da bi se jasno šta su oni.
  4. Parametri obavijest statički otkuca. Tipovi su navedene u posmatrač postavili imovine i obaveštenja pošiljaoci i prijemnika se pregledava od strane kompajler. Podržane su sve vrste, bez potrebe za boks.
  5. Za zajednički slučaj gdje se uklanja posmatrača kada deallocated, uklanjanje može biti automatski.
  6. Svaki posmatrač skup održava popis stavki koje se mogu pregledati u debugger.

Izgleda dobro! Kako, onda, da gradimo to?

Tipovi Observer Funkcija
Pretpostavimo da su parametri za posmatrača funkcija se nazivaju Parameters koji je ime ću koristiti za generičke vrste u kodu. U osnovi, tip posmatrača funkcija je onda Parameters -> Void Međutim, za zajednički slučaj gdje je posmatrač funkcija je metoda, to ga čini teško za održavanje slabe referencu na posmatrača objekta i brisanje unosa kada je objekat uništen.Kada dobijete metodu iz klase, kao što self.dynamicType.gotNewItem tip rezultira funkcija je zapravo TheClass -> Parameters -> Void Možete pozvati funkciju i prođe ga instancu klase, a onda se vraća novu funkciju za metodu koja se odnosi na taj primjer.

Da bi zadržali sve organizovali, mi ćemo pohraniti slaba referenca na posmatrača objekt, a mi ćemo čuvati posmatrač funkciju u ovom obliku duže. Za jednostavniji oblik add metode, funkcija može jednostavno biti umotana u neku drugu funkciju koja baca svoje parametar i vraća izvornu funkciju. Od posmatrač objekti mogu biti različitih vrsta, mi ćemo ih čuvati kaoAnyObject i posmatrača funkcije kao AnyObject -> Parameters -> Void

Kod
To je vrijeme da pogledamo realizacije. Kao i obično, kod je dostupan na GitHub:

https://github.com/mikeash/SwiftObserverSet

Unosi
Svaki unos u promatrača postavljen je slabo održana posmatrač objekata i funkciju. Ovaj mali generičke klase objedinjuje dva zajedno, što omogućava proizvoljne vrste parametara:

     public class ObserverSetEntry<Parameters> {
        private weak var object: AnyObject?
        private let f: AnyObject -> Parameters -> Void

        private init(object: AnyObject, f: AnyObject -> Parameters -> Void) {
            self.object = object
            self.f = f
        }
    }

Posmatrač set može koristiti objekt i funkcionirati da pozove promatrača, a može provjeriti objekt za nil za uklanjanje unosa za deallocated objektima. Ova klasa je označen public jer će se vratiti pozivatelja koji će se koristiti kao parametar removemetoda.

U idealnom slučaju, ova klasa će se ugnijezditi unutar ObserverSet Međutim, Swift ne dozvoljava gnijezde generički tipovi, tako da mora biti poseban tip top-level.

Observer Set
U ObserverSet razred također ima generički Parameters

    public class ObserverSet<Parameters> {

NSNotificationCenter je nit sigurno, a to bi trebalo da bude klase kao dobro. Izabrao sam koristiti serijski red depešu da postigne to:

        private var queue = dispatch_queue_create("com.mikeash.ObserverSet", nil)

Također sam napisao brzo funkciju pomagač za to:

        private func synchronized(f: Void -> Void) {
            dispatch_sync(queue, f)
        }

Uz ovaj, to je kao jednostavan i pisanje synchronized{ ...code... } na kod koji koristi zajedničku podataka.

Unosi se čuvaju u nizu:

       private var entries: [ObserverSetEntry<Parameters>] = []

Pravilno govoreći, ovo bi trebao biti skup, a ne niz. Međutim, setova nisu svi koji lijepo da se u Swift još, jer nema izgrađen-in set tipa. Umjesto toga, morate koristiti ili NSSet ili koristite Dictionary s Void tip vrijednosti. Od posmatrač će setova obično sadrži nekoliko unosa u većini, odlučio sam otići zbog jasnoće umjesto toga.

Swift također insistira na izričit javnom inicijalizator, iako je prazan, kao default jedan očigledno se ne objavljuju:

        public init() {}

Koji se brine o setup. Pogledajmo na glavnom add metod, koji se posmatrača objekta i funkcija:

        public func add<T: AnyObject>(object: T, _ f: T -> Parameters -> Void) -> ObserverSetEntry<Parameters> {

Zatim, izgraditi unos. Tipa f ne poklapaju kako ObserverSetEntry očekuje, kako je u potrazi za funkciju koja uzima AnyObjectdok ovo traje T Mala adapter vodi računa o neusklađenosti sa tip cast:

            let entry = ObserverSetEntry<Parameters>(object: object, f: { f($0 as T) })

To je nesreću da zaobiđu Swift tip sistema na ovaj način, ali to postaje obavio posao.

Ulaskom stvorio, dodajte ga u niz:

            synchronized {
                self.entries.append(entry)
            }

Konačno, unos se vraća na pozivatelja:

            return entry
        }

Druga add metoda je mali adapter:

        public func add(f: Parameters -> Void) -> ObserverSetEntry<Parameters> {
            return self.add(self, { ignored in f })
        }

Ovo prolazi self kao objekt jednostavno zato što je zgodan pokazivač koji se garantuje da ostane živ. Od unosa sa nil objekti se uklanjaju, ovo drži unos oko dok posmatrača sebi postavila. U prošli u drugi parametar funkcija samo ignoriše svoje parametar i vraća f

U remove metoda se provodi filtriranjem niz za uklanjanje odgovarajući unos. Swift Array tip nema remove metodu, ali je filternačin postiže isti zadatak:

        public func remove(entry: ObserverSetEntry<Parameters>) {
            synchronized {
                self.entries = self.entries.filter{ $0 !== entry }
            }
        }

U notify metoda je jednostavna, u principu: za svaki unos, pozovite posmatrač funkciju, i ukloniti bilo koju stavku s nilposmatrač objekt. Međutim, to je napravio malo komplicira činjenica da entries se treba pristupiti iznutra synchronized ali to je loša ideja da pozove posmatrača funkcije od tamo, jer bi to moglo lako dovesti do zastoja. Da biste izbjegli taj problem, strategija je prikupiti sve funkcije posmatrača u lokalnoj nizu, a zatim ponoviti i da ih pozvati izvan synchronized bloka. Evo funkcija:

        public func notify(parameters: Parameters) {

Funkcije pozvati se prikupljaju u nizu:

            var toCall: [Parameters -> Void] = []

Imajte na umu da je tip funkcija ne uključuje početne AnyObject -> Da bi stvari jednostavno, mi ćemo učiniti taj prvi poziv u okviru synchronized bloka, tako da funkcije prikupljeni u nizu su konačni posmatrač funkcije s posmatrač objekt već primjenjuje.

Ponavljanjem preko stavke treba da se desi u synchronized bloku:

            synchronized {
                for entry in self.entries {

Preskočite unose sa nil objekta:

                    if let object: AnyObject = entry.object {

Pozivanje entry.f sa objektom proizvodi posmatrač funkciju da pozove:

                        toCall.append(entry.f(object))
                    }
                }

Prije nego što napusti synchronized blok, očistiti stavke filtriranjem od onih koje sada sadrže nil

                self.entries = self.entries.filter{ $0.object != nil }
            }

Sada kada je funkcija se prikupljaju i synchronized blok je završena, što nazivamo funkcije posmatrač:

            for f in toCall {
                f(parameters)
            }
        }

To je to za ObserverSet klasu. Nemojmo zaboraviti na zatvaranje brace:

     }

Napomena o Tuple
Vi ćete imajte na umu da nijedan od ObserverSet kod predstavio adresama slučaju kada postoji više Parameters na primjer:

        public let newItemObservers = ObserverSet<(String, Int)>()

Međutim, u svakom slučaju radi se, koristeći kod gore. Ono što se događa?

Ispostavilo se da je Swift ne pravi razliku između funkciju koja uzima više parametara i funkcija koja traje jedan paremeter čiji tip je tuple. Na primjer, ovaj kod poziva funkciju pomoću oba načina:

    func f(x: Int, y: Int) {}

    f(0, 0)

    let params = (0, 0)
    f(params)

To znači da se ObserverSet kod može biti napisan na funkcije s jednog parametra, a to dobiva podršku za više parametara za besplatno. Postoje neki jaki ograničenja na ono što možete učiniti u ovim slučajevima (na primjer, to je u suštini nemoguće izmijeniti bilo koji od parametara), ali radi dobro u ovom slučaju.

Zaključak
NSNotificationCenter je zgodan, ali Swift funkcije jezika omogućiti znatno poboljšanu verziju. Generika omogućavaju jednostavan API koji i dalje dopušta sve iste slučajeve upotrebe dok pruža statički vrsta i tip provjeru na obje strane.

To je to za danas! Vrati se sljedeći put za više zastrašujuće eksperimente. U međuvremenu u petak Pitanja i pokreće čitač ideje, tako da ako imate nešto što bih da vidim ovdje pokrivene, poslati ga u!

Jeste li uživati ovaj članak? Ja prodajem čitavu knjigu puna njih. To je dostupan za iBooks i Kindle, plus direktni download u PDF i ePub formatu. To je također dostupan u radu za staromodne. Kliknite ovdje za više informacija.

Leave a Reply

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