El seguiment d’esdeveniments al client es divideix, a grans trets, en tres categories:

  • Seguiment basat en codi
  • Seguiment visual
  • Seguiment automàtic

Aquest article presenta principalment les idees i el procés d’implementació del seguiment automàtic.

1. Seguiment basat en codi

El seguiment basat en codi consisteix a cridar una interfície a cada punt que es vol seguir i pujar directament les dades de l’esdeveniment. La majoria dels serveis d’analítica de tercers ofereixen una api de seguiment basat en codi, cosa que resulta pràctica.

Però això crea un problema: el codi de seguiment queda escampat per tota la lògica de negoci i fortament acoblat amb ella. Quan canvien les pantalles o els fluxos de negoci, no queda clar què cal fer amb els esdeveniments antics ni si en calen de nous. A mesura que creix el nombre d’esdeveniments, mantenir el codi de seguiment dispers es converteix en un malson. Les macros i les classes de fàbrica poden simplificar el codi, però no canvien el problema de fons.

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

2. Seguiment visual

El seguiment visual configura els punts de recollida mitjançant una eina visual. El client analitza automàticament la configuració i informa dels esdeveniments, cosa que produeix l’anomenat «seguiment sense rastre». Una solució representativa de codi obert és Mixpanel.

3. Seguiment automàtic

El seguiment automàtic, també anomenat seguiment de tots els esdeveniments, no significa realment que no hi hagi instrumentació. Recull i informa de tots els esdeveniments i deixa que el servidor filtri les dades útils.

Com a optimització addicional, el servidor pot enviar un fitxer de configuració perquè el mateix client filtri els esdeveniments.

Pels KPI, vaig reinventar la roda a partir d’aquesta idea i vaig crear WKTrackingData.

Aquí recullo alguns problemes i idees sorgits en implementar WKTrackingData.

La idea d’implementació és senzilla. El codi s’organitza així:

Arquitectura original de WKTrackingData: gestor, camins d’esdeveniments i mòduls AOP

Aquí, WKTrackingDataManager gestiona totes les dades i la configuració del seguiment d’esdeveniments.

Exemple original d’ús de la configuració del seguiment d’esdeveniments

WKTrackingDataViewPathHelper genera els valors event_path.

AOP és el mòdul responsable de seguir tots els esdeveniments.

Per consultar-ne l’ús detallat, visita GitHub.

Com es pot identificar un esdeveniment de manera única?

Hi ha dues preguntes: com representem un esdeveniment i com en garantim la unicitat?

Representar un esdeveniment

Els esdeveniments es representen mitjançant event_path. Per al responder que activa un esdeveniment, la idea principal és seguir la seva cadena de responsors i construir el path d’aquesta cadena.

El path del responsor resultant té aquest aspecte:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Identificar un esdeveniment de manera única

En alguns escenaris de negoci, el mateix button o un altre control representa esdeveniments de negoci diferents a mesura que canvien les seves propietats.

Per exemple, un button de la pantalla d’inici pot mostrar Toca per iniciar la sessió quan no hi ha cap sessió iniciada i, després d’iniciar-la, mostrar Verifica la identitat si la verificació d’identitat encara no s’ha completat.

Per a aquest mateix button, l’arbre de vistes no ha canviat, de manera que el path del responsor generat continua sent el mateix.

Altres casos semblants inclouen l’estat activat o desactivat d’un UISwitch, la propietat indexSelect de UISegmentedControl, UIStepper i les interaccions amb una cell de UITableView o UICollectionView.

Per resoldre-ho, WKTrackingData construeix event_path afegint selectivament propietats del mateix control. El event_path resultant té aquest aspecte:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Extensions específiques del negoci

Considerem un altre escenari: un carrusel de banner a la pantalla d’inici.

Cada espai de banner té una imatge i un url de destinació proporcionats pel servidor, i les seves posicions són configurables.

