Śledzenie zdarzeń po stronie klienta dzieli się zasadniczo na trzy kategorie:
- Śledzenie oparte na kodzie
- Śledzenie wizualne
- Śledzenie automatyczne
Ten artykuł przedstawia przede wszystkim idee i proces implementacji śledzenia automatycznego.
1. Śledzenie oparte na kodzie
Śledzenie oparte na kodzie oznacza wywoływanie interfejsu w każdym punkcie, który chcemy śledzić, i bezpośrednie przesyłanie danych zdarzenia. Większość zewnętrznych usług analitycznych udostępnia wygodne api do takiego śledzenia.
Powstaje jednak problem: kod śledzący jest rozrzucony po logice biznesowej i ściśle z nią powiązany. Gdy zmieniają się ekrany lub procesy biznesowe, nie wiadomo, co zrobić ze starymi zdarzeniami ani czy potrzebne są nowe. Wraz ze wzrostem liczby zdarzeń utrzymanie rozproszonego kodu staje się koszmarem. Makra i klasy fabryczne mogą uprościć kod, lecz zasadniczo tego nie zmieniają.
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 埋点
APE(kEvent4001);
}
2. Śledzenie wizualne
Śledzenie wizualne konfiguruje punkty zbierania za pomocą narzędzia wizualnego. Klient automatycznie interpretuje konfigurację i raportuje zdarzenia, tworząc tak zwane „śledzenie bez śladów”. Reprezentatywnym rozwiązaniem open source jest Mixpanel.
3. Śledzenie automatyczne
Śledzenie automatyczne, nazywane też śledzeniem wszystkich zdarzeń, nie oznacza naprawdę braku instrumentacji. Zbiera i raportuje wszystkie zdarzenia, pozostawiając serwerowi odfiltrowanie użytecznych danych.
W ramach dalszej optymalizacji serwer może wysyłać plik konfiguracyjny, aby klient sam filtrował zdarzenia.
Na potrzeby KPI wymyśliłem koło na nowo, opierając się na tej idei, i stworzyłem WKTrackingData.
Zapisuję tutaj problemy i pomysły z implementacji WKTrackingData.
Idea implementacji jest prosta. Kod jest zorganizowany następująco:

Tutaj WKTrackingDataManager odpowiada za zarządzanie wszystkimi danymi i konfigurację śledzenia zdarzeń.

WKTrackingDataViewPathHelper generuje wartości event_path.
Moduł AOP odpowiada za śledzenie każdego zdarzenia.
Szczegółowy sposób użycia znajdziesz w serwisie GitHub.
Jak jednoznacznie zidentyfikować zdarzenie?
Są dwa pytania: jak przedstawić zdarzenie i jak zapewnić jego unikalność?
Przedstawianie zdarzenia
Zdarzenia są przedstawiane za pomocą event_path. Dla obiektu responder, który wyzwala zdarzenie, główna idea polega na przejściu jego łańcucha responderów i zbudowaniu path tego łańcucha.
Wynikowa path respondera wygląda tak:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Jednoznaczne identyfikowanie zdarzenia
W niektórych scenariuszach biznesowych ten sam button lub inny element sterujący reprezentuje różne zdarzenia biznesowe, gdy zmieniają się jego właściwości.
Na przykład button na ekranie głównym może wyświetlać Dotknij, aby się zalogować przed zalogowaniem, a po zalogowaniu Zweryfikuj tożsamość, jeśli weryfikacja nie została jeszcze ukończona.
Dla tego samego button drzewo widoku się nie zmienia, więc wygenerowana path respondera pozostaje taka sama.
Podobne przypadki obejmują stan włączony/wyłączony UISwitch, właściwość indexSelect kontrolki UISegmentedControl, UIStepper oraz interakcje UITableView lub UICollectionView z cell.
Aby to obsłużyć, WKTrackingData buduje event_path, selektywnie dołączając właściwości samego elementu. Wynikowa event_path wygląda wtedy tak:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
Rozszerzenia biznesowe
Rozważmy inny scenariusz: karuzelę banner na ekranie głównym.
Każda pozycja banner ma obraz i docelowy url dostarczane przez serwer, a jej położenie można konfigurować.
Tutaj element banner przy każdym index nie ma stałego celu. Zdarzenia na pozycji 0 nie można ustalić wyłącznie na podstawie event_path.
Aby jednoznacznie zidentyfikować zdarzenie, trzeba dołączyć parametry biznesowe, takie jak url.
WKTrackingData obsługuje też rozszerzenia parametrów biznesowych, pozwalając wywołującemu dołączać parametry niestandardowe:

