Sledování událostí na klientské straně lze obecně rozdělit do tří kategorií:

  • Sledování pomocí kódu
  • Vizuální sledování
  • Automatické sledování

Tento článek představuje především myšlenky a postup implementace automatického sledování.

1. Sledování pomocí kódu

Sledování pomocí kódu znamená zavolat na každém sledovaném místě rozhraní, které přímo nahraje data události. Většina analytických služeb třetích stran nabízí api pro sledování pomocí kódu, což je praktické.

Vzniká tím však problém: sledovací kód je rozptýlen po celé obchodní logice a je s ní pevně provázán. Když se změní obrazovky nebo obchodní postupy, není jasné, co dělat se starými událostmi ani zda jsou potřeba nové. S rostoucím počtem událostí se údržba rozptýleného sledovacího kódu stává noční můrou. Makra a tovární třídy mohou kód zjednodušit, ale podstatu problému nemění.

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

2. Vizuální sledování

Vizuální sledování konfiguruje body sběru prostřednictvím vizuálního nástroje. Klient konfiguraci automaticky zpracuje a ohlásí události, čímž vzniká takzvané „bezešvé sledování“. Jedním z reprezentativních open-source řešení je Mixpanel.

3. Automatické sledování

Automatické sledování, nazývané také úplné sledování událostí, ve skutečnosti neznamená, že není potřeba žádná instrumentace. Shromažďuje a hlásí všechny události a užitečná data následně filtruje server.

Dalším vylepšením může být konfigurační soubor odeslaný serverem, podle kterého klient filtruje události sám.

Kvůli KPI jsem na základě této myšlenky znovu vynalezl kolo a vytvořil WKTrackingData.

Zde zaznamenávám některé problémy a myšlenky z implementace WKTrackingData.

Myšlenka implementace je jednoduchá. Kód je uspořádán následovně:

Původní architektura WKTrackingData: správce, cesty událostí a moduly AOP

Zde WKTrackingDataManager zajišťuje veškerou správu dat a konfiguraci sledování událostí.

Původní příklad použití konfigurace sledování událostí

WKTrackingDataViewPathHelper generuje hodnoty event_path.

Modul AOP zodpovídá za sledování každé události.

Podrobný návod k použití najdete na GitHubu.

Jak lze jednoznačně identifikovat událost?

Jsou tu dvě otázky: jak událost vyjádřit a jak zajistit její jedinečnost?

Vyjádření události

Události se vyjadřují pomocí event_path. Základní myšlenkou je sledovat řetězec responderů od responder, který událost vyvolá, a sestavit path tohoto řetězce.

Výsledná path responderu vypadá takto:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Jednoznačná identifikace události

V některých obchodních scénářích představuje stejné button nebo jiný ovládací prvek při změně svých vlastností různé obchodní události.

Například button na domovské obrazovce může po odhlášení zobrazovat Klepněte pro přihlášení a po přihlášení, pokud ještě nebylo dokončeno ověření totožnosti, Ověřit totožnost.

Strom zobrazení se u stejného button nezměnil, takže vygenerovaná path responderu zůstává stejná.

Podobné případy zahrnují zapnutý či vypnutý stav UISwitch, vlastnost indexSelect prvku UISegmentedControl, UIStepper a interakce UITableView nebo UICollectionView s prvkem cell.

V takovém případě WKTrackingData při vytváření event_path výběrově připojuje vlastnosti samotného ovládacího prvku. Výsledná event_path pak vypadá takto:

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

Uvažujme další scénář: karusel banner na domovské obrazovce.

Každá plocha banner má obrázek a cílovou url dodanou serverem a její pozice je nastavitelná.

Každý index prvku banner zde nemá pevně stanovený cíl. Událost na pozici 0 nelze určit pouze z event_path.

Pro jednoznačnou identifikaci události musíme připojit parametry obchodní logiky, například url.

WKTrackingData podporuje také rozšíření o obchodní parametry a umožňuje volajícímu připojit vlastní parametry:

Původní příklad kódu rozšiřujícího události o obchodní parametry

Ovládací prvky, které nemají být sledovány, lze vyloučit pomocí wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Další problémy

Pro sledování různých událostí využívá WKTrackingData aspektově orientovaný přístup a přímo zaměňuje metody pomocí runtime.

Při sledování prvků UIAlertView, UIActionSheet, UITableView a UICollectionView však musíme zaměnit jejich metody delegate.

  • Pokud již delegate class metodu implementuje, stačí ji zaměnit.

  • Pokud ji delegate class neimplementuje a událost přesto chceme sledovat, musíme implementaci příslušné delegate method přidat ručně.

- (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_));
        }
    }
}

Implementace je zde

V praxi se však u UITableView a UICollectionView ukázalo, že když jejich objekty delegate metodu neimplementovaly, ruční přidání tableView:didSelectRowAtIndexPath: s implementation stále nezpůsobilo její spuštění.

Nejprve se podívejme na běžný zásobník volání tableView:didSelectRowAtIndexPath::

Původní zásobník volání debuggeru při výběru řádku tabulky

Zde UITableView při reakci na klepnutí na cell postupně volá tyto čtyři metody:

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

Pořadí volání ukazuje, že UITableView zavolá metodu tableView:didSelectRowAtIndexPath: třídy delegate class až poté, co nejprve spustí vlastní metodu _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:.

Nastavme bod přerušení na _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: a prohlédněme si assembler:

Původní zobrazení assembleru s větví metody delegáta

Pokud delegate class implementuje tableView:didSelectRowAtIndexPath:, vše funguje a volání tableView:didSelectRowAtIndexPath: se dokončí.

Jestliže však delegate class neimplementuje tableView:didSelectRowAtIndexPath:, ani ruční přidání tableView:didSelectRowAtIndexPath: s implementation nevyvolá pokus na tableView:didSelectRowAtIndexPath: reagovat.

Sledování assembleru odhalí přímý jump na jinou instrukci.

WKTrackingData zde používá jiný přístup a přímo zaměňuje [UITableView _userSelectRowAtPendingSelectionIndexPath:] (s podobným přístupem pro UICollectionView). Díky tomu mohou UITableView a UICollectionView sledovat interakce, i když objekt delegate neimplementuje odpovídající metodu události cell.

4. Závěrem

Více hodnot event_path pro stejnou událost

Uvažujme několik vydaných verzí s drobnými rozdíly na obrazovkách. Stejná událost pak může mít více hodnot event_path.

V praxi toto mapování vyžaduje spolupráci kolegů na straně serveru. V backendu lze nastavit jeho konfiguraci.

Optimalizace analýzy událostí

Oddíl Rozšíření pro obchodní logiku popsal přidávání obchodních parametrů. WKTrackingData je implementuje prostřednictvím kategorií.

V praxi úplné sledování událostí plýtvalo velkým množstvím přenosového pásma. Mnoho těchto událostí nebylo potřeba [(ŏ_ŏ)].

Další optimalizací je, aby server odeslal konfigurační soubor se seznamem všech událostí, které chce hlásit. Nahraje se pouze event_path odpovídající konfiguraci.

V této fázi lze obchodní parametry získat i bez WKTrackingData: místo něj stačí přímo použít kvc.

Například:

key_path = "viewController.banner.url"

Pokud jste dočetli až sem, otevřete WKTrackingData a dejte mu hvězdičku. Máte-li dotazy, otevřete issue na GitHubu nebo napište komentář níže!