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:

Aqui, WKTrackingDataManager gerencia dados e configuração.

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:

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 classjá implementa, basta trocar.Se a
delegate classnão implementa e queremos rastrear, adicionamos manualmente odelegate methodcorrespondente.
- (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_));
}
}
}
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::

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:

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!