Elementy, których nie należy śledzić, można wykluczyć za pomocą wk_ignoreTracking:
self.slider.wk_ignoreTracking = YES;
Inne problemy
Do śledzenia różnych zdarzeń WKTrackingData stosuje podejście aspektowe i używa runtime do bezpośredniej zamiany metod.
W przypadku śledzenia UIAlertView, UIActionSheet, UITableView i UICollectionView musimy jednak zamieniać metody ich delegate.
Jeżeli
delegate classjuż implementuje metodę, wystarczy ją zamienić.Jeżeli
delegate classjej nie implementuje, a mimo to chcemy śledzić zdarzenie, musimy ręcznie dodać implementację odpowiedniejdelegate 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_));
}
}
}
W praktyce jednak w przypadku UITableView i UICollectionView, gdy ich obiekty delegate nie implementowały metody, ręczne dodanie tableView:didSelectRowAtIndexPath: z implementation nadal nie powodowało jej wywołania.
Najpierw spójrzmy na zwykły stos wywołań tableView:didSelectRowAtIndexPath::

Tutaj UITableView wywołuje kolejno te cztery metody w odpowiedzi na dotknięcie cell:
- 1. [UITableView _userSelectRowAtPendingSelectionIndexPath:]
- 2. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
- 3. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]
- 4. [TableViewController tableView:didSelectRowAtIndexPath:]
Kolejność pokazuje, że UITableView wywołuje metodę klasy delegate class o nazwie tableView:didSelectRowAtIndexPath: dopiero po wcześniejszym wywołaniu własnej metody _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:.
Ustaw punkt przerwania na _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: i sprawdź kod asemblera:

Jeśli delegate class implementuje tableView:didSelectRowAtIndexPath:, wszystko działa i wywołanie tableView:didSelectRowAtIndexPath: się kończy.
Jeśli jednak delegate class nie implementuje tableView:didSelectRowAtIndexPath:, nawet ręczne dodanie tableView:didSelectRowAtIndexPath: z implementation nie powoduje próby odpowiedzi na tableView:didSelectRowAtIndexPath:.
Prześledzenie asemblera ujawnia bezpośredni jump do innej instrukcji.
WKTrackingData stosuje tutaj inne podejście, bezpośrednio zamieniając [UITableView _userSelectRowAtPendingSelectionIndexPath:] (analogicznie dla UICollectionView). Dzięki temu UITableView i UICollectionView mogą śledzić interakcje nawet wtedy, gdy obiekt delegate nie implementuje odpowiedniej metody zdarzenia cell.
4. Podsumowanie
Wiele wartości event_path dla tego samego zdarzenia
Rozważmy wiele opublikowanych wersji z drobnymi różnicami na ekranach. To samo zdarzenie może wtedy mieć wiele wartości event_path.
W praktyce takie mapowanie wymaga współpracy z osobami odpowiedzialnymi za serwer. Można je konfigurować w backendzie.
Optymalizacja analityki zdarzeń
Sekcja Rozszerzenia biznesowe opisywała dodawanie parametrów biznesowych. WKTrackingData implementuje to przez kategorie.
W praktyce śledzenie wszystkich zdarzeń marnowało ogromną ilość transferu. Wiele z tych zdarzeń było zbędnych [(ŏ_ŏ)].
Dalsza optymalizacja polega na przesłaniu przez serwer pliku konfiguracyjnego z listą wszystkich zdarzeń, które chce otrzymywać. Przesyłane są tylko event_path zgodne z konfiguracją.
Na tym etapie parametry biznesowe można też pobierać bez WKTrackingData: zamiast tego bezpośrednio użyć kvc.
Na przykład:
key_path = "viewController.banner.url"
Jeśli dotarłeś aż tutaj, otwórz WKTrackingData i daj mu gwiazdkę. Jeśli masz pytania, otwórz zgłoszenie w GitHubie albo zostaw komentarz poniżej!