İstemci tarafında olay takibi genel olarak üç kategoriye ayrılır:
- Kod tabanlı takip
- Görsel takip
- Otomatik takip
Bu yazı ağırlıklı olarak otomatik takibin ardındaki fikirleri ve uygulama sürecini anlatıyor.
1. Kod tabanlı takip
Kod tabanlı takip, izlemek istediğiniz her noktada bir arayüz çağırarak olay verilerini doğrudan yüklemek demektir. Üçüncü taraf analiz hizmetlerinin çoğu, kod tabanlı takip için kullanışlı bir api sunar.
Ancak bu bir sorun doğurur: takip kodu iş mantığının her yerine dağılır ve ona sıkıca bağlanır. Ekranlar veya iş akışları değiştiğinde eski olaylarla ne yapılacağı ya da yenilerinin gerekip gerekmediği belirsizleşir. Olay sayısı arttıkça dağınık takip kodunun bakımı kâbusa dönüşür. Makrolar ve fabrika sınıfları kodu sadeleştirebilir ama durumu temelden değiştirmez.
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 埋点
APE(kEvent4001);
}
2. Görsel takip
Görsel takipte toplama noktaları görsel bir araçla yapılandırılır. İstemci yapılandırmayı otomatik ayrıştırıp olayları bildirerek sözde “iz bırakmadan takip” sağlar. Temsilî açık kaynak çözümlerden biri Mixpanel'dir.
3. Otomatik takip
Tüm olayların takibi de denilen otomatik takip, gerçekten hiçbir ölçüm kodu olmadığı anlamına gelmez. Tüm olayları toplayıp bildirir; yararlı verileri süzme işini sunucuya bırakır.
Ek bir iyileştirme olarak sunucu, olayları istemcinin kendisinin süzmesi için bir yapılandırma dosyası gönderebilir.
KPI'lar uğruna, bu fikir etrafında tekerleği yeniden icat edip WKTrackingData'yı yaptım.
Burada WKTrackingData'yı uygularken karşılaştığım bazı sorunları ve fikirleri kaydediyorum.
Uygulama fikri basit. Kod şu şekilde düzenlenmiş:

Burada WKTrackingDataManager, tüm veri yönetimini ve olay takibi yapılandırmasını üstlenir.

WKTrackingDataViewPathHelper, event_path değerlerini üretir.
AOP, her olayı takip etmekten sorumlu modüldür.
Ayrıntılı kullanım için GitHub'a bakın.
Bir olay nasıl benzersiz biçimde tanımlanır?
İki soru var: olayı nasıl temsil ederiz ve benzersizliğini nasıl sağlarız?
Bir olayı temsil etmek
Olaylar event_path ile temsil edilir. Bir olayı tetikleyen responder için temel fikir, yanıtlayıcı zincirini izleyip o zincirin path değerini oluşturmaktır.
Ortaya çıkan yanıtlayıcı path değeri şöyle görünür:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
Bir olayı benzersiz biçimde tanımlamak
Bazı iş senaryolarında aynı button veya başka bir denetim, özellikleri değiştikçe farklı iş olaylarını temsil eder.
Örneğin ana ekrandaki bir button, oturum kapalıyken Giriş yapmak için dokunun, girişten sonra kimlik doğrulaması hâlâ tamamlanmadıysa Kimliğinizi doğrulayın yazabilir.
Aynı button için görünüm ağacı değişmemiştir; dolayısıyla üretilen yanıtlayıcı path değeri aynı kalır.
Benzer durumlar arasında UISwitch'in açık/kapalı hâli, UISegmentedControl'ün indexSelect özelliği, UIStepper ve UITableView veya UICollectionView içindeki cell etkileşimleri bulunur.
Bunu ele almak için WKTrackingData, event_path oluştururken denetimin kendi özelliklerini seçerek sona ekler. Ortaya çıkan event_path artık şöyle görünür:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
İş mantığına özel genişletmeler
Başka bir senaryoyu düşünelim: ana ekranda bir banner karuseli.
Her banner alanının görselini ve hedef url değerini sunucu sağlar; konumlar da yapılandırılabilir.
Burada banner öğesinin her index konumunda sabit bir hedefi yoktur. 0 konumundaki olay yalnızca event_path ile belirlenemez.
Olayı benzersiz biçimde tanımlamak için url gibi iş mantığına özel parametreleri eklememiz gerekir.
WKTrackingData, çağıran tarafın özel parametreler ekleyebilmesi için iş parametresi genişletmelerini de destekler:

