El seguimiento del lado cliente se divide en tres categorías:

  • Seguimiento en código
  • Seguimiento visual
  • Seguimiento automático

Este artículo presenta las ideas y la implementación del seguimiento automático.

1. Seguimiento en código

El seguimiento en código llama a una interfaz en cada punto y sube directamente el evento. Casi todos los servicios ofrecen una api práctica.

Pero el código queda disperso y acoplado al negocio. Al cambiar pantallas o flujos, no se sabe qué hacer con eventos viejos ni si hacen falta nuevos. Mantenerlos se vuelve una pesadilla. Macros y fábricas simplifican, pero no resuelven el fondo.

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

2. Seguimiento visual

Configura puntos mediante una herramienta visual; el cliente interpreta la configuración y comunica eventos, el llamado “seguimiento sin huella”. Una solución abierta representativa es Mixpanel.

3. Seguimiento automático

También llamado seguimiento completo, no carece realmente de instrumentación: recoge todos los eventos y deja al servidor filtrar lo útil.

El servidor puede enviar una configuración para que el cliente filtre.

Por los KPI, reinventé la rueda y creé WKTrackingData.

Aquí registro problemas e ideas de implementar WKTrackingData.

La idea es sencilla. El código se organiza así:

Arquitectura original: gestor, rutas de eventos y módulos AOP

Aquí, WKTrackingDataManager gestiona datos y configuración.

Ejemplo original de configuración del seguimiento

WKTrackingDataViewPathHelper genera valores event_path.

El módulo AOP sigue todos los eventos.

Para un uso detallado, visita GitHub.

¿Cómo identificar un evento de forma única?

Hay dos preguntas: cómo representarlo y cómo garantizar su unicidad.

Representar un evento

Se usa event_path. Para el responder que dispara el evento, seguimos su cadena y construimos el path.

El path resultante es:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Identificarlo de forma única

En ciertos casos, el mismo button representa eventos distintos al cambiar sus propiedades.

Por ejemplo, un button de inicio muestra Toca para iniciar sesión sin sesión y Verifica tu identidad si falta verificarla.

El árbol del button no cambia, así que su path es el mismo.

Ocurre también con UISwitch, indexSelect de UISegmentedControl, UIStepper y una cell de UITableView o UICollectionView.

Por ello, WKTrackingData añade propiedades del control a event_path. El nuevo event_path es:

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

Otro caso: un carrusel banner en inicio.

El servidor entrega imagen y url de destino para cada banner, con posiciones configurables.

Aquí, el banner en cada index carece de destino fijo; el evento de posición 0 no se determina solo con event_path.

Hay que añadir parámetros como la url.

WKTrackingData permite añadir parámetros propios:

Código original para ampliar eventos con parámetros de negocio

Los controles excluidos usan wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Otros problemas

Para seguir eventos distintos, WKTrackingData adopta aspectos y usa la runtime para intercambiar métodos.

Para UIAlertView, UIActionSheet, UITableView y UICollectionView hay que intercambiar métodos delegate.

  • Si la delegate class ya implementa el método, se intercambia.

  • Si la delegate class no lo implementa y queremos seguirlo, añadimos manualmente la implementación del delegate 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_));
        }
    }
}

La implementación está aquí

En la práctica, si los objetos delegate de UITableView y UICollectionView no implementaban el método, añadir una implementation a tableView:didSelectRowAtIndexPath: tampoco lo activaba.

Veamos la pila normal de tableView:didSelectRowAtIndexPath::

Pila original para seleccionar una fila

Aquí, UITableView llama a cuatro métodos para responder al toque de una cell:

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

El orden muestra que UITableView llama primero a _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: y después a tableView:didSelectRowAtIndexPath: de la delegate class.

Pon un punto de ruptura en _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: y mira el ensamblador:

Ensamblador original con la rama del método delegate

Si la delegate class implementa ya tableView:didSelectRowAtIndexPath:, todo funciona y la llamada a tableView:didSelectRowAtIndexPath: termina.

Si la delegate class no implementa tableView:didSelectRowAtIndexPath:, añadir manualmente tableView:didSelectRowAtIndexPath: con una implementation no intenta responder a tableView:didSelectRowAtIndexPath:.

El ensamblador salta con un jump a otra instrucción.

WKTrackingData intercambia [UITableView _userSelectRowAtPendingSelectionIndexPath:] (de forma similar en UICollectionView). Así UITableView y UICollectionView siguen interacciones aunque el objeto delegate no implemente el evento de la cell.

4. Conclusión

Varios event_path para un evento

Distintas versiones con pantallas algo diferentes pueden dar varios event_path al mismo evento.

Esta correspondencia necesita cooperación del servidor y puede configurarse en backend.

Optimizar las estadísticas

La sección Extensiones de negocio explicó sus parámetros; WKTrackingData usa categorías.

El seguimiento completo desperdició mucho ancho de banda; muchos eventos sobraban [(ŏ_ŏ)].

Como optimización adicional, el servidor puede enviar un archivo de configuración que enumera todos los eventos que desea recibir. Solo se sube un event_path que coincida con esa configuración.

También se obtienen parámetros sin WKTrackingData: usa kvc.

Por ejemplo:

key_path = "viewController.banner.url"

Si llegaste aquí, abre WKTrackingData y dale una estrella. Para preguntas, abre una issue en GitHub o comenta abajo.