Home / Да изградбата Свифт Известувања

Да изградбата Свифт Известувања

glider-thumbМајк Аш

А програмерот во Разумното Лабс од ноќта,
и пилот на едрилица по ден
Економист од Универзитетот во Висконсин-Милвоки и на Универзитетот на Слоновата Орлеан

Оригиналното: Friday Q&A 2015-01-23: Let’s Build Swift Notifications

NSNotificationCenter е корисна API кое е сеприсутна во рамки на Apple, и често се гледа многу на употреба во рамките на нашите сопствени код. Јас претходно истражени градење NSNotificationCenterво Objective-C. Денес, сакам да го изгради во Свифт, но не само уште една reimplementation на истата идеја. Наместо тоа, јас ќе одам да се земе на API, и да ја направат побрзо, подобро, посилно, и да ги искористат сите убави работи Свифт има да ни го понуди.NSNotifications
NSNotifications се и едноставен и моќен, која е причината зошто тие се појавуваат толку често во рамки.Поточно, тие имаат неколку различни предности:

  1. Лабава спрега помеѓу известување испраќачите и примателите.
  2. Поддршка за повеќекратни приемници за едно известување.
  3. Поддршка за сопствени податоци за известување со помош на userInfo сопственост.

Има некои недостатоци, како и:

  1. Примање и регистрирање за известувања вклучува интеракција со единечна пример без јасна поврзаност со твоите часови.
  2. Тоа не е секогаш јасно што известувања се достапни за одредена класа.
  3. За известувања кои користат userInfo тоа не е секогаш јасно што копчињата се достапни во речникот.
  4. userInfo клучеви се динамички отчукува и бараат соработка помеѓу испраќачот и примачот, кои не може да се изрази во јазикот, и неуредна бокс / unboxing за типови на не-објект.
  5. Отстранување на регистрација известување бара експлицитно повик отстранување.
  6. Тешко е да се провери дали објекти се регистрирани за било кое дадено известување, која може да биде тешко да се debug.

Мојата цел во reimagining известувања во Свифт е за отстранување на овие проблеми.

Скица на API
Јавното лице на API е класа наречена ObserverSet Една ObserverSet пример е збир на набљудувачи заинтересирани во одредена известување испратено од страна на одреден објект. Наместо прогласување низа константи и посредување преку единечна, постоењето на известување е само јавна сопственост на класа:

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

На Void е тип на податоци што е испратен заедно со известување. Void означува чиста известување без дополнителни податоци. Испраќање на податоци е толку едноставно како обезбедување на видот:

       public let newURLObservers = ObserverSet<NSURL>()

Ова е известување дека обезбедува NSURL за сите набљудувачи секој пат тоа е испратена.

Повеќе парчиња податоци не се проблем со магијата на торка:

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

Ова обезбедува секој набљудувач со името и индекс на нов објект. Ако сакате да се направи овој поексплицитен, па дури и може да се даде имињата на параметрите:

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

За да се регистрирате набљудувач, додадете за одговорање на сетот набљудувач:

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

На add метод враќа во знак кој може да се користи за отстранување на набљудувачот:

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

Честа случај за известувања е да ги добијат со метод, и одјавуваат известување кога на пример е deallocated. Ова е лесно да се постигне со користење на варијанта add метод:

    object.newItemObservers.add(self, self.dynamicType.gotNewItem)
    func gotNewItem(name: String, index: Int) {
        println("Got a new item: (name) (index)")
    }

На self.dynamicType синтакса е малку опширниот и непотребни, но толерантна. Сет на набљудувачот ќе одржи слаба врска со self и автоматски да се отстрани набљудувач, кога на пример е deallocated, а во меѓувреме ќе се повика gotNewItem кога таа испраќа известување.

Испраќање на известувањето вклучува повикувајќи notify и донесување на соодветни параметри:

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

