O rastreamento no cliente se divide em três categorias:

  • Rastreamento no código
  • Rastreamento visual
  • Rastreamento automático

Este artigo apresenta ideias e implementação do rastreamento automático.

1. Rastreamento no código

O rastreamento no código chama uma interface em cada ponto e envia o evento. Quase todo serviço fornece uma api prática.

Mas o código fica espalhado e acoplado à lógica. Ao mudar telas ou fluxos, não se sabe como tratar eventos antigos nem se faltam novos. Com muitos eventos, a manutenção vira pesadelo. Macros e fábricas simplificam, mas não resolvem.

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

2. Rastreamento visual

Configura pontos numa ferramenta visual; o cliente interpreta e relata eventos, o “rastreamento sem marcas”. Uma solução aberta é Mixpanel.

3. Rastreamento automático

Também chamado completo, não significa ausência de instrumentação: coleta tudo e o servidor filtra dados úteis.

O servidor pode enviar uma configuração para o cliente filtrar.

Pelos KPIs, reinventei a roda e fiz WKTrackingData.

Aqui registro problemas e ideias ao implementar WKTrackingData.

A ideia é simples. O código é organizado assim:

Arquitetura original: gerenciador, caminhos e módulos AOP

Aqui, WKTrackingDataManager gerencia dados e configuração.

Exemplo original de configuração de rastreamento

WKTrackingDataViewPathHelper gera valores event_path.

O módulo AOP rastreia os eventos.

Para detalhes, visite GitHub.

Como identificar um evento unicamente?

Duas perguntas: como representá-lo e garantir sua unicidade?

Representação

Usamos event_path. Para o responder que dispara, seguimos sua cadeia e construímos o path.

O path resultante:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Identificação única

Em alguns cenários, o mesmo button representa eventos diferentes quando suas propriedades mudam.

Por exemplo, um button inicial mostra Toque para entrar desconectado e Verifique a identidade após entrar se faltar verificação.

A árvore desse button não muda, então o path é igual.

Também ocorre com UISwitch, indexSelect de UISegmentedControl, UIStepper e uma cell de UITableView ou UICollectionView.

Assim, WKTrackingData acrescenta propriedades a event_path. O novo event_path é:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Extensões de negócio

Outro cenário: carrossel de banner na tela inicial.

Cada posição de banner recebe imagem e url do servidor e é configurável.

Aqui, o banner de cada index não tem destino fixo. O evento na posição 0 não vem só de event_path.

Precisamos acrescentar parâmetros como a url.

WKTrackingData permite parâmetros personalizados:

Código original ampliando eventos com parâmetros de negócio

Controles ignorados usam wk_ignoreTracking:

self.slider.wk_ignoreTracking = YES;

Outros problemas

Para rastrear eventos diferentes, WKTrackingData usa orientação a aspectos e a runtime para trocar métodos.

Para UIAlertView, UIActionSheet, UITableView e UICollectionView, porém, é preciso trocar métodos delegate.

  • Se a delegate class já implementa, basta trocar.

  • Se a delegate class não implementa e queremos rastrear, adicionamos manualmente o delegate method correspondente.

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

A implementação está aqui

Na prática, em UITableView e UICollectionView, se o delegate não implementava, adicionar uma implementation a tableView:didSelectRowAtIndexPath: não fazia disparar.

Veja a pilha normal de tableView:didSelectRowAtIndexPath::

Pilha original ao selecionar uma linha

Aqui, UITableView chama quatro métodos para o toque numa cell:

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

A ordem mostra que UITableView chama primeiro _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: e depois tableView:didSelectRowAtIndexPath: da delegate class.

Coloque um breakpoint em _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: e examine o assembly:

Assembly original mostrando o desvio do delegate

Se a delegate class já implementa tableView:didSelectRowAtIndexPath:, funciona e a chamada a tableView:didSelectRowAtIndexPath: termina.

Se a delegate class não implementa tableView:didSelectRowAtIndexPath:, adicionar tableView:didSelectRowAtIndexPath: com uma implementation não tenta responder a tableView:didSelectRowAtIndexPath:.

O assembly dá um jump direto.

WKTrackingData troca [UITableView _userSelectRowAtPendingSelectionIndexPath:] (similar em UICollectionView). Assim UITableView e UICollectionView rastreiam mesmo quando o delegate não implementa o evento da cell.

4. Conclusão

Vários event_path para um evento

Versões com telas diferentes podem dar vários event_path ao mesmo evento.

O mapeamento exige colaboração do servidor e pode ser configurável no backend.

Otimizar análise

A seção Extensões de negócio tratou dos parâmetros. WKTrackingData usa categorias.

O rastreamento completo gastou muita banda; muitos eventos eram desnecessários [(ŏ_ŏ)].

O servidor pode enviar um arquivo que lista os eventos desejados. Só um event_path correspondente é enviado.

Também podemos obter parâmetros sem WKTrackingData, usando kvc.

Por exemplo:

key_path = "viewController.banner.url"

Se leu até aqui, abra WKTrackingData e dê uma estrela. Para dúvidas, abra uma issue no GitHub ou comente abaixo!