Sledovanie udalostí na strane klienta sa všeobecne delí do troch kategórií:

  • Sledovanie pomocou kódu
  • Vizuálne sledovanie
  • Automatické sledovanie

Tento článok predstavuje najmä myšlienky a proces implementácie automatického sledovania.

1. Sledovanie pomocou kódu

Sledovanie pomocou kódu znamená zavolať rozhranie na každom mieste, ktoré chceme sledovať, a priamo nahrať údaje udalosti. Väčšina analytických služieb tretích strán ponúka pohodlné api na takéto sledovanie.

Vzniká však problém: sledovací kód je roztrúsený v obchodnej logike a je s ňou úzko previazaný. Keď sa zmenia obrazovky alebo obchodné toky, nie je jasné, čo urobiť so starými udalosťami ani či treba nové. S rastúcim počtom udalostí sa údržba roztrúseného kódu stáva nočnou morou. Makrá a továrenské triedy môžu kód zjednodušiť, ale podstatu nemenia.

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    // 埋点
    APE(kEvent4001);
}

2. Vizuálne sledovanie

Vizuálne sledovanie nastavuje zberné body pomocou vizuálneho nástroja. Klient automaticky spracuje konfiguráciu a nahlási udalosti, čím vzniká takzvané „bezstopové sledovanie“. Reprezentatívnym open-source riešením je Mixpanel.

3. Automatické sledovanie

Automatické sledovanie, nazývané aj sledovanie všetkých udalostí, v skutočnosti neznamená neprítomnosť inštrumentácie. Zbiera a hlási všetky udalosti a server následne vyfiltruje užitočné údaje.

Pri ďalšej optimalizácii môže server poslať konfiguračný súbor, aby udalosti filtroval samotný klient.

Kvôli KPI som na základe tejto myšlienky znovu vynašiel koleso a vytvoril WKTrackingData.

Tu zaznamenávam problémy a nápady z implementácie WKTrackingData.

Myšlienka implementácie je jednoduchá. Kód je usporiadaný takto:

Pôvodná architektúra WKTrackingData: správca, cesty udalostí a moduly AOP

Tu WKTrackingDataManager spravuje všetky údaje a konfiguráciu sledovania udalostí.

Pôvodný príklad použitia konfigurácie sledovania udalostí

WKTrackingDataViewPathHelper vytvára hodnoty event_path.

Modul AOP zodpovedá za sledovanie každej udalosti.

Podrobný návod nájdete na GitHube.

Ako jednoznačne identifikovať udalosť?

Sú tu dve otázky: ako udalosť vyjadriť a ako zabezpečiť jej jedinečnosť?

Vyjadrenie udalosti

Udalosti sa vyjadrujú pomocou event_path. Pri objekte responder, ktorý udalosť spustí, je hlavnou myšlienkou nasledovať jeho reťazec responderov a vytvoriť path tohto reťazca.

Výsledná path respondera vyzerá takto:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Jednoznačná identifikácia udalosti

V niektorých obchodných situáciách ten istý button alebo iný ovládací prvok predstavuje po zmene svojich vlastností rôzne obchodné udalosti.

Napríklad button na domovskej obrazovke môže pred prihlásením zobrazovať Klepnite a prihláste sa a po prihlásení Overte totožnosť, ak overenie ešte nie je dokončené.

Pri tom istom button sa strom zobrazenia nezmenil, takže vytvorená path respondera zostáva rovnaká.

Podobné prípady zahŕňajú zapnutý/vypnutý stav UISwitch, vlastnosť indexSelect prvku UISegmentedControl, UIStepper a interakcie UITableView alebo UICollectionView s cell.

Aby to vyriešil, WKTrackingData vytvára event_path selektívnym pridaním vlastností samotného ovládacieho prvku. Výsledná event_path potom vyzerá takto:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Rozšírenia pre obchodnú logiku

Uvažujme o ďalšom scenári: karusel banner na domovskej obrazovke.

Každá pozícia banner má obrázok a cieľovú adresu url dodanú serverom a jej poloha je nastaviteľná.

Tu prvok banner pri každom index nemá pevný cieľ. Udalosť na pozícii 0 nemožno určiť iba z event_path.

Na jednoznačnú identifikáciu udalosti musíme pridať parametre obchodnej logiky, napríklad url.

WKTrackingData podporuje aj rozšírenia obchodných parametrov, takže volajúci môže pridať vlastné parametre:

Pôvodný príklad kódu rozširujúceho udalosti o obchodné parametre

Ovládacie prvky, ktoré sa nemajú sledovať, možno vylúčiť pomocou wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Ďalšie problémy