Ова го прави за еден навистина убав API. Минува низ недостатоците наведени погоре:

  1. Нема singleton вклучени. Секој (object, notification) пар е претставена со посебен набљудувач постави пример.
  2. Сите известувања на располагање за класа се јавни својства на таа класа.
  3. Експлицитни параметри се користи за да се помине податоци за набљудувачи. Тие можат да бидат именувани во кодот да биде јасно што точно се тие.
  4. Параметри нотификација се статички отчукува. Видовите се прикажани во сопственост и известување испраќачите на набљудувач во собата и приемници се проверуваат од страна на компајлерот. Сите видови се поддржани, без потреба за бокс.
  5. За општото случај каде набљудувач е отстранет кога deallocated, отстранување може да биде автоматски.
  6. Секој сет набљудувач одржува листа на записи кои можат да бидат прегледани во дебагерот.

Изгледа добро! Тогаш, како ние да се изгради?

Видови Функција набљудувач
Ајде да се претпостави дека параметрите на функцијата набљудувач се нарекуваат Parameters која е име јас ќе се користи за генерички тип во кодот. Во основа, тип функција е набљудувач е тогаш Parameters -> Void Сепак, за заеднички случај кога функцијата на набљудувачот е метод, тоа го прави тешко да се одржи слаба врска со предметот на набљудувачот и чистење на влез кога објектот е уништен. Кога ќе добие метод од класата, како self.dynamicType.gotNewItem видот на добиената функција е всушностTheClass -> Parameters -> Void Ќе се јавите на функција и го давате инстанца на класа, а потоа се враќа на нова функција за метод што се однесува на тој случај.

Со цел да се задржи сето организирани, ќе се сместат слаба врска со предметот на набљудувачот, а ние ќе се сместат функција на набљудувач во оваа форма повеќе. За поедноставна форма на add метод, функцијата може едноставно да се завитка во друга функција која фрла параметар и враќа оригиналната функција. Бидејќи објекти набљудувач може да бидат различни видови, ние ќе ги чувате како AnyObjectи функциите на набљудувач како AnyObject -> Parameters -> Void

Код
Тоа е време да се погледне на имплементација. Како и обично, го кодот е достапна на GitHub:

https://github.com/mikeash/SwiftObserverSet

Записи
Секоја позиција на набљудувач во собата е слабо одржа објект набљудувач и функција. Овој мал генерички класа снопови двете заедно, овозможувајќи за произволни параметар видови:

    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
        }
    }

Сет на набљудувачот да го користите на објектот и да функционира за да се јавите набљудувачи, и може да се провери на објектот за nil до отстранување на записите за deallocated објекти. Оваа класа е обележан public бидејќи тоа, исто така, ќе бидат вратени на тие што се јавуваа да се користи како параметар во remove метод.

Идеално, оваа класа ќе бидат вгнездени внатре ObserverSet Сепак, Свифт не дозволува вгнездени генерички типови, па затоа треба да биде посебен вид на највисоко ниво.

Постави набљудувач
На ObserverSet класа исто така, има генерички Parameters

    public class ObserverSet<Parameters> {

NSNotificationCenter е безбедно нишка, а оваа класа треба да биде, како и. Избрав да се користи сериски испраќање редица за да се постигне тоа:

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

Јас, исто така напиша функција брз помошник за тоа:

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

Со ова, тоа е толку едноставно како и пишувањето synchronized{ ...code... } на кодот кој го користи споделени податоци.

Во записите се чуваат во низа:

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

Соодветно говорејќи, ова треба да биде збир наместо низа. Сепак, поставува уште не се сите дека убаво да се користи кај Свифт, како што нема вграден во собата тип. Наместо тоа, ќе мора да го користат илиNSSet или користете Dictionary со Void тип вредност. Бидејќи сета набљудувачот обично ќе содржи неколку записи во повеќето, решив да одам за да биде појасно, наместо.

Свифт, исто така, инсистира на експлицитен јавни initializer, иако тоа е празна, како еден стандардно очигледно не се објавува:

        public init() {}

Која се грижи за поставување. Ајде да погледнеме во главните add метод, кој ги зема некој предмет и функција на набљудувачи:

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

Следно, изградба запис. Од видот на f сосема не одговара она ObserverSetEntry очекува, како што е во потрага по функција што го AnyObject додека ова го T Една мала адаптер се грижи за неусогласеност со тип улоги:

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

Тоа е жално да се заобиколат тип систем Свифт е на овој начин, но тоа добива се заврши работата.

Со влегувањето создадени, да го додадете во низа:

            synchronized {
                self.entries.append(entry)
            }

Конечно, на влез се враќа на повикувачот:

            return entry
        }