Takip edilmemesi gereken denetimler wk_ignoreTracking ile dışarıda bırakılabilir:
self.slider.wk_ignoreTracking = YES;
Diğer sorunlar
Farklı olayları takip etmek için WKTrackingData, kesit yönelimli programlama (AOP) yaklaşımını izler ve runtime kullanarak metot uygulamalarını doğrudan değiş tokuş eder.
Ancak UIAlertView, UIActionSheet, UITableView ve UICollectionView takibi için bunların delegate metotlarını değiş tokuş etmemiz gerekir.
Eğer
delegate classmetodu zaten uyguluyorsa doğrudan değiş tokuş etmek yeterlidir.Eğer
delegate classmetodu uygulamıyorsa ve olayı yine de takip etmek istiyorsak, ilgilidelegate methodiçin elle bir uygulama eklememiz gerekir.
- (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_));
}
}
}
Ancak pratikte, UITableView ve UICollectionView için delegate nesneleri metodu uygulamadığında, tableView:didSelectRowAtIndexPath: metodunu bir implementation ile elle eklemek yine de tetiklenmesini sağlamadı.
Önce normal tableView:didSelectRowAtIndexPath: çağrı yığınına bakalım:

Burada UITableView, bir cell dokunuşuna yanıt vermek için şu dört metodu sırayla çağırır:
- 1. [UITableView _userSelectRowAtPendingSelectionIndexPath:]
- 2. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
- 3. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]
- 4. [TableViewController tableView:didSelectRowAtIndexPath:]
Çağrı sırası, UITableView'ın delegate class içindeki tableView:didSelectRowAtIndexPath: metodunu ancak kendi _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: metodunu çağırdıktan sonra çağırdığını gösterir.
Doğrudan _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: üzerine bir kesme noktası koyup assembly kodunu inceleyin:

Eğer delegate class, tableView:didSelectRowAtIndexPath: metodunu uyguluyorsa her şey çalışır ve tableView:didSelectRowAtIndexPath: çağrısı tamamlanır.
Ama delegate class, tableView:didSelectRowAtIndexPath: metodunu uygulamıyorsa, tableView:didSelectRowAtIndexPath: metodunu bir implementation ile elle eklemek bile tableView:didSelectRowAtIndexPath: çağrısına yanıt verme girişimini sağlamaz.
Assembly akışı izlendiğinde doğrudan başka bir komuta jump yapıldığı görülür.
WKTrackingData burada farklı bir yol izleyerek doğrudan [UITableView _userSelectRowAtPendingSelectionIndexPath:] metodunu değiş tokuş eder (UICollectionView için de benzer biçimde). Böylece UITableView ve UICollectionView, delegate nesnesi ilgili cell olay metodunu uygulamasa bile etkileşimleri takip edebilir.
4. Son düşünceler
Aynı olay için birden fazla event_path değeri
Ekranları arasında küçük farklar olan birden fazla yayımlanmış sürümü düşünün. Aynı olayın birden fazla event_path değeri olabilir.
Pratikte bu eşleme için sunucu tarafındaki çalışma arkadaşlarıyla iş birliği gerekir. Arka uçta yapılandırılabilir hâle getirilebilir.
Olay analizini iyileştirmek
Yukarıdaki iş mantığına özel genişletmeler bölümünde iş parametrelerinin eklenmesi anlatıldı. WKTrackingData bunu kategoriler aracılığıyla uygular.
Pratikte tüm olayları takip etmek çok fazla bant genişliği israfına yol açtı. Bu olayların birçoğu gereksizdi [(ŏ_ŏ)].
Bir sonraki iyileştirme, sunucunun bildirilmesini istediği tüm olayları listeleyen bir yapılandırma dosyası göndermesidir. Yalnızca yapılandırmayla eşleşen bir event_path yüklenir.
Bu aşamada iş parametreleri WKTrackingData olmadan da alınabilir: bunun yerine doğrudan kvc kullanın.
Örneğin:
key_path = "viewController.banner.url"
Buraya kadar okuduysanız WKTrackingData'yı açıp bir yıldız verin. Sorunuz varsa GitHub'da bir issue açın veya aşağıya yorum bırakın!