Gebeurtenistracking aan de clientkant valt grofweg in drie categorieën:

  • Tracking in code
  • Visuele tracking
  • Automatische tracking

Dit artikel behandelt vooral de ideeën en uitvoering van automatische tracking.

1. Tracking in code

Tracking in code roept op elk meetpunt een interface aan en uploadt gebeurtenisgegevens direct. De meeste analysediensten bieden hiervoor een handige api.

Hierdoor raakt trackingcode verspreid door en gekoppeld aan bedrijfslogica. Bij gewijzigde schermen of processen is onduidelijk wat met oude gebeurtenissen moet of welke nieuwe nodig zijn. Met steeds meer gebeurtenissen wordt onderhoud een nachtmerrie. Macro’s en factoryklassen vereenvoudigen code maar lossen dit niet fundamenteel op.

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

2. Visuele tracking

Hiermee configureer je meetpunten in een visueel hulpmiddel. De client verwerkt de configuratie automatisch en meldt gebeurtenissen: zogenoemde “spoorloze tracking”. Een representatieve open oplossing is Mixpanel.

3. Automatische tracking

Automatische of volledige tracking betekent niet dat instrumentatie ontbreekt. Alle gebeurtenissen worden verzameld en gemeld, waarna de server nuttige gegevens filtert.

De server kan ter optimalisatie een configuratiebestand sturen zodat de client zelf filtert.

Voor de KPI’s vond ik vanuit dit idee het wiel opnieuw uit en bouwde WKTrackingData.

Hier noteer ik problemen en ideeën uit de implementatie van WKTrackingData.

Het idee is eenvoudig. De code is als volgt georganiseerd:

Oorspronkelijke architectuur: manager, gebeurtenispaden en AOP-modules

Hier beheert WKTrackingDataManager alle gegevens en trackingconfiguratie.

Oorspronkelijk gebruiksvoorbeeld voor trackingconfiguratie

WKTrackingDataViewPathHelper genereert event_path-waarden.

De module AOP volgt elke gebeurtenis.

Bekijk voor gedetailleerd gebruik GitHub.

Hoe identificeer je een gebeurtenis uniek?

Twee vragen: hoe stellen we een gebeurtenis voor, en hoe garanderen we uniciteit?

Een gebeurtenis voorstellen

Gebeurtenissen gebruiken event_path. Voor de responder die ze activeert, volgen we de responderketen en bouwen we daarvan het path.

Het responder-path ziet er zo uit:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Een gebeurtenis uniek identificeren

In sommige bedrijfssituaties vertegenwoordigt dezelfde button of bediening door gewijzigde eigenschappen verschillende gebeurtenissen.

Een button op het beginscherm toont bijvoorbeeld Tik om in te loggen wanneer afgemeld, en daarna Identiteit verifiëren als verificatie nog ontbreekt.

De weergaveboom van dezelfde button verandert niet, dus het responder-path blijft gelijk.

Vergelijkbare gevallen zijn de stand van UISwitch, indexSelect van UISegmentedControl, UIStepper en interacties met een cell in UITableView of UICollectionView.

Daarom voegt WKTrackingData geselecteerde eigenschappen aan event_path toe. Het resulterende event_path is:

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

Neem een ander geval: een banner-carrousel op het beginscherm.

De server levert voor elke banner-plek een afbeelding en doel-url; posities zijn instelbaar.

Hier heeft de banner bij elke index geen vaste bestemming. De gebeurtenis op positie 0 volgt niet alleen uit event_path.

Voor unieke identificatie voegen we bedrijfsparameters toe, zoals de url.

WKTrackingData ondersteunt zulke uitbreidingen en laat de aanroeper eigen parameters toevoegen:

Oorspronkelijke code die gebeurtenissen met bedrijfsparameters uitbreidt

Bedieningen die niet gevolgd moeten worden sluit je uit met wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Andere kwesties

Om verschillende gebeurtenissen te volgen, gebruikt WKTrackingData aspectoriëntatie en de runtime om methoden direct te swizzelen.

Voor UIAlertView, UIActionSheet, UITableView en UICollectionView moeten echter hun delegate-methoden worden gewisseld.

  • Als de delegate class de methode al heeft, wissel je die.

  • Als de delegate class haar niet heeft en we toch willen volgen, voegen we de bijbehorende delegate method handmatig toe.

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

De implementatie staat hier

In de praktijk vuurde tableView:didSelectRowAtIndexPath: bij UITableView en UICollectionView nog steeds niet als hun delegate-object de methode niet had, zelfs na handmatig toevoegen van een implementation.

Bekijk eerst de normale aanroepstack van tableView:didSelectRowAtIndexPath::

Oorspronkelijke debuggerstack voor rijselectie

Hier roept UITableView vier methoden op om op een tik op een cell te reageren:

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

De volgorde toont dat UITableView eerst zijn eigen _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: oproept en dan pas tableView:didSelectRowAtIndexPath: van de delegate class.

Zet een breakpoint op _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: en bekijk de assembly:

Oorspronkelijke assemblyweergave met delegatevertakking

Als de delegate class reeds tableView:didSelectRowAtIndexPath: implementeert, werkt alles en is de aanroep van tableView:didSelectRowAtIndexPath: voltooid.

Als de delegate class juist tableView:didSelectRowAtIndexPath: niet implementeert, zorgt handmatig toevoegen van tableView:didSelectRowAtIndexPath: met een implementation nog niet voor een reactie op tableView:didSelectRowAtIndexPath:.

De assembly maakt een directe jump naar een andere instructie.

WKTrackingData wisselt rechtstreeks [UITableView _userSelectRowAtPendingSelectionIndexPath:] (en doet iets vergelijkbaars voor UICollectionView). Daardoor kunnen UITableView en UICollectionView interacties volgen als het delegate-object de bijbehorende cell-methode niet implementeert.

4. Slotgedachten

Meerdere event_path-waarden voor één gebeurtenis

Verschillen tussen uitgebrachte schermversies kunnen één gebeurtenis meerdere event_path-waarden geven.

Deze toewijzing vraagt samenwerking met servercollega’s en kan in de backend instelbaar worden gemaakt.

Gebeurtenisanalyse optimaliseren

Het onderdeel Bedrijfsspecifieke uitbreidingen beschreef bedrijfsparameters. WKTrackingData gebruikt hiervoor categorieën.

Volledige tracking verspilde veel bandbreedte. Veel gebeurtenissen waren overbodig [(ŏ_ŏ)].

De server kan een configuratiebestand sturen met alle gewenste gebeurtenissen. Alleen een overeenkomend event_path wordt geüpload.

Bedrijfsparameters zijn ook zonder WKTrackingData op te halen: gebruik rechtstreeks kvc.

Bijvoorbeeld:

key_path = "viewController.banner.url"

Heb je dit helemaal gelezen, open dan WKTrackingData en geef een ster. Open voor vragen een GitHub-issue of laat hieronder een reactie achter!