क्लाइंट-साइड इवेंट ट्रैकिंग को मोटे तौर पर तीन श्रेणियों में बाँटा जा सकता है:
- कोड-आधारित ट्रैकिंग
- विज़ुअल ट्रैकिंग
- स्वचालित ट्रैकिंग
यह लेख मुख्य रूप से स्वचालित ट्रैकिंग के विचार और कार्यान्वयन की प्रक्रिया बताता है।
1. कोड-आधारित ट्रैकिंग
कोड-आधारित ट्रैकिंग का अर्थ है हर ट्रैक किए जाने वाले स्थान पर इंटरफ़ेस कॉल करके इवेंट डेटा सीधे अपलोड करना। अधिकांश तृतीय-पक्ष एनालिटिक्स सेवाएँ कोड-आधारित ट्रैकिंग api देती हैं, जो सुविधाजनक है।
लेकिन इससे समस्या आती है: ट्रैकिंग कोड पूरे व्यावसायिक तर्क में बिखर जाता है और उससे कसकर जुड़ जाता है। स्क्रीन या प्रवाह बदलें तो पुराने इवेंट का क्या हो या नए चाहिए या नहीं, यह स्पष्ट नहीं रहता। इवेंट बढ़ने पर बिखरे कोड का रखरखाव दुःस्वप्न बन जाता है। मैक्रो और फ़ैक्टरी क्लास कोड सरल कर सकते हैं, लेकिन मूल समस्या नहीं बदलते।
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 埋点
APE(kEvent4001);
}
2. विज़ुअल ट्रैकिंग
विज़ुअल ट्रैकिंग में दृश्य टूल से संग्रह बिंदु कॉन्फ़िगर किए जाते हैं। क्लाइंट कॉन्फ़िगरेशन को अपने-आप पढ़कर इवेंट रिपोर्ट करता है, जिससे तथाकथित “बिना निशान वाली ट्रैकिंग” बनती है। इसका एक प्रतिनिधि ओपन-सोर्स समाधान Mixpanel है।
3. स्वचालित ट्रैकिंग
स्वचालित ट्रैकिंग, जिसे पूर्ण-इवेंट ट्रैकिंग भी कहते हैं, वास्तव में बिना इंस्ट्रुमेंटेशन के नहीं होती। यह सभी इवेंट एकत्र कर रिपोर्ट करती है और उपयोगी डेटा चुनने का काम सर्वर पर छोड़ देती है।
अगले सुधार के रूप में सर्वर कॉन्फ़िगरेशन फ़ाइल भेज सकता है ताकि क्लाइंट स्वयं इवेंट फ़िल्टर करे।
KPI के लिए, मैंने इसी विचार पर पहिया फिर से बनाया और WKTrackingData तैयार किया।
यहाँ WKTrackingData बनाते समय सामने आई कुछ समस्याएँ और विचार दर्ज हैं।
कार्यान्वयन का विचार सरल है। कोड की संरचना इस प्रकार है:

यहाँ WKTrackingDataManager सभी डेटा प्रबंधन और इवेंट ट्रैकिंग कॉन्फ़िगरेशन संभालता है।

WKTrackingDataViewPathHelper, event_path मान बनाता है।
AOP हर इवेंट की ट्रैकिंग का ज़िम्मेदार मॉड्यूल है।
विस्तृत उपयोग के लिए GitHub देखें।
किसी इवेंट को विशिष्ट रूप से कैसे पहचानें?
दो सवाल हैं: इवेंट को दर्शाएँ कैसे, और उसका विशिष्ट होना सुनिश्चित कैसे करें?
इवेंट को दर्शाना
इवेंट को event_path से दर्शाया जाता है। मूल विचार यह है कि इवेंट शुरू करने वाले responder की responder chain का अनुसरण करके उस शृंखला का path बनाया जाए।
बना हुआ responder path ऐसा दिखता है:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
इवेंट को विशिष्ट रूप से पहचानना
कुछ व्यावसायिक स्थितियों में वही button या दूसरा नियंत्रण अपनी विशेषताएँ बदलने पर अलग व्यावसायिक इवेंट दर्शाता है।
उदाहरण के लिए होम स्क्रीन का button साइन आउट होने पर साइन इन करने के लिए टैप करें दिखा सकता है और साइन इन होने के बाद पहचान सत्यापन अधूरा हो तो पहचान सत्यापित करें दिखा सकता है।
उसी button का view tree नहीं बदला, इसलिए उसका responder path वही रहता है।
ऐसी ही स्थितियाँ UISwitch की चालू/बंद अवस्था, UISegmentedControl की indexSelect प्रॉपर्टी, UIStepper और UITableView या UICollectionView में cell इंटरैक्शन में होती हैं।
इसे संभालने के लिए WKTrackingData, event_path बनाते समय नियंत्रण की चुनी हुई प्रॉपर्टियाँ जोड़ता है। परिणामी event_path ऐसा दिखता है:
"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0";
व्यवसाय-विशिष्ट विस्तार
एक और स्थिति लें: होम स्क्रीन पर banner कैरसेल।
हर banner स्थान का चित्र और गंतव्य url सर्वर देता है और उनका स्थान कॉन्फ़िगर किया जा सकता है।
यहाँ हर index के banner का गंतव्य तय नहीं है। स्थान 0 का इवेंट केवल event_path से निर्धारित नहीं हो सकता।
इवेंट को विशिष्ट पहचान देने के लिए url जैसे व्यवसाय-विशिष्ट पैरामीटर जोड़ने पड़ते हैं।
WKTrackingData व्यवसाय पैरामीटर विस्तार भी देता है, ताकि कॉल करने वाला कस्टम पैरामीटर जोड़ सके:

जिन नियंत्रणों को ट्रैक नहीं करना है, उन्हें wk_ignoreTracking से बाहर रखा जा सकता है:
self.slider.wk_ignoreTracking = YES;
दूसरी समस्याएँ
अलग इवेंट ट्रैक करने के लिए WKTrackingData aspect-oriented तरीका अपनाता है और विधियों को सीधे swizzle करने के लिए runtime इस्तेमाल करता है।
लेकिन UIAlertView, UIActionSheet, UITableView और UICollectionView को ट्रैक करने के लिए उनके delegate तरीकों को swizzle करना होता है।
यदि
delegate classने वह तरीका पहले से लागू किया है, तो बस उसे swizzle करें।यदि
delegate classने तरीका लागू नहीं किया है और फिर भी इवेंट ट्रैक करना है, तो संबंधितdelegate 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_));
}
}
}
व्यवहार में, UITableView और UICollectionView के delegate ऑब्जेक्ट ने तरीका लागू न किया हो, तो tableView:didSelectRowAtIndexPath: का implementation हाथ से जोड़ने के बाद भी वह नहीं चलता था।
पहले सामान्य tableView:didSelectRowAtIndexPath: कॉल स्टैक देखें:

यहाँ UITableView, cell टैप पर प्रतिक्रिया देने के लिए क्रम से ये चार तरीके कॉल करता है:
- 1. [UITableView _userSelectRowAtPendingSelectionIndexPath:]
- 2. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]
- 3. [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]
- 4. [TableViewController tableView:didSelectRowAtIndexPath:]
कॉल क्रम दिखाता है कि UITableView, delegate class का tableView:didSelectRowAtIndexPath: तरीका कॉल करने से पहले अपना _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: तरीका चलाता है।
सीधे _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect: पर breakpoint लगाकर assembly देखें:

यदि delegate class ने tableView:didSelectRowAtIndexPath: लागू किया है, तो सब सामान्य चलता है और tableView:didSelectRowAtIndexPath: कॉल पूरा होता है।
लेकिन यदि delegate class ने tableView:didSelectRowAtIndexPath: लागू नहीं किया, तो tableView:didSelectRowAtIndexPath: का implementation हाथ से जोड़ने पर भी tableView:didSelectRowAtIndexPath: को प्रतिक्रिया देने का प्रयास नहीं होता।
assembly का अनुसरण करने पर सीधे दूसरे निर्देश पर jump दिखाई देता है।
यहाँ WKTrackingData अलग तरीका अपनाता है और सीधे [UITableView _userSelectRowAtPendingSelectionIndexPath:] को swizzle करता है; UICollectionView के लिए भी ऐसा ही होता है। इससे delegate में संबंधित cell इवेंट तरीका न होने पर भी UITableView और UICollectionView इंटरैक्शन ट्रैक हो सकते हैं।
4. अंतिम विचार
एक इवेंट के लिए कई event_path मान
कई जारी संस्करणों की स्क्रीन में छोटे अंतर हो सकते हैं। तब उसी इवेंट के कई event_path मान हो सकते हैं।
व्यवहार में इस मैपिंग के लिए सर्वर-साइड सहयोगियों का साथ चाहिए। इसे बैकएंड में कॉन्फ़िगर करने योग्य बनाया जा सकता है।
इवेंट एनालिटिक्स में सुधार
ऊपर के व्यवसाय-विशिष्ट विस्तार भाग में व्यवसाय पैरामीटर जोड़ने की बात थी। WKTrackingData इसे categories के माध्यम से लागू करता है।
व्यवहार में पूर्ण-इवेंट ट्रैकिंग ने बहुत bandwidth व्यर्थ की। इनमें कई इवेंट आवश्यक ही नहीं थे [(ŏ_ŏ)]।
अगला सुधार यह है कि सर्वर रिपोर्ट किए जाने वाले हर इवेंट की कॉन्फ़िगरेशन फ़ाइल भेजे। केवल कॉन्फ़िगरेशन से मेल खाने वाला event_path ही अपलोड हो।
इस चरण में व्यवसाय पैरामीटर WKTrackingData के बिना भी लिए जा सकते हैं: सीधे kvc इस्तेमाल करें।
उदाहरण:
key_path = "viewController.banner.url"
यदि आप यहाँ तक पढ़ चुके हैं, तो WKTrackingData खोलकर उसे star दें। सवाल हों तो GitHub पर issue खोलें या नीचे टिप्पणी करें!