Hændelsessporing på klientsiden falder overordnet i tre kategorier:

  • Kodebaseret sporing
  • Visuel sporing
  • Automatisk sporing

Denne artikel præsenterer hovedsageligt idéerne og implementeringsprocessen bag automatisk sporing.

1. Kodebaseret sporing

Kodebaseret sporing betyder, at man kalder en grænseflade ved hvert punkt, der skal spores, og uploader hændelsesdata direkte. De fleste tredjepartstjenester til analyse tilbyder en praktisk kodebaseret api til sporing.

Men det skaber et problem: Sporingskoden er spredt over hele forretningslogikken og tæt koblet til den. Når skærmbilleder eller forretningsforløb ændrer sig, er det uklart, hvad der skal ske med gamle hændelser, eller om der er brug for nye. Når antallet af hændelser vokser, bliver vedligeholdelsen af den spredte sporingskode et mareridt. Makroer og factory-klasser kan forenkle koden, men ændrer ikke grundlæggende på dette.

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

2. Visuel sporing

Visuel sporing konfigurerer indsamlingspunkter gennem et visuelt værktøj. Klienten fortolker automatisk konfigurationen og rapporterer hændelser, hvilket skaber såkaldt “sporløs sporing”. En repræsentativ open source-løsning er Mixpanel.

3. Automatisk sporing

Automatisk sporing, også kaldet fuld hændelsessporing, betyder ikke i egentlig forstand, at der ikke er instrumentering. Den indsamler og rapporterer alle hændelser og lader serveren filtrere de nyttige data ud.

Som yderligere optimering kan serveren sende en konfigurationsfil, så klienten selv filtrerer hændelserne.

For KPI'ernes skyld genopfandt jeg hjulet ud fra denne idé og byggede WKTrackingData.

Her beskriver jeg nogle af de problemer og idéer, der opstod under implementeringen af WKTrackingData.

Implementeringsidéen er enkel. Koden er organiseret sådan:

Oprindelig WKTrackingData-arkitektur: manager, hændelsesstier og AOP-moduler

Her håndterer WKTrackingDataManager al datastyring og konfiguration af hændelsessporing.

Oprindeligt brugseksempel på konfiguration af hændelsessporing

WKTrackingDataViewPathHelper genererer event_path-værdier.

AOP er det modul, der er ansvarligt for at spore alle hændelser.

Detaljeret brug kan ses på GitHub.

Hvordan kan en hændelse identificeres entydigt?

Der er to spørgsmål: Hvordan repræsenterer vi en hændelse, og hvordan sikrer vi, at den er entydig?

Repræsentation af en hændelse

Hændelser repræsenteres med event_path. For den responder, der udløser en hændelse, er hovedidéen at følge dens responderkæde og opbygge kædens path.

Den resulterende responder-path ser sådan ud:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Entydig identifikation af en hændelse

I nogle forretningsscenarier repræsenterer den samme button eller en anden kontrol forskellige forretningshændelser, når dens egenskaber ændres.

En button på forsiden kan for eksempel vise Tryk for at logge ind, mens brugeren er logget ud, og derefter Bekræft identitet efter login, hvis identitetsbekræftelsen stadig mangler.

For den samme button er visningstræet ikke ændret, så dens genererede responder-path forbliver den samme.

Lignende tilfælde omfatter til/fra-tilstanden i UISwitch, egenskaben indexSelect i UISegmentedControl, UIStepper samt interaktioner med en cell i UITableView eller UICollectionView.

For at håndtere dette tilføjer WKTrackingData ved genereringen af event_path selektivt egenskaber fra selve kontrollen. Den resulterende event_path ser så sådan ud:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Forretningsspecifikke udvidelser

Overvej et andet scenarie: En banner-karrusel på forsiden.

Hver banner-placering har et billede og en destinations-url, som leveres af serveren, og placeringernes positioner kan konfigureres.

Her har hvert index i banner ingen fast destination. Hændelsen på position 0 kan ikke bestemmes alene ud fra event_path.

