Hendelsessporing på klientsiden faller grovt sett i tre kategorier:

  • Kodebasert sporing
  • Visuell sporing
  • Automatisk sporing

Denne artikkelen presenterer først og fremst ideene og implementeringsprosessen bak automatisk sporing.

1. Kodebasert sporing

Kodebasert sporing betyr å kalle et grensesnitt på hvert punkt du vil spore, og laste opp hendelsesdata direkte. De fleste tredjeparts analysetjenester tilbyr et api for kodebasert sporing, noe som er praktisk.

Men dette skaper et problem: Sporingskoden ligger spredt i forretningslogikken og er tett koblet til den. Når skjermbilder eller forretningsflyter endres, er det uklart hva som skal skje med gamle hendelser, eller om det er behov for nye. Etter hvert som antallet hendelser øker, blir vedlikehold av spredt sporingskode et mareritt. Makroer og fabrikk-klasser kan forenkle koden, men endrer ikke dette grunnleggende.

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

2. Visuell sporing

Visuell sporing konfigurerer innsamlingspunkter gjennom et visuelt verktøy. Klienten tolker automatisk konfigurasjonen og rapporterer hendelser, noe som gir såkalt «sporløs sporing». En representativ løsning med åpen kildekode er Mixpanel.

3. Automatisk sporing

Automatisk sporing, også kalt full hendelsessporing, betyr egentlig ikke at det ikke finnes instrumentering. Alle hendelser samles inn og rapporteres, mens serveren filtrerer ut nyttige data.

Som en videre optimalisering kan serveren sende en konfigurasjonsfil slik at klienten filtrerer hendelsene selv.

For KPI-enes skyld fant jeg opp hjulet på nytt rundt denne ideen og laget WKTrackingData.

Her noterer jeg noen problemer og ideer fra implementeringen av WKTrackingData.

Ideen bak implementeringen er enkel. Koden er organisert slik:

Opprinnelig WKTrackingData-arkitektur: behandler, hendelsesbaner og AOP-moduler

Her håndterer WKTrackingDataManager all dataadministrasjon og konfigurasjon av hendelsessporing.

Opprinnelig brukseksempel for konfigurasjon av hendelsessporing

WKTrackingDataViewPathHelper genererer event_path-verdier.

AOP er modulen som er ansvarlig for å spore alle hendelser.

Du finner detaljert bruk på GitHub.

Hvordan kan en hendelse identifiseres entydig?

Det er to spørsmål: Hvordan representerer vi en hendelse, og hvordan sikrer vi at den er unik?

Representasjon av en hendelse

Hendelser representeres med event_path. Kjerneideen er å følge responder-kjeden til responder-objektet som utløser en hendelse, og bygge denne kjedens path.

Den resulterende responder-path-verdien ser slik ut:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Entydig identifikasjon av en hendelse

I noen forretningsscenarioer representerer samme button eller annen kontroll ulike forretningshendelser når egenskapene endres.

En button på startskjermen kan for eksempel vise Trykk for å logge på når brukeren er logget av, og deretter Bekreft identiteten etter pålogging hvis identitetsbekreftelsen ennå ikke er fullført.

Visningstreet for denne samme button har ikke endret seg, så den genererte responder-path-verdien forblir den samme.

Lignende tilfeller omfatter av/på-tilstanden til UISwitch, indexSelect-egenskapen til UISegmentedControl, UIStepper samt UITableView- eller UICollectionView-interaksjoner med en cell.

For å håndtere dette legger WKTrackingData utvalgte egenskaper ved selve kontrollen til når en event_path bygges. Den resulterende event_path-verdien ser da slik ut:

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

Tenk på et annet scenario: en banner-karusell på startskjermen.

Hver banner-plassering har et bilde og en mål-url fra serveren, og plasseringen kan konfigureres.

Hver index i banner-elementet har ikke et fast mål. Hendelsen på posisjon 0 kan ikke bestemmes bare ut fra event_path.

