Le suivi d’événements côté client se divise globalement en trois catégories :

  • Suivi dans le code
  • Suivi visuel
  • Suivi automatique

Cet article présente surtout les idées et le processus de mise en œuvre du suivi automatique.

1. Suivi dans le code

Le suivi dans le code consiste à appeler une interface à chaque point à suivre pour téléverser directement les données d’événement. La plupart des services d’analyse tiers proposent une api pratique à cette fin.

Mais cela pose un problème : le code de suivi se retrouve dispersé dans la logique métier et étroitement couplé à celle-ci. Lorsque les écrans ou les parcours changent, on ne sait plus quoi faire des anciens événements ni si de nouveaux sont nécessaires. À mesure que leur nombre augmente, entretenir ce code dispersé devient un cauchemar. Les macros et les classes factory peuvent le simplifier, sans toutefois changer le fond du problème.

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

2. Suivi visuel

Le suivi visuel configure les points de collecte dans un outil graphique. Le client analyse automatiquement cette configuration et transmet les événements, produisant ce que l’on appelle un « suivi sans trace ». Mixpanel en est une solution open source représentative.

3. Suivi automatique

Le suivi automatique, aussi appelé suivi de tous les événements, ne signifie pas réellement une absence d’instrumentation. Il collecte et transmet tous les événements, puis laisse le serveur filtrer les données utiles.

Pour aller plus loin, le serveur peut envoyer un fichier de configuration afin que le client filtre lui-même les événements.

Pour les KPI, j’ai réinventé la roue autour de cette idée et créé WKTrackingData.

Je consigne ici quelques problèmes et idées rencontrés pendant la mise en œuvre de WKTrackingData.

Le principe est simple. Le code est organisé comme suit :

Architecture d’origine de WKTrackingData : gestionnaire, chemins d’événements et modules AOP

Ici, WKTrackingDataManager assure toute la gestion des données et la configuration du suivi d’événements.

Exemple d’origine d’utilisation de la configuration du suivi d’événements

WKTrackingDataViewPathHelper génère les valeurs event_path.

Le module AOP est chargé du suivi de chaque événement.

Pour une utilisation détaillée, consultez GitHub.

Comment identifier un événement de manière unique ?

Deux questions se posent : comment représenter un événement et comment garantir son unicité ?

Représenter un événement

Les événements sont représentés par event_path. Pour le responder qui déclenche un événement, l’idée centrale est de suivre sa chaîne de répondants et d’en construire le path.

Le path de répondants obtenu ressemble à ceci :

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Identifier un événement de manière unique

Dans certains scénarios métier, le même button ou un autre contrôle représente des événements différents lorsque ses propriétés changent.

Par exemple, un button d’accueil peut afficher Toucher pour se connecter lorsqu’on est déconnecté, puis Vérifier l’identité après connexion si cette vérification reste incomplète.

Pour ce même button, l’arbre de vues n’a pas changé ; le path de répondants généré reste donc identique.

On rencontre des cas semblables avec l’état actif/inactif de UISwitch, la propriété indexSelect de UISegmentedControl, UIStepper, ainsi que les interactions de UITableView ou UICollectionView avec une cell.

Pour gérer cette situation, WKTrackingData construit event_path en ajoutant certaines propriétés du contrôle. Le nouvel event_path ressemble alors à ceci :

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Extensions propres au métier

Prenons un autre scénario : un carrousel de banner sur l’écran d’accueil.

Pour chaque emplacement de banner, le serveur fournit une image et une url de destination ; les positions sont configurables.

La destination du banner à chaque index n’est donc pas fixe. L’événement de la position 0 ne peut pas être déterminé par le seul event_path.

Pour l’identifier sans ambiguïté, il faut ajouter des paramètres métier tels que l’url.

WKTrackingData permet également d’étendre les paramètres métier et autorise l’appelant à ajouter ses propres paramètres :

Exemple de code d’origine étendant les événements avec des paramètres métier

Les contrôles à ne pas suivre peuvent être exclus avec wk_ignoreTracking :

self.slider.wk_ignoreTracking = YES;

Autres problèmes

Pour suivre différents événements, WKTrackingData suit une approche orientée aspect et emploie la runtime pour permuter directement les méthodes.

Pour suivre UIAlertView, UIActionSheet, UITableView et UICollectionView, il faut toutefois permuter leurs méthodes delegate.

  • Si la delegate class implémente déjà la méthode, il suffit de la permuter.

  • Si la delegate class ne l’implémente pas mais que nous voulons quand même suivre l’événement, il faut ajouter manuellement une implémentation de la delegate method correspondante.

- (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 mise en œuvre se trouve ici

En pratique, avec UITableView et UICollectionView, lorsque leurs objets delegate n’implémentaient pas la méthode, l’ajout manuel d’une implementation à tableView:didSelectRowAtIndexPath: ne suffisait toujours pas à la déclencher.

Observons d’abord la pile d’appels normale de tableView:didSelectRowAtIndexPath: :

Pile d’appels d’origine du débogueur pour la sélection d’une ligne de tableau

Ici, UITableView appelle ces quatre méthodes dans l’ordre pour répondre au toucher d’une cell :

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

L’ordre montre que UITableView appelle d’abord sa propre méthode _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:, puis seulement la méthode tableView:didSelectRowAtIndexPath: de la delegate class.

Placez un point d’arrêt sur _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: et examinez l’assembleur :

Vue assembleur d’origine montrant le branchement vers la méthode delegate

Si la delegate class implémente bien tableView:didSelectRowAtIndexPath:, tout fonctionne et l’appel de tableView:didSelectRowAtIndexPath: s’achève.

Mais si la delegate class n’implémente pas tableView:didSelectRowAtIndexPath:, même l’ajout manuel de tableView:didSelectRowAtIndexPath: avec une implementation n’entraîne aucune tentative de répondre à tableView:didSelectRowAtIndexPath:.

Le suivi de l’assembleur révèle un jump direct vers une autre instruction.

WKTrackingData adopte ici une autre approche et permute directement [UITableView _userSelectRowAtPendingSelectionIndexPath:] (avec une méthode semblable pour UICollectionView). UITableView et UICollectionView peuvent ainsi suivre les interactions même si l’objet delegate n’implémente pas la méthode d’événement cell correspondante.

4. Conclusion

Plusieurs valeurs event_path pour un même événement

Lorsque plusieurs versions publiées présentent de légères différences d’écran, un même événement peut avoir plusieurs valeurs event_path.

En pratique, cette correspondance nécessite la coopération des collègues côté serveur. Elle peut être rendue configurable dans le backend.

Optimiser l’analyse des événements

La section Extensions propres au métier décrivait l’ajout de paramètres métier. WKTrackingData l’implémente au moyen de catégories.

Dans la pratique, le suivi de tous les événements gaspillait énormément de bande passante. Beaucoup de ces événements étaient inutiles [(ŏ_ŏ)].

Une autre optimisation consiste à faire envoyer par le serveur un fichier de configuration listant tous les événements à transmettre. Seul un event_path correspondant à la configuration est téléversé.

À ce stade, les paramètres métier peuvent aussi être récupérés sans WKTrackingData : utilisez directement kvc.

Par exemple :

key_path = "viewController.banner.url"

Si vous avez lu jusqu’ici, ouvrez WKTrackingData et donnez-lui une étoile. Si vous avez des questions, ouvrez une issue sur GitHub ou laissez un commentaire ci-dessous !