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:

Tu WKTrackingDataManager spravuje všetky údaje a konfiguráciu 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:

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 classuž metódu implementuje, jednoducho ju vymeňte.Ak ju
delegate classneimplementuje a udalosť stále chceme sledovať, musíme ručne pridať implementáciu príslušnejdelegate 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_));
}
}
}
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::

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:

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!