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:

Her håndterer WKTrackingDataManager al datastyring og 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:

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 classallerede implementerer metoden, swizzles den blot.Hvis
delegate classikke implementerer den, og vi stadig vil spore hændelsen, skal vi manuelt tilføje en implementering af den tilsvarendedelegate 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_));
}
}
}
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::

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:

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!