For at identificere hændelsen entydigt skal vi tilføje forretningsspecifikke parametre såsom url.

WKTrackingData understøtter også udvidelser med forretningsparametre, så den kaldende kode kan tilføje brugerdefinerede parametre:

Oprindeligt kodeeksempel, der udvider hændelser med forretningsparametre

Kontroller, som ikke skal spores, kan udelukkes med wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Andre problemer

For at spore forskellige hændelser følger WKTrackingData en aspektorienteret tilgang og bruger runtime til at swizzle metoder direkte.

Ved sporing af UIAlertView, UIActionSheet, UITableView og UICollectionView er vi imidlertid nødt til at swizzle deres delegate-metoder.

  • Hvis delegate class allerede implementerer metoden, swizzles den blot.

  • Hvis delegate class ikke implementerer den, og vi stadig vil spore hændelsen, skal vi manuelt tilføje en implementering af den tilsvarende 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_));
        }
    }
}

Implementeringen findes her

I praksis viste det sig dog, at når delegate-objekterne i UITableView og UICollectionView ikke implementerede metoden, blev tableView:didSelectRowAtIndexPath: stadig ikke udløst, selv om den blev tilføjet manuelt med en implementation.

Se først på den normale kaldestak for tableView:didSelectRowAtIndexPath::

Oprindelig debugger-kaldestak for valg af en række i en tabelvisning

Her kalder UITableView disse fire metoder i rækkefølge som svar på et tryk på en cell:

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

Kalderækkefølgen viser, at UITableView først kalder delegate class-metoden tableView:didSelectRowAtIndexPath:, efter at dens egen metode _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: er blevet kaldt.

Sæt et breakpoint på _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:, og undersøg assembly-koden:

Oprindelig assemblyvisning, der viser forgreningen til delegate-metoden

Hvis delegate class implementerer tableView:didSelectRowAtIndexPath:, fungerer alt, og kaldet til tableView:didSelectRowAtIndexPath: gennemføres.

Men hvis delegate class ikke implementerer tableView:didSelectRowAtIndexPath:, vil selv en manuel tilføjelse af tableView:didSelectRowAtIndexPath: med en implementation ikke medføre et forsøg på at svare på tableView:didSelectRowAtIndexPath:.

Når man følger assembly-koden, ses et direkte jump til en anden instruktion.

WKTrackingData bruger en anden tilgang her og swizzler direkte [UITableView _userSelectRowAtPendingSelectionIndexPath:] (med en lignende tilgang til UICollectionView). Dermed kan UITableView og UICollectionView spore interaktioner, selv når delegate-objektet ikke implementerer den tilsvarende hændelsesmetode for en cell.

4. Afsluttende tanker

Flere event_path-værdier for samme hændelse

Overvej flere udgivne versioner med små forskelle i skærmbillederne. Den samme hændelse kan så have flere event_path-værdier.

I praksis kræver denne tilknytning samarbejde med serverkolleger. Den kan gøres konfigurerbar i backend.

Optimering af hændelsesanalyse

I afsnittet Forretningsspecifikke udvidelser blev det beskrevet, hvordan forretningsparametre tilføjes. WKTrackingData implementerer dette gennem kategorier.

I praksis spildte fuld hændelsessporing en stor mængde båndbredde. Mange af hændelserne var unødvendige [(ŏ_ŏ)].

En yderligere optimering er, at serveren sender en konfigurationsfil med en liste over alle de hændelser, den ønsker rapporteret. Kun en event_path, der matcher konfigurationen, uploades.

På dette tidspunkt kan forretningsparametre også hentes uden WKTrackingData: Brug i stedet kvc direkte.

For eksempel:

key_path = "viewController.banner.url"

Hvis du har læst så langt, så åbn WKTrackingData, og giv det en stjerne. Hvis du har spørgsmål, kan du oprette en issue på GitHub eller skrive en kommentar nedenfor!