Na sledovanie rôznych udalostí používa WKTrackingData aspektovo orientovaný prístup a pomocou runtime priamo vymieňa metódy.

Pri sledovaní UIAlertView, UIActionSheet, UITableView a UICollectionView však musíme vymeniť ich metódy delegate.

  • Ak delegate class už metódu implementuje, jednoducho ju vymeňte.

  • Ak ju delegate class neimplementuje a udalosť stále chceme sledovať, musíme ručne pridať implementáciu príslušnej delegate method.

- (void)wk_swizzleInstanceSelector:(SEL)origSel_ fromClass:(Class)fromClass replaceSelector:(SEL)replaceSel_ originNotImp:(SEL)notImpSel_ {
    Method originalMethod = class_getInstanceMethod([self class], origSel_);
    if (originalMethod) {
        [self wk_swizzleInstanceSelector:origSel_ fromClass:fromClass replaceSelector:replaceSel_];
    } else {
        Method notImpMethod = class_getInstanceMethod(fromClass, notImpSel_);
        // 如果delegateClass没有实现 origSel_ 方法
        // 则给delegateClass的 origSel_ 添加 orginReplaceMethod 的实现
        BOOL didAddNotImpMethod =
        class_addMethod([self class],
                        origSel_,
                        method_getImplementation(notImpMethod),
                        method_getTypeEncoding(notImpMethod));
        if (didAddNotImpMethod) {
            NSLog(@"%@ did add not imp method %@" , NSStringFromClass([self class]) , NSStringFromSelector(notImpSel_));
        }
    }
}

Implementácia je tu

V praxi však pri UITableView a UICollectionView, keď ich objekty delegate metódu neimplementovali, ručné pridanie tableView:didSelectRowAtIndexPath: s implementation stále nespôsobilo jej vyvolanie.

Najprv sa pozrime na bežný zásobník volaní tableView:didSelectRowAtIndexPath::

Pôvodný zásobník volaní ladiaceho nástroja pri výbere riadka tabuľky

Tu UITableView volá tieto štyri metódy postupne ako reakciu na klepnutie na cell:

  • 1. [UITableView _userSelectRowAtPendingSelectionIndexPath:]
  • 2. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
  • 3. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]
  • 4. [TableViewController tableView:didSelectRowAtIndexPath:]

Poradie volaní ukazuje, že UITableView zavolá metódu triedy delegate class s názvom tableView:didSelectRowAtIndexPath: až po vyvolaní vlastnej metódy _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:.

Nastavte bod prerušenia na _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: a prezrite si assembler:

Pôvodný pohľad na assembler zobrazujúci vetvu metódy delegáta

Ak delegate class implementuje tableView:didSelectRowAtIndexPath:, všetko funguje a volanie tableView:didSelectRowAtIndexPath: sa dokončí.

Ak však delegate class neimplementuje tableView:didSelectRowAtIndexPath:, ani ručné pridanie tableView:didSelectRowAtIndexPath: s implementation nespôsobí pokus reagovať na tableView:didSelectRowAtIndexPath:.

Sledovanie assemblera odhalí priamy jump na inú inštrukciu.

WKTrackingData tu volí iný prístup a priamo vymieňa [UITableView _userSelectRowAtPendingSelectionIndexPath:] (podobne pre UICollectionView). Tak môže UITableView a UICollectionView sledovať interakcie aj vtedy, keď objekt delegate neimplementuje príslušnú metódu udalosti cell.

4. Záverečné myšlienky

Viac hodnôt event_path pre tú istú udalosť

Predstavme si viac vydaných verzií s malými rozdielmi obrazoviek. Tá istá udalosť potom môže mať viac hodnôt event_path.

V praxi toto mapovanie vyžaduje spoluprácu kolegov na serveri. Dá sa nastaviť v backende.

Optimalizácia analytiky udalostí

Časť Rozšírenia pre obchodnú logiku opisovala pridávanie obchodných parametrov. WKTrackingData to implementuje pomocou kategórií.

V praxi sledovanie všetkých udalostí plytvalo obrovským množstvom prenesených dát. Mnohé udalosti boli zbytočné [(ŏ_ŏ)].

Ďalšou optimalizáciou je, aby server poslal konfiguračný súbor so zoznamom všetkých udalostí, ktoré chce dostávať. Nahrajú sa iba hodnoty event_path zodpovedajúce konfigurácii.

Obchodné parametre možno v tomto bode získať aj bez WKTrackingData: namiesto toho priamo použite kvc.

Napríklad:

key_path = "viewController.banner.url"

Ak ste sa dočítali až sem, otvorte WKTrackingData a dajte mu hviezdičku. Ak máte otázky, otvorte issue na GitHube alebo zanechajte komentár nižšie!