Други на add метод е мала адаптер:

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

Ова минува self како објект, едноставно затоа што тоа е погодно покажувачот што е гарантирано да остане жив. Бидејќи записите со nil предмети се отстранети, ова го прави за влез околу додека набљудувачот си ги постави. Функцијата донесен во втор параметар само игнорира параметар и враќа f

На remove метод се спроведува со филтрирање на низата за отстранување на влез појавување. СвифтArray тип нема remove метод, но filter метод се постигнува истата задача:

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

На notify Методот е едноставен, во принцип: за секој влез, јавете се на функција на набљудувачот и отстраните влез со nil објект набљудувач. Сепак, тоа е направено малку комплицирана од фактот декаentries треба да се пристапи од рамките synchronized но дека тоа е лоша идеја да се јавите функции набљудувач од таму, бидејќи тоа лесно може да доведе до ќор-сокак. За да се избегне овој проблем, стратегијата е да се соберат сите на функциите на набљудувач во еден локален низа, а потоа и да ги наречеме iterate надвор од synchronized блок. Еве на функцијата:

        public func notify(parameters: Parameters) {

Функциите за да се јавите се собираат во низа:

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

Имајте на ум дека од типот на функции не вклучува првичните AnyObject -> Да се задржи нешта едноставно, ќе го направи тој прв повик во рамките на synchronized блок, така што функциите собрани во низа се функциите финалето набљудувач објект набљудувачот веќе се применуваат.

Процесирањето над записи треба да се случи во synchronized блок:

            synchronized {
                for entry in self.entries {

Прескокнете записи со nil објект:

                if let object: AnyObject = entry.object {

Повикувајќи entry.f со објектот произведува во функција на набљудувачи за да се јавите:

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

Пред да се напушти synchronized блок, исчисти записи преку филтер на оние кои сега ги содржи nil

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

Сега дека функциите се собираат и synchronized блок е завршена, ние го нарекуваме функции набљудувачот:

          for f in toCall {
                f(parameters)
            }
        }

Тоа е тоа за ObserverSet класа. Да не заборавиме на завршната голема заграда:

  }

Белешка за Торките
Ќе се напомене дека ниту еден од ObserverSet код презентирани адреси случај кога има повеќеParameters на пример:

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

Сепак, таа работи во секој случај, со користење на кодот погоре. Што се случува?

Излегува дека Свифт не прави разлика меѓу функција која трае повеќе параметри и функција што трае една paremeter чиј тип е торка. На пример, овој код повикува функција со користење на двата начини:

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

    f(0, 0)

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

Ова значи дека ObserverSet код може да се запише за функции со еден параметар, и го добива поддршка за повеќе параметри за слободни. Постојат некои силни ограничувања на она што можете да направите во овие случаи (на пример, тоа е во суштина е невозможно да менувате било на параметри), но тоа функционира одлично во овој случај.

Заклучок
NSNotificationCenter е корисна, но Свифт јазик карактеристики се овозможи многу подобрена верзија.Генерики овозможи едноставна API кое сè уште овозможува сите случаи иста употреба, додека обезбедување на статички типови и тип проверка на двете страни.

Тоа е тоа за денес! Се врати следниот пат за повеќе застрашувачки експерименти. Во меѓувреме, во петок Q & A е управувано од страна на читателот идеи, па ако имаш нешто што би сакал да видам покриени овде, да го испрати во!

Дали ќе уживате во овој напис? Јас сум продажба на цела книга полна со нив. Тоа е на располагање за iBooks и поттикне, плус директно преземање во PDF и ePub формат. Тоа е исто така на располагање во хартија за старомодни.
banner