Händelsespårning på klientsidan kan i stora drag delas in i tre kategorier:
- Kodbaserad spårning
- Visuell spårning
- Automatisk spårning
Den här artikeln presenterar främst idéerna och implementationsprocessen bakom automatisk spårning.
1. Kodbaserad spårning
Kodbaserad spårning innebär att anropa ett gränssnitt vid varje punkt som ska spåras och direkt ladda upp händelsedata. De flesta externa analystjänster erbjuder ett api för kodbaserad spårning, vilket är praktiskt.
Men det skapar ett problem: spårningskoden är utspridd i affärslogiken och tätt kopplad till den. När skärmar eller affärsflöden ändras är det oklart vad som ska göras med gamla händelser eller om nya behövs. När antalet händelser växer blir underhållet av utspridd spårningskod en mardröm. Makron och fabriksklasser kan förenkla koden, men ändrar inte detta i grunden.
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 埋点
APE(kEvent4001);
}
2. Visuell spårning
Visuell spårning konfigurerar insamlingspunkter via ett visuellt verktyg. Klienten tolkar konfigurationen automatiskt och rapporterar händelser, vilket ger så kallad ”spårlös spårning”. Ett representativt alternativ med öppen källkod är Mixpanel.
3. Automatisk spårning
Automatisk spårning, även kallad spårning av alla händelser, betyder inte att instrumentering verkligen saknas. Alla händelser samlas in och rapporteras, sedan filtrerar servern fram användbara data.
Som ytterligare optimering kan servern skicka en konfigurationsfil så att klienten själv filtrerar händelserna.
För KPI:ernas skull uppfann jag hjulet på nytt utifrån den här idén och byggde WKTrackingData.
Här dokumenterar jag några problem och idéer från implementationen av WKTrackingData.
Grundidén är enkel. Koden är organiserad så här:

Här hanterar WKTrackingDataManager all datahantering och konfiguration av händelsespårning.

WKTrackingDataViewPathHelper genererar event_path-värden.
Modulen AOP ansvarar för spårningen av varje händelse.
Detaljerad användning finns på GitHub.
Hur identifierar man en händelse entydigt?
Det finns två frågor: hur representerar vi en händelse, och hur säkerställer vi att den är unik?
Representera en händelse
Händelser representeras med event_path. För den responder som utlöser en händelse är grundidén att följa dess responderkedja och bygga kedjans path.
Den resulterande path för respondern ser ut så här:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Identifiera en händelse entydigt
I vissa affärsscenarier representerar samma button eller annan kontroll olika affärshändelser när dess egenskaper ändras.
En button på hemskärmen kan till exempel visa Tryck för att logga in när användaren är utloggad och sedan Verifiera identiteten efter inloggning om identitetskontrollen inte är klar.
För samma button har vyträdet inte ändrats, så den genererade path för respondern är densamma.
Liknande fall är på/av-läget för UISwitch, hos UISegmentedControl egenskapen indexSelect, UIStepper och interaktioner i UITableView eller UICollectionView med en cell.
För att hantera detta bygger WKTrackingData en event_path genom att selektivt lägga till kontrollens egna egenskaper. Den resulterande event_path ser då ut så här:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Affärsspecifika utökningar
Tänk på ett annat scenario: en banner-karusell på hemskärmen.
Varje banner-placering har en bild och en mål-url som kommer från servern, och positionerna kan konfigureras.
Här har varje banner vid ett visst index inget fast mål. Händelsen på position 0 kan inte bestämmas enbart utifrån event_path.
För att identifiera händelsen entydigt måste vi lägga till affärsspecifika parametrar, till exempel url.
WKTrackingData stöder också utökningar av affärsparametrar, så att anroparen kan lägga till egna parametrar:

Kontroller som inte ska spåras kan undantas med wk_ignoreTracking:
self.slider.wk_ignoreTracking = YES;
Andra problem
För att spåra olika händelser använder WKTrackingData ett aspektorienterat angreppssätt och runtime för att byta metoder direkt.
För spårning av UIAlertView, UIActionSheet, UITableView och UICollectionView måste vi däremot byta deras delegate-metoder.
Om
delegate classredan implementerar metoden byter vi helt enkelt ut den.Om
delegate classinte implementerar metoden och vi ändå vill spåra händelsen måste vi manuellt lägga till en implementation av motsvarandedelegate 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 praktiken gick det dock inte att utlösa metoden för UITableView och UICollectionView när deras delegate-objekt inte implementerade den, inte ens genom att manuellt lägga till tableView:didSelectRowAtIndexPath: med en implementation.
Titta först på den normala anropsstacken för tableView:didSelectRowAtIndexPath::

Här anropar UITableView dessa fyra metoder i följd för att svara på ett tryck på en cell:
- 1. [UITableView _userSelectRowAtPendingSelectionIndexPath:]
- 2. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
- 3. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]
- 4. [TableViewController tableView:didSelectRowAtIndexPath:]
Anropsordningen visar att UITableView anropar metoden i delegate class som heter tableView:didSelectRowAtIndexPath: först efter att ha anropat sin egen metod _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:.
Sätt en brytpunkt på _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: och granska assemblerkoden:

Om delegate class implementerar tableView:didSelectRowAtIndexPath: fungerar allt och anropet till tableView:didSelectRowAtIndexPath: slutförs.
Men om delegate class inte implementerar tableView:didSelectRowAtIndexPath: leder inte ens manuell tilläggning av tableView:didSelectRowAtIndexPath: med en implementation till ett försök att svara på tableView:didSelectRowAtIndexPath:.
När vi följer assemblerkoden ser vi ett direkt jump till en annan instruktion.
WKTrackingData väljer här en annan väg och byter direkt ut [UITableView _userSelectRowAtPendingSelectionIndexPath:] (med ett liknande tillvägagångssätt för UICollectionView). Då kan UITableView och UICollectionView spåra interaktioner även när delegate-objektet inte implementerar motsvarande händelsemetod för en cell.
4. Avslutande tankar
Flera event_path-värden för samma händelse
Tänk dig flera släppta versioner med små skillnader i skärmarna. Samma händelse kan då ha flera event_path-värden.
I praktiken kräver den här mappningen samarbete med serverkollegorna. Den kan göras konfigurerbar i backend.
Optimera händelseanalysen
Avsnittet om affärsspecifika utökningar beskrev hur affärsparametrar läggs till. WKTrackingData implementerar detta med kategorier.
I praktiken slösade spårningen av alla händelser mycket bandbredd. Många av händelserna behövdes inte [(ŏ_ŏ)].
En ytterligare optimering är att servern skickar en konfigurationsfil som listar alla händelser den vill få rapporterade. Bara en event_path som matchar konfigurationen laddas upp.
Nu kan affärsparametrarna också hämtas utan WKTrackingData: använd kvc direkt i stället.
Till exempel:
key_path = "viewController.banner.url"
Har du läst ända hit, öppna WKTrackingData och ge projektet en stjärna. Om du har frågor kan du öppna ett ärende på GitHub eller lämna en kommentar nedan!