ক্লায়েন্ট-সাইড ইভেন্ট ট্র্যাকিং মোটামুটি তিন ধরনের:

  • কোড-ভিত্তিক ট্র্যাকিং
  • ভিজ্যুয়াল ট্র্যাকিং
  • স্বয়ংক্রিয় ট্র্যাকিং

এই লেখায় মূলত স্বয়ংক্রিয় ট্র্যাকিং-এর ধারণা ও বাস্তবায়ন প্রক্রিয়া বলা হয়েছে।

1. কোড-ভিত্তিক ট্র্যাকিং

কোড-ভিত্তিক ট্র্যাকিং মানে প্রতিটি ট্র্যাকযোগ্য বিন্দুতে ইন্টারফেস কল করে সরাসরি ইভেন্ট ডেটা আপলোড করা। অধিকাংশ তৃতীয়-পক্ষ অ্যানালিটিক্স সেবা এর জন্য সুবিধাজনক api দেয়।

কিন্তু এতে ট্র্যাকিং কোড ব্যবসায়িক যুক্তিজুড়ে ছড়িয়ে তার সঙ্গে শক্তভাবে জড়িয়ে যায়। স্ক্রিন বা প্রবাহ বদলালে পুরোনো ইভেন্টের কী হবে বা নতুন লাগবে কি না বোঝা যায় না। ইভেন্ট বাড়লে ছড়ানো কোড রক্ষণাবেক্ষণ দুঃস্বপ্ন হয়। ম্যাক্রো ও ফ্যাক্টরি ক্লাস কোড সহজ করতে পারে, তবে মূল সমস্যা বদলায় না।

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    // 埋点
    APE(kEvent4001);
}

2. ভিজ্যুয়াল ট্র্যাকিং

ভিজ্যুয়াল ট্র্যাকিংয়ে দৃশ্যমান টুল দিয়ে সংগ্রহ বিন্দু কনফিগার করা হয়। ক্লায়েন্ট নিজে কনফিগারেশন বিশ্লেষণ করে ইভেন্ট পাঠায়, ফলে কথিত “চিহ্নহীন ট্র্যাকিং” হয়। একটি প্রতিনিধিত্বশীল ওপেন-সোর্স সমাধান Mixpanel।

3. স্বয়ংক্রিয় ট্র্যাকিং

স্বয়ংক্রিয় বা ফুল-ইভেন্ট ট্র্যাকিং মানে আসলে কোনো ইন্সট্রুমেন্টেশন নেই এমন নয়। এটি সব ইভেন্ট সংগ্রহ ও পাঠায়; দরকারি ডেটা সার্ভার বেছে নেয়।

আরও উন্নত করতে সার্ভার কনফিগারেশন ফাইল পাঠাতে পারে, যাতে ক্লায়েন্ট নিজেই ইভেন্ট ছাঁকে।

KPI-এর জন্য, এই ধারণায় আবার চাকা বানিয়ে WKTrackingData তৈরি করলাম।

এখানে WKTrackingData বাস্তবায়নের কিছু সমস্যা ও ধারণা লিখেছি।

বাস্তবায়নের ধারণা সহজ। কোডের বিন্যাস:

মূল WKTrackingData স্থাপত্য: ম্যানেজার, ইভেন্ট পাথ ও AOP মডিউল

এখানে WKTrackingDataManager সব ডেটা ব্যবস্থাপনা ও ইভেন্ট ট্র্যাকিং কনফিগারেশন সামলায়।

ইভেন্ট ট্র্যাকিং কনফিগারেশনের মূল ব্যবহার উদাহরণ

WKTrackingDataViewPathHelper তৈরি করে event_path মান।

AOP প্রতিটি ইভেন্ট ট্র্যাক করার মডিউল।

বিস্তারিত ব্যবহারের জন্য GitHub দেখুন।

একটি ইভেন্ট কীভাবে অনন্যভাবে শনাক্ত হবে?

দুটি প্রশ্ন: ইভেন্টকে কীভাবে প্রকাশ করব এবং তার অনন্যতা কীভাবে নিশ্চিত করব?

ইভেন্ট প্রকাশ