Aquí, el banner de cada index no té una destinació fixa. L’esdeveniment de la posició 0 no es pot determinar només a partir de event_path.

Per identificar l’esdeveniment de manera única, cal afegir paràmetres específics del negoci, com ara el url.

WKTrackingData també admet extensions de paràmetres de negoci i permet que el codi que el crida afegeixi paràmetres personalitzats:

Exemple original de codi que amplia esdeveniments amb paràmetres de negoci

Els controls que no s’han de seguir es poden excloure mitjançant wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Altres qüestions

Per seguir diferents esdeveniments, WKTrackingData adopta un enfocament orientat a aspectes i utilitza el runtime per intercanviar directament els mètodes.

Tanmateix, per seguir UIAlertView, UIActionSheet, UITableView i UICollectionView, cal intercanviar els seus mètodes de delegate.

  • Si la delegate class ja implementa el mètode, només cal intercanviar-lo.

  • Si la delegate class no l’implementa i tot i així volem seguir l’esdeveniment, cal afegir manualment una implementació del delegate method corresponent.

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

La implementació és aquí

A la pràctica, però, amb UITableView i UICollectionView, quan els objectes delegate no implementaven el mètode, afegir manualment tableView:didSelectRowAtIndexPath: amb una implementation continuava sense fer que s’activés.

Primer, observem la pila de crides normal de tableView:didSelectRowAtIndexPath::

Pila de crides original del depurador en seleccionar una fila d’una vista de taula

Aquí, UITableView crida aquests quatre mètodes en seqüència per respondre al toc d’una cell:

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

L’ordre de crida mostra que UITableView només crida el mètode tableView:didSelectRowAtIndexPath: de la delegate class després d’invocar primer el seu propi mètode _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:.

Posa un punt d’interrupció a _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: i examina l’assemblador:

Vista original de l’assemblador que mostra la bifurcació del mètode del delegat

Si la delegate class implementa tableView:didSelectRowAtIndexPath:, tot funciona i la crida a tableView:didSelectRowAtIndexPath: es completa.

Però si la delegate class no implementa tableView:didSelectRowAtIndexPath:, fins i tot afegir manualment tableView:didSelectRowAtIndexPath: amb una implementation no provoca cap intent de respondre a tableView:didSelectRowAtIndexPath:.

En seguir l’assemblador, es veu un jump directe a una altra instrucció.

WKTrackingData adopta un enfocament diferent i intercanvia directament [UITableView _userSelectRowAtPendingSelectionIndexPath:] (amb un enfocament semblant per a UICollectionView). Això permet que UITableView i UICollectionView segueixin les interaccions fins i tot quan l’objecte delegate no implementa el mètode d’esdeveniment de cell corresponent.

4. Reflexions finals

Diversos valors event_path per al mateix esdeveniment

Considerem diverses versions publicades amb petites diferències a les pantalles. El mateix esdeveniment pot tenir aleshores diversos valors event_path.

A la pràctica, aquesta correspondència requereix la cooperació dels companys de servidor. Es pot fer configurable al rerefons.

Optimitzar l’analítica d’esdeveniments

La secció Extensions específiques del negoci explicava com afegir paràmetres de negoci. WKTrackingData ho implementa mitjançant categories.

A la pràctica, el seguiment de tots els esdeveniments malgastava una gran quantitat d’amplada de banda. Molts d’aquests esdeveniments eren innecessaris [(ŏ_ŏ)].

Una optimització addicional és que el servidor enviï un fitxer de configuració amb la llista de tots els esdeveniments dels quals vol rebre informes. Només es puja un event_path que coincideixi amb la configuració.

En aquest punt, els paràmetres de negoci també es poden obtenir sense WKTrackingData: es pot utilitzar directament kvc.

Per exemple:

key_path = "viewController.banner.url"

Si has arribat fins aquí, obre WKTrackingData i dona-li una estrella. Si tens preguntes, obre una incidència a GitHub o deixa un comentari a sota!