For å identifisere hendelsen entydig må vi legge til forretningsspesifikke parametere, for eksempel url.

WKTrackingData støtter også utvidelser med forretningsparametere og lar den som kaller, legge til egendefinerte parametere:

Opprinnelig kodeeksempel som utvider hendelser med forretningsparametere

Kontroller som ikke skal spores, kan utelukkes med wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Andre problemer

For å spore ulike hendelser følger WKTrackingData en aspektorientert tilnærming og bruker runtime til å bytte metoder direkte.

For sporing av UIAlertView, UIActionSheet, UITableView og UICollectionView må vi imidlertid bytte delegate-metodene deres.

  • Hvis delegate class allerede implementerer metoden, bytter vi den ganske enkelt.

  • Hvis delegate class ikke implementerer den og vi fortsatt vil spore hendelsen, må vi legge til en implementering av den aktuelle delegate method manuelt.

- (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 finnes her

I praksis viste det seg imidlertid at når delegate-objektene til UITableView og UICollectionView ikke implementerte metoden, førte manuell tilføyelse av tableView:didSelectRowAtIndexPath: med en implementation fortsatt ikke til at den ble utløst.

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

Opprinnelig kallstakk i feilsøkeren for valg av en tabellrad

Her kaller UITableView disse fire metodene i rekkefølge for å svare på et trykk på en cell:

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

Kallrekkefølgen viser at UITableView kaller metoden tableView:didSelectRowAtIndexPath: i delegate class først etter at den har kalt sin egen metode _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:.

Sett et bruddpunkt på _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:, og undersøk assemblerkoden:

Opprinnelig assemblervisning som viser grenen for delegatmetoden

Hvis delegate class implementerer tableView:didSelectRowAtIndexPath:, fungerer alt, og kallet til tableView:didSelectRowAtIndexPath: fullføres.

Men hvis delegate class ikke implementerer tableView:didSelectRowAtIndexPath:, vil selv manuell tilføyelse av tableView:didSelectRowAtIndexPath: med en implementation ikke føre til et forsøk på å svare på tableView:didSelectRowAtIndexPath:.

Når vi følger assemblerkoden, ser vi et direkte jump til en annen instruksjon.

WKTrackingData bruker en annen tilnærming her og bytter direkte [UITableView _userSelectRowAtPendingSelectionIndexPath:] (med en lignende tilnærming for UICollectionView). Dermed kan UITableView og UICollectionView spore interaksjoner selv når delegate-objektet ikke implementerer den tilsvarende hendelsesmetoden for cell.

4. Avsluttende tanker

Flere event_path-verdier for samme hendelse

Tenk deg flere lanserte versjoner med små forskjeller på skjermbildene. Den samme hendelsen kan da ha flere event_path-verdier.

I praksis krever denne tilordningen samarbeid fra kolleger på serversiden. Den kan gjøres konfigurerbar i backend.

Optimalisering av hendelsesanalyse

Avsnittet Forretningsspesifikke utvidelser beskrev hvordan man legger til forretningsparametere. WKTrackingData implementerer dette gjennom kategorier.

I praksis sløste full hendelsessporing bort svært mye båndbredde. Mange av disse hendelsene var unødvendige [(ŏ_ŏ)].

En videre optimalisering er at serveren sender en konfigurasjonsfil med en liste over alle hendelsene den ønsker rapportert. Bare en event_path som samsvarer med konfigurasjonen, blir lastet opp.

På dette tidspunktet kan forretningsparameterne også hentes uten WKTrackingData: Bruk ganske enkelt kvc direkte i stedet.

For eksempel:

key_path = "viewController.banner.url"

Hvis du har lest så langt, kan du åpne WKTrackingData og gi prosjektet en stjerne. Hvis du har spørsmål, kan du åpne en issue på GitHub eller legge igjen en kommentar nedenfor!