ইভেন্ট event_path দিয়ে প্রকাশ করা হয়। ইভেন্ট ঘটানো responder-এর জন্য মূল ধারণা হলো তার রেসপন্ডার চেইন অনুসরণ করে সেই চেইনের path বানানো।

তৈরি রেসপন্ডার path এমন:

"event_path" = "#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate
ইভেন্টের অনন্য পরিচয়

কিছু ব্যবসায়িক পরিস্থিতিতে একই button বা অন্য কন্ট্রোলের বৈশিষ্ট্য বদলালে তা ভিন্ন ব্যবসায়িক ইভেন্ট বোঝায়।

যেমন হোম স্ক্রিনের button সাইন আউট অবস্থায় সাইন ইন করতে চাপুন, আর সাইন ইনের পর পরিচয় যাচাই বাকি থাকলে পরিচয় যাচাই করুন দেখাতে পারে।

একই button-এর ভিউ ট্রি বদলায়নি, তাই তৈরি রেসপন্ডার 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 অ্যাসপেক্ট-ওরিয়েন্টেড পদ্ধতি অনুসরণ করে এবং runtime দিয়ে সরাসরি মেথড swizzle করে।

তবে 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: মেথড চালায়।

এই মেথডে breakpoint বসান: _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:, তারপর assembly দেখুন:

ডেলিগেট মেথড শাখা দেখানো মূল assembly দৃশ্য

যদি delegate class এটি বাস্তবায়ন করে tableView:didSelectRowAtIndexPath:, সব স্বাভাবিক এবং tableView:didSelectRowAtIndexPath: কল সম্পন্ন হয়।

কিন্তু delegate class যদি tableView:didSelectRowAtIndexPath: বাস্তবায়ন না করে, হাতে tableView:didSelectRowAtIndexPath:-এর implementation যোগ করেও tableView:didSelectRowAtIndexPath:-এ সাড়া দেওয়ার চেষ্টা হয় না।

assembly অনুসরণ করলে সরাসরি অন্য instruction-এ jump দেখা যায়।

WKTrackingData এখানে অন্য পথ নিয়ে সরাসরি [UITableView _userSelectRowAtPendingSelectionIndexPath:] swizzle করে (UICollectionView-এর ক্ষেত্রেও একই)। ফলে UITableView ও UICollectionView-এর delegate অবজেক্ট সংশ্লিষ্ট cell ইভেন্ট মেথড বাস্তবায়ন না করলেও ট্র্যাক হয়।

4. শেষ কথা

এক ইভেন্টের জন্য একাধিক event_path

অনলাইনে সামান্য ভিন্ন স্ক্রিনের একাধিক সংস্করণ থাকলে একই ইভেন্টের একাধিক event_path হতে পারে।

বাস্তবে এই ম্যাপিংয়ে সার্ভার-সাইড সহকর্মীদের সহযোগিতা লাগে; ব্যাকএন্ডে কনফিগারযোগ্য করা যায়।

ইভেন্ট অ্যানালিটিক্স উন্নত করা

আগের ব্যবসাভিত্তিক সম্প্রসারণ অংশে ব্যবসায়িক প্যারামিটার যোগ করা বলা হয়েছে। WKTrackingData categories দিয়ে এটি করে।

বাস্তবে ফুল-ইভেন্ট ট্র্যাকিং প্রচুর ব্যান্ডউইথ নষ্ট করেছিল। অনেক ইভেন্টই দরকার ছিল না [(ŏ_ŏ)]।

আরও উন্নত করতে সার্ভার যে সব ইভেন্ট চায় তার কনফিগারেশন ফাইল পাঠাতে পারে। শুধু কনফিগারেশনের সঙ্গে মেলা event_path আপলোড হবে।

এই পর্যায়ে ব্যবসায়িক প্যারামিটার পেতে WKTrackingData দরকার নেই; সরাসরি kvc ব্যবহার করা যায়।

উদাহরণ:

key_path = "viewController.banner.url"

এত দূর পড়লে WKTrackingData খুলে একটি star দিন। প্রশ্ন থাকলে GitHub-এ issue খুলুন বা নিচে মন্তব্য করুন!