Il tracciamento degli eventi lato client rientra in tre categorie:
- Tracciamento nel codice
- Tracciamento visivo
- Tracciamento automatico
L’articolo presenta idee e realizzazione del tracciamento automatico.
1. Tracciamento nel codice
Il tracciamento nel codice chiama un’interfaccia in ogni punto da monitorare, caricando direttamente i dati. Quasi tutti i servizi di analisi offrono una comoda api.
Ma il codice si disperde nella logica di business e vi resta strettamente accoppiato. Quando cambiano schermate o flussi, non è chiaro come trattare i vecchi eventi o se ne servano di nuovi. Con molti eventi, mantenerlo diventa un incubo. Macro e factory possono semplificarlo, ma non risolvono il problema.
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 埋点
APE(kEvent4001);
}
2. Tracciamento visivo
Il tracciamento visivo configura i punti tramite uno strumento grafico. Il client interpreta la configurazione e invia gli eventi, realizzando il cosiddetto “tracciamento senza tracce”. Una soluzione open source rappresentativa è Mixpanel.
3. Tracciamento automatico
Il tracciamento automatico, o completo, non significa assenza di strumentazione: raccoglie e invia tutti gli eventi, lasciando al server il filtro dei dati utili.
Come ottimizzazione, il server può inviare una configurazione affinché il client filtri gli eventi.
Per i KPI, reinventai la ruota e creai WKTrackingData.
Qui annoto problemi e idee incontrati realizzando WKTrackingData.
L’idea è semplice. Il codice è organizzato così:

Qui WKTrackingDataManager gestisce dati e configurazione del tracciamento.

WKTrackingDataViewPathHelper genera i valori event_path.
Il modulo AOP traccia ogni evento.
Per l’uso dettagliato, visita GitHub.
Come identificare un evento univocamente?
Ci sono due domande: come rappresentarlo e come garantirne l’unicità?
Rappresentare un evento
Gli eventi usano event_path. Per il responder che li attiva, si segue la catena dei responder e se ne costruisce il path.
Il path risultante è:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Identificare un evento univocamente
In alcuni scenari lo stesso button o controllo rappresenta eventi diversi al cambiare delle sue proprietà.
Per esempio, un button sulla home può mostrare Tocca per accedere da disconnessi, poi Verifica identità dopo l’accesso se la verifica è incompleta.
Per lo stesso button l’albero delle viste non cambia, quindi il path resta uguale.
Casi simili sono lo stato di UISwitch, la proprietà indexSelect di UISegmentedControl, UIStepper e le interazioni con una cell di UITableView o UICollectionView.
Per gestirli, WKTrackingData costruisce event_path aggiungendo proprietà selezionate del controllo. Il nuovo event_path è:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Estensioni di business
Consideriamo un altro scenario: un carosello banner sulla home.
Per ogni posizione banner, immagine e url di destinazione arrivano dal server; anche le posizioni sono configurabili.
Ogni index del banner non ha quindi una destinazione fissa. L’evento in posizione 0 non si determina dal solo event_path.
Per identificarlo bisogna aggiungere parametri specifici, come l’url.
WKTrackingData supporta questi parametri e permette al chiamante di aggiungerne di propri:

I controlli da non tracciare possono essere esclusi con wk_ignoreTracking:
self.slider.wk_ignoreTracking = YES;
Altri problemi
Per eventi diversi, WKTrackingData usa un approccio orientato agli aspetti e la runtime per scambiare direttamente i metodi.
Per UIAlertView, UIActionSheet, UITableView e UICollectionView, però, occorre scambiare i metodi delegate.
Se la
delegate classimplementa già il metodo, basta scambiarlo.Se la
delegate classnon lo implementa e vogliamo tracciare l’evento, bisogna aggiungere manualmente l’implementazione deldelegate methodcorrispondente.
- (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_));
}
}
}
In pratica, se gli oggetti delegate di UITableView e UICollectionView non implementavano il metodo, aggiungere una implementation a tableView:didSelectRowAtIndexPath: non lo faceva comunque partire.
Vediamo prima il normale stack di chiamata di tableView:didSelectRowAtIndexPath::

Qui UITableView chiama in sequenza questi quattro metodi per rispondere al tocco di una cell:
- Passo 1. [UITableView _userSelectRowAtPendingSelectionIndexPath:]
- Passo 2. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
- Passo 3. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]
- Passo 4. [TableViewController tableView:didSelectRowAtIndexPath:]
L’ordine mostra che UITableView invoca prima il proprio _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:, poi il metodo tableView:didSelectRowAtIndexPath: della delegate class.
Imposta un breakpoint su _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: ed esamina l’assembly:

Se la delegate class implementa già tableView:didSelectRowAtIndexPath:, tutto funziona e la chiamata a tableView:didSelectRowAtIndexPath: termina.
Ma se la delegate class non implementa tableView:didSelectRowAtIndexPath:, anche aggiungere manualmente tableView:didSelectRowAtIndexPath: con una implementation non provoca un tentativo di rispondere a tableView:didSelectRowAtIndexPath:.
Seguendo l’assembly si vede un jump diretto a un’altra istruzione.
WKTrackingData scambia direttamente [UITableView _userSelectRowAtPendingSelectionIndexPath:] (e usa un approccio simile per UICollectionView). Così UITableView e UICollectionView tracciano le interazioni anche se il delegate non implementa il metodo della cell.
4. Conclusione
Più event_path per lo stesso evento
Con più versioni pubblicate e schermate leggermente diverse, lo stesso evento può avere più valori event_path.
In pratica questa mappatura richiede la collaborazione lato server e può essere configurata nel backend.
Ottimizzare le analisi
La sezione Estensioni di business descriveva i parametri specifici. WKTrackingData li realizza tramite categorie.
Il tracciamento completo sprecava molta banda: tanti eventi erano inutili [(ŏ_ŏ)].
Il server può inviare una configurazione con tutti gli eventi desiderati. Si caricano solo gli event_path corrispondenti.
I parametri di business possono anche essere ottenuti senza WKTrackingData, usando direttamente kvc.
Per esempio:
key_path = "viewController.banner.url"
Se hai letto fin qui, apri WKTrackingData e lascia una stella. Per domande, apri una issue su GitHub o commenta qui sotto!