IOS optimal traceless buried point scheme

Time:2021-12-1

IOS optimal traceless buried point scheme

In the era of mobile Internet, user behavior data is very important for every company and enterprise. How important it is, how long users stay on this page, what buttons they click, what content they browse, what mobile phone, what network environment, and what version of APP need to be clear. Many business achievements of some large factories are secondary conversion after recommendation based on user operation behavior. On the other hand, log is an auxiliary means to help developers analyze online problems.

With the above demands, how can technicians meet these needs? A technical point – “buried point” is introduced

As a developer, it is particularly important to have a learning atmosphere and a communication circle. This is my IOS communication group:812157648, whether you are Xiaobai or Daniel, welcome to settle in, share bat, Ali interview questions, interview experience, discuss technology, and exchange, learn and grow together!

0x01. Buried point means

There are three main schemes for code embedding points in the industry: Code Manual embedding point, visual embedding point and traceless embedding point. Briefly talk about these buried point schemes.

  • Code manual embedding point: manually call the embedding point interface and upload the embedding point data where the embedding point is needed according to the business requirements (from the perspectives of operation, product and development).
  • Visual buried point: complete the acquisition node through the visual configuration tool, automatically analyze the configuration at the front end and report the buried point data, so as to realize the visual “traceless buried point”
  • Traceless embedding point: complete the statistical upload of user behavior data without difference through technical means. In the later stage of data analysis and processing, appropriate data are selected by technical means for statistical analysis.

0x02. Technical selection

1. Code manual buried point

In the case of this scheme, if the buried point is required, the relevant code of the buried point shall be written in the engineering code. Because it invades the business code and pollutes the business code, the obvious disadvantage isThe cost of buried points is high, and violatedSingle principle

Example 1: if you need to know the relevant information (mobile phone model, APP version, page path, dwell time, action, etc.) when the user clicks the “purchase button”, you need to write the code of buried point statistics in the click event of the button. The obvious disadvantage of this is that there are more embedded code on the previous business logic code. Due to the scattered code, large workload, high code maintenance cost and headache in later reconstruction.

Example 2: if the app adopts hybrid architecture, when the first version of the app is released, the key business logic statistics of H5 are bridged by the key logic defined by native (for example, if H5 calls up the native sharing function, there is a shared buried point event). If a scan function is added one day and the scan buried point bridge is not defined, then when the H5 page changes and the native buried point code is not updated, the changed H5 business will not be accurately counted.

Advantages: the workload of products and operations is small. The relevant business scenarios and data can be restored by comparing with the business mapping table without a lot of processing and processing

Disadvantages: heavy development workload, early-stage needs and good business identification specified by products for statistical analysis of products and operations

2. Visual buried point

The emergence of visual embedding point is to solve the problem that the code embedding point process is complex, the cost is high, and the newly developed page (H5, or the JSON issued by the server to generate the corresponding page) cannot have the embedding point capability in time

In the “buried point editing mode”, the front end configures and binds the path of key business modules in a “visual” way. The front end can uniquely determine the XPath process to the view.

Each time the user operates on a control, axpathString, and then the XPath string (the unique location of view in the front-end system through the interface. Take IOS as an example, APP name, controller name, layer by layer view, serial number of the same type of view: “goodcell. 21. Retailtableview. Goodsviewcontroller. * baoapp”) to the real business module (“treasure app mall controller – distribution commodity list – the 21st commodity was clicked”) Upload the mapping relationship to the server. What XPath is will be described below.

After operating the app, the corresponding XPath and embedded point data are generated (the developer plugs the key data obtained from the server into the front-end UI control through technical means. For IOS, for example, the accessibilityidentifier property of uiview can set the embedded point data obtained from the server) and uploaded to the server.

Advantages: the amount of data is relatively accurate and the cost of later data analysis is low

Disadvantages: the unique identification and positioning of early controls require additional development; The development cost of visualization platform is high; Analysis of additional requirements may be difficult

3. Traceless buried point

Record the user’s behavior on the front-end page without difference through technical means. PV, UV, IP, action, time and other information can be obtained correctly.

Disadvantages: the cost of technical products for developing basic statistical information in the early stage is high, the amount of data for later data analysis is large, and the analysis cost is high (a large amount of data, and the traditional relational database is under great pressure)

Advantages: small workload of developers, comprehensive data, no omission, on-demand analysis of products and operations, and support statistical analysis of dynamic pages

4. How to select

Combined with the above advantages and disadvantages, we chooseCombination of traceless buried point + visual buried pointTechnical solutions.

How to put it? After the key business development is completed and launched online, click the binding correspondence to the server through the visualization scheme (similar to an interface. Think of dreamwaver. You can drag controls on the interface and generate the corresponding HTML code under simple editing).

So what is the corresponding relationship? We only need to locate a front-end element, so the idea is that regardless of the native and web front-end, the control or element is a tree level, DOM tree or UI tree, so we locate this element through technical means, taking native IOS as an example, If you click the add shopping cart button on the product details page, a unique ID “addcartbutton. Goodsviewcontroller. Goodsview. * baoapp” will be generated according to the UI hierarchy. However, when users use the app, they upload the MD5 of this string of things to the server.

There are two reasons for this: it is not very good for the server database to store this long string of things; If the buried point data is hijacked, it is not good to see the plaintext directly. So MD5 upload again.

0x03. Do it with a knife

1. Data collection

The implementation scheme consists of the following key indicators:

  • There are few changes to the existing code. Try not to invade the business code to intercept system events
  • Full collection
  • How to uniquely identify a control element

2. Do not invade business code to intercept system events

Take IOS as an example. We’ll thinkAOP(Aspect Oriented Programming)Aspect oriented programming idea. Dynamically insert the corresponding code before and after the function call. In Objective-C, we can use the runtime feature toMethod SwizzlingTo hook the corresponding function

In order to hook all classes conveniently, we can add a category to nsobject, called nsobject + methodswizzling

#pragma mark - public Method
+ (void)lbp_swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
    class_swizzleInstanceMethod(self, originalSelector, swizzledSelector);
}
 
+ (void)lbp_swizzleClassMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector
{
    //Class methods are actually stored in the class of class objects (i.e. metaclasses), that is, class methods are equivalent to instance methods of metaclasses, so you only need to pass in metaclasses. Other logic is the same as interactive instance methods.
    Class class2 = object_getClass(self);
    class_swizzleInstanceMethod(class2, originalSelector, swizzledSelector);
}
 
#pragma mark - private method
 
void class_swizzleInstanceMethod(Class class, SEL originalSEL, SEL replacementSEL)
{
    /*
     Class class = [self class];
     //Original method
     Method originalMethod = class_getInstanceMethod(class, originalSelector);
     //A new method to replace the original method
     Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
     //Try to add IMP to the source sel first. This is to avoid that the source sel does not implement imp
     BOOL didAddMethod = class_addMethod(class,originalSelector,
     method_getImplementation(swizzledMethod),
     method_getTypeEncoding(swizzledMethod));
     If (didaddmethod) {// adding succeeded: it indicates that the source sel does not implement imp. replace the imp of the source sel with the imp of the exchange sel
     class_replaceMethod(class,swizzledSelector,
     method_getImplementation(originalMethod),
     method_getTypeEncoding(originalMethod));
     }Else {// failed to add: it indicates that the source sel already has an imp. just exchange the imps of the two sels
     method_exchangeImplementations(originalMethod, swizzledMethod);
     }
     */
    
    Method originMethod = class_getInstanceMethod(class, originalSEL);
    Method replaceMethod = class_getInstanceMethod(class, replacementSEL);
    
    if(class_addMethod(class, originalSEL, method_getImplementation(replaceMethod),method_getTypeEncoding(replaceMethod)))
    {
        class_replaceMethod(class,replacementSEL, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
    }else {
        method_exchangeImplementations(originMethod, replaceMethod);
    }
}
Copy code

3. Full collection

We will think of hook appdelegate proxy method, uiviewcontroller life cycle method, button click event, gesture event, click callback method of various system controls, application state switching, etc.

action event
Switching of APP status Add categories to appdelegate, hook life cycle
Uiviewcontroller lifecycle function Add categories to uiviewcontroller, hook life cycle
Click on uibutton, etc Add a category to the uibutton and click the hook event
Uicollectionview, uitableview, etc Add a category in the corresponding cell and click the hook event
Gesture events uitapgesturerecognizer, uicontrol, uiresponder Corresponding system events

Taking the opening time of the statistics page and the opening and closing requirements of the statistics page as an example, we hook the uiviewcontroller

static char *lbp_viewController_open_time = "lbp_viewController_open_time";
static char *lbp_viewController_close_time = "lbp_viewController_close_time";
 
@implementation UIViewController (lbpka)
 
//Add dispatch to the load method_ Once is to prevent manual calls to the load method.
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            [[self class] lbp_swizzleMethod:@selector(viewWillAppear:) swizzledSelector:@selector(lbp_viewWillAppear:)];
            [[self class] lbp_swizzleMethod:@selector(viewWillDisappear:) swizzledSelector:@selector(lbp_viewWillDisappear:)];
 
 
        }
    });
}
 
 
#pragma mark - add prop
 
- (void)setOpenTime:(NSDate *)openTime {
    objc_setAssociatedObject(self,&lbp_viewController_open_time, openTime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (NSDate *)getOpenTime{
    return objc_getAssociatedObject(self, &lbp_viewController_open_time);
}
 
- (void)setCloseTime:(NSDate *)closeTime {
    objc_setAssociatedObject(self,&lbp_viewController_close_time, closeTime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (NSDate *)getCloseTime{
    return objc_getAssociatedObject(self, &lbp_viewController_close_time);
}
 
- (void)lbp_viewWillAppear:(BOOL)animated {
 
    NSString *className = NSStringFromClass([self class]);
    NSString *refer = [NSString string];
    //Todo: does todo only bury pages with local URLs
    if ([self getPageUrl:className]) {
        //Set opening time
       [self setOpenTime:[NSDate dateWithTimeIntervalSinceNow:0]];
        if (self.navigationController) {
            if (self.navigationController.viewControllers.count >=2) {
                //Get the previous VC in the current VC stack
                UIViewController *referVC =  self.navigationController.viewControllers[self.navigationController.viewControllers.count-2];
                refer = [self getPageUrl:NSStringFromClass([referVC class])];
            }
        }
        if (!refer || refer.length == 0) {
            refer = @"unknown";
        }
        [UserTrackDataCenter openPage:[self getPageUrl:className] fromPage:refer];
    }
   
    [self lbp_viewWillAppear:animated];
}
 
- (void)lbp_viewWillDisappear:(BOOL)animated {
    NSString *className = NSStringFromClass([self class]);
    if ([self getPageUrl:className]) {
        [self setCloseTime:[NSDate dateWithTimeIntervalSinceNow:0]];
        [UserTrackDataCenter leavePage:[self getPageUrl:className] spendTime:[self p_calculationTimeSpend]];
    }
    [self lbp_viewWillDisappear:animated];
}
 
#pragma mark - private method
 
- (NSString *)p_calculationTimeSpend {
    
    if (![self getOpenTime] || ![self getCloseTime]) {
        return @"unknown";
    }
    NSTimeInterval aTimer = [[self getCloseTime] timeIntervalSinceDate:[self getOpenTime]];
    
    int hour = (int)(aTimer/3600);
    
    int minute = (int)(aTimer - hour*3600)/60;
    
    int second = aTimer - hour*3600 - minute*60;
    
    return [NSString stringWithFormat:@"%d",second];
}
 
@end
Copy code

4. How to uniquely identify a control element

xpathIt is the unique identification of the operable area defined by the mobile terminal. Since you want to identify the operable controls in the front-end system through a string, XPath needs two indicators:

  • Uniqueness: there are no different controls with the same XPath in the same system
  • Stability: in different versions of the system, when the page structure has not changed, the XPath of the same page and the same control in different versions needs to be consistent.

We think that naive, H5 pages and other systems render in a tree structure, so we connect all the key points (uiviewcontroller, uiview, uiview container (uitableview, uicollectionview, etc.), uibutton…) from the current view to the root element of the system, so as to uniquely locate the control element.

To precisely locate element nodes, see the following figure

Suppose there are three sub views in a uiview, in the order of label, button1 and button2, then the depth is 0, 1 and 2. If the user does something, label1 is removed from the parent view. At this time, uiview has only two child views: button1 and button2, and the depth changes to: 0 and 1.

IOS optimal traceless buried point scheme

View level

It can be seen that only the change of one sub view leads to the change of the depth of other sub views. Therefore, during design, it should be noted that when adding / removing a view, the impact on the depth of the existing view is minimized, and the calculation method of the node depth is adjusted: all nodes in the current view in its parent view are usedSame type as current viewThe index value in the subview.

Let’s take another look at the above example. Initially, the depths of label, button1 and button2 are 0, 0 and 1 in turn. After the label is removed, the depth of button1 and button2 is 0 and 1 respectively. It can be seen that in this example, the removal of label does not affect the depth of button1 and button2. This adjusted calculation method enhances the anti-interference of XPath to a certain extent.

In addition, the calculation method of the adjusted depth depends on the type of each node. Therefore, the name of each node must be placed in theviewPathInstead of just to increase readability.

When identifying the hierarchy of control elements, you need to know “all the layers where the current view is in its parent view”Same type as current viewIndex value in subview. Referring to the above figure, if it is not of the same type, the uniqueness cannot be guaranteed.

5. The unique location of views of the same type

There is a problem. For example, the element we clicked on is uitableviewcell. Although it can locate the logo xxapp.goodsviewcontroller.goodstableview.goodscell, there are multiple cells of the same type, so it is impossible to locate the specific cell that has been clicked by virtue of this string alone.

Of course there are solutions.

  • Find the index of the current element in the same type element of the parent layer. Traverse the child elements of the parent element of the current element according to the current element. If the same element appears, you need to judge which element of the current element is at the level

    Traverse all the child views of the parent view of the current control element. If there are controls of the same type as the current control element, you need to judge the position of the current control element in the control element of the same type, so it can be uniquely located. Example: goodscell-3.goodstableview.goodsviewcontroller.xxapp

//Uiresponder classification
- (NSString *)lbp_identifierKa
{
//    if (self.xq_identifier_ka == nil) {
        if ([self isKindOfClass:[UIView class]]) {
            UIView *view = (id)self;
            NSString *sameViewTreeNode = [view obtainSameSuperViewSameClassViewTreeIndexPath];
            NSMutableString *str = [NSMutableString string];
            //Special addition and subtraction purchases are provided with SPM, but treenode is required to distinguish addition and subtraction
            NSString *className = [NSString stringWithUTF8String:object_getClassName(view)];
            if (!view.accessibilityIdentifier || [className isEqualToString:@"lbpButton"]) {
                [str appendString:sameViewTreeNode];
                [str appendString:@","];
            }
            while (view.nextResponder) {
                [str appendFormat:@"%@,", NSStringFromClass(view.class)];
                if ([view.class isSubclassOfClass:[UIViewController class]]) {
                    break;
                }
                view = (id)view.nextResponder;
            }
            self.xq_identifier_ka = [self md5String:[NSString stringWithFormat:@"%@",str]];
            //            self.xq_identifier_ka = [NSString stringWithFormat:@"%@",str];
        }
//    }
    return self.xq_identifier_ka;
}
 
//Uiview classification
- (NSString *)obtainSameSuperViewSameClassViewTreeIndexPat
{    
    NSString *classStr = NSStringFromClass([self class]);
    //Sub view of cell
    //Uitableview special superview (uitableviewcontentview)
    //UICollectionViewCell
    BOOL shouldUseSuperView =
    ([classStr isEqualToString:@"UITableViewCellContentView"]) ||
    ([[self.superview class] isKindOfClass:[UITableViewCell class]])||
    ([[self.superview class] isKindOfClass:[UICollectionViewCell class]]);
    if (shouldUseSuperView) {
        return [self obtainIndexPathByView:self.superview];
    }else {
        return [self obtainIndexPathByView:self];
    }
}
 
- (NSString *)obtainIndexPathByView:(UIView *)view
{    
    NSInteger viewTreeNodeDepth = NSIntegerMin;
    NSInteger sameViewTreeNodeDepth = NSIntegerMin;
    
    NSString *classStr = NSStringFromClass([view class]);
   
    NSMutableArray *sameClassArr = [[NSMutableArray alloc]init];
    //Depth of all subviews root nodes of the parent view
    for (NSInteger index =0; index < view.superview.subviews.count; index ++) {
        //Same type
        if  ([classStr isEqualToString:NSStringFromClass([view.superview.subviews[index] class])]){
            [sameClassArr addObject:view.superview.subviews[index]];
        }
        if (view == view.superview.subviews[index]) {
            viewTreeNodeDepth = index;
            break;
        }
    }
    //The root node depth of the same type subviews of the parent view
    for (NSInteger index =0; index < sameClassArr.count; index ++) {
        if (view == sameClassArr[index]) {
            sameViewTreeNodeDepth = index;
            break;
        }
    }
    return [NSString stringWithFormat:@"%ld",sameViewTreeNodeDepth];

    Copy code
}
IOS optimal traceless buried point scheme

Page unique identification diagram

6. The same type of view, but the meaning of clicking is different. How to uniquely identify?

Question 5 shows that there are multiple different views on the same interface, and their types are the same (cyclebannerview, but the data sources are different. When the data source length is greater than 1, it will rotate, and uipagecontrol will be displayed below. If the data source is 1, it will not rotate and display uipagecontrol). Case 6 is the same type of view, but the meaning of clicking is different according to the displayed content. That is, the operation needs to know which one the user clicks. As shown in the figure below, “buy now” and “share earned Commission” are the same type of view, but the click meaning is different. We need to uniquely identify them. The previous method passed“Viewpath is configured with view of contract type to add index value“There is still no way to uniquely identify the way. So I came up with a scheme to add a classification to nsobject and add a protocol to the classification. Let the view that needs reuse but unique identification implement the protocol method. Because it is a protocol added to the nsobject classification, the view does not need to be specified to follow.

IOS optimal traceless buried point scheme

Key steps:

  • Add category of nsobject. The protocol with unique identification is declared in the classification

  • Take out the unique ID of the current view where the viewpath is generated (the view calls the protocol method). Then take out the viewpath before splicing

//NSObject+UniqueIdentify.h
#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@class NSObject;
@protocol UniqueIdentify<NSObject>
 
@optional
- (NSString *)setUniqueIdentifier;
 
@end
 
@interface NSObject (UniqueIdentify)<UniqueIdentify>
 
@end
 
NS_ASSUME_NONNULL_END
    
//NSObject+UniqueIdentify.m
#import "NSObject+UniqueIdentify.h"
 
@implementation NSObject (UniqueIdentify)
 
@end
Copy code
//MallTGoodTagView.h
 
extern NSString * _Nonnull const ImmediateyPurchase;
extern NSString * _Nonnull const ShareToAward;
 
//MallTGoodTagView.m
Nsstring * const immediateypurchase = @ "rush purchase now";
Nsstring * const sharetoaward = @ "share earned Commission";
 
- (NSString *)setUniqueIdentifier
{
    if (self.tagString) {
        return self.tagString;
    } else {
        return NSStringFromClass([self class]);
    }
}
Copy code
//Generate viewpath from uiresponder category
- (NSString *)lbp_identifierKa
{
//    if (self.xq_identifier_ka == nil) {
        if ([self isKindOfClass:[UIView class]]) {
            UIView *view = (id)self;
            NSString *sameViewTreeNode = [view obtainSameSuperViewSameClassViewTreeIndexPath];
            NSMutableString *str = [NSMutableString string];
            //Special addition and subtraction purchases are provided with SPM, but treenode is required to distinguish addition and subtraction
            NSString *className = [NSString stringWithUTF8String:object_getClassName(view)];
            if (!view.accessibilityIdentifier || [className isEqualToString:@"lbpButton"]) {
                [str appendString:sameViewTreeNode];
                [str appendString:@","];
            }
            while (view.nextResponder) {
                 if ([view respondsToSelector:@selector(setUniqueIdentifier)]) {
                    NSString *unqiueIdentifier = [view setUniqueIdentifier];
                    if (unqiueIdentifier) {
                        [str appendFormat:@"%@,", unqiueIdentifier];
                    }
                }00
                [str appendFormat:@"%@,", NSStringFromClass(view.class)];
                if ([view.class isSubclassOfClass:[UIViewController class]]) {
                    break;
                }
                view = (id)view.nextResponder;
            }
            self.xq_identifier_ka = [self md5String:[NSString stringWithFormat:@"%@",str]];
            //            self.xq_identifier_ka = [NSString stringWithFormat:@"%@",str];
        }
//    }
    return self.xq_identifier_ka;
}
Copy code
IOS optimal traceless buried point scheme

Unique identification of improved view: rush to buy now
IOS optimal traceless buried point scheme

Unique identification of improved view: share and earn commission

7. How to process data

A. How to process business data

Using the information provided by the systemaccessibilityIdentifierThe official explanation is a string identifying user interface elements

/*

A string that identifies the user interface element.

default == nil

*/

@property(nullable, nonatomic, copy) NSString *accessibilityIdentifier NS_AVAILABLE_IOS(5_0);

Unique identifier issued by the server

The data obtained by the interface contains the unique ID of the current element. For example, if you request the interface to get data in the uitableview interface, there will be a field in the obtained data source, which is specially used to store dynamic and frequently changing business data.

cell.accessibilityIdentifier = [[[SDGGoodsCategoryServices sharedInstance].categories[indexPath.section] children][indexPath.row].spmContent yy_modelToJSONString];
Copy code

B. Basic data

It is designed to be divided into two pod libraries, one is triggerkit (specially used for all events required by hook opportunities, such as page dwell time, page ID and view ID), and the other is appmonitor (specially used to provide maintenance and upload mechanism of basic data and buried point data). Therefore, there is a class called usertrackdatacenter in appmonitor, which specifically provides some basic data (system version, operating system, geographic location, network and other information).

Some methods are exposed to the outside, which are used to hand over the buried point data to appmonitor to maintain the buried point data, reach the appropriate “mechanism”, and then upload the buried point data to the server.

+ (void)clickEventUuid:(NSString *)uuid otherParam:(NSDictionary *)otherParam spmContent:(NSDictionary *)spmContent {
    if (uuid) {
        NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithDictionary:otherParam];
        params[SDGStatisticEventtagKey] = @"clickMonitorV1";
        NSMutableDictionary *valueDict = [[NSMutableDictionary alloc] initWithDictionary:spmContent];
        valueDict[@"xpath"] = uuid?:@"";
        params[SDGStatisticEventtagValue] = valueDict?:@{};
        [[AppMonotior shareInstance] traceEvent:[AMStatisticEvent eventWithInfo:params]];
    }
}
Copy code

8. Data reporting

After the data is collected through the above methods, how to upload it to the back end timely and efficiently for operation analysis and processing?

During app operation, users will click a lot of data. If uploaded in real time, the utilization of the network is low, so a mechanism needs to be considered to control the upload of embedded data generated by users.

The idea is like this. An interface is exposed to the outside to store the generated data in the data center. The data generated by the user will be saved to the memory of appmonitor and set a critical value (memoryeventmax = 50). If the stored value reaches the set critical value memoryeventmax, the data in memory will be written to the file system, saved in the form of zip, and then uploaded to the embedded point system. If the critical value is not reached, but there are some app state switches, you need to save the data to persistence in time. The next time you open the app, read whether there is any non uploaded data from the local persistent place. If so, upload the log information. After success, delete the local log compression package.

The switching strategy of APP application status is as follows:

  • Didfinishlunchwithoptions: the memory log information is written to the hard disk
  • Didbecomeactive: Upload
  • Willterminate: the memory log information is written to the hard disk
  • Didenterbackground: the memory log information is written to the hard disk

The following code is the saving and uploading of APP buried point data

//Write app log information to memory. When the amount in memory reaches a certain scale (exceeding the set amount stored in memory), the log in memory is stored in the file information
- (void)joinEvent:(NSDictionary *)dictionary
{
    if (dictionary) {
        NSDictionary *tmp = [self createDicWithEvent:dictionary];
        if (!s_memoryArray) {
            s_memoryArray = [NSMutableArray array];
        }
        [s_memoryArray addObject:tmp];
        if ([s_memoryArray count] >= s_flushNum) {
            [self writeEventLogsInFilesCompletion:^{
                [self startUploadLogFile];
            }];
        }
    }
}
 
//Data transfer entry for external calls (APP buried point statistics)
- (void)traceEvent:(AMStatisticEvent *)event
{
    //Thread lock to prevent concurrency problems caused by multiple calls
    @synchronized (self) {
        if (event && event.userInfo) {
            [self joinEvent:event.userInfo];
        }
    }
}
 
//Write the data in memory to a file for persistent storage
- (void)writeEventLogsInFilesCompletion:(void(^)(void))completionBlock
{
    NSArray *tmp = nil;
    @synchronized (self) {
        tmp = s_memoryArray;
        s_memoryArray = nil;
    }
    if (tmp) {
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSString *jsonFilePath = [weakSelf createTraceJsonFile];
            if ([weakSelf writeArr:tmp toFilePath:jsonFilePath]) {
                NSString *zipedFilePath = [weakSelf zipJsonFile:jsonFilePath];
                if (zipedFilePath) {
                    [AppMonotior clearCacheFile:jsonFilePath];
                    if (completionBlock) {
                        completionBlock();
                    }
                }
            }
        });
    }
}
 
//Upload each compressed package file in the compressed package folder from the app embedding point to the server, and delete the local log compressed package after success
- (void)startUploadLogFile
{
    NSArray *fList = [self listFilesAtPath:[self eventJsonPath]];
    if (!fList || [fList count] == 0) {
        return;
    }
    [fList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if (![obj hasSuffix:@".zip"]) {
            return;
        }
        
        NSString *zipedPath = obj;
        unsigned long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:zipedPath error:nil] fileSize];
        if (!fileSize || fileSize < 1) {
            return;
        }
        //Calling the interface to upload buried point data
        [self uploadZipFileWithPath:zipedPath completion:^(NSString *completionResult) {
            if ([completionResult isEqual:@"OK"]) {
                [AppMonotior clearCacheFile:zipedPath];
            }
        }];
    }];
}
Copy code

It is used to call the statistics page to upload data when the hook system event occurs

//UIViewController
[UserTrackDataCenter openPage:[self getPageUrl:className] fromPage:refer];  //  Page appears
[UserTrackDataCenter leavePage:[self getPageUrl:className] spendTime:[self p_calculationTimeSpend]];    // Page disappear
Copy code
IOS optimal traceless buried point scheme

Corresponding relationship between unique ID of binding page and function description

Summarize the key steps:

  1. Various events of hook system (uiresponder, uitableview, uicollectionview agent event, uicontrol event, uitapgesturerecognizers), hook application and controller life cycle. Add additional monitoring code before doing the original logic
  2. For the clicked element, the MD5 value of the corresponding unique identifier (addcartbutton. Goodsview. Goodsviewcontroller) is generated according to the view tree
  3. After business development, enter the edit mode of embedding point to bind MD5 and key events of key pages (key modules for operation and product statistics: app level, business module, key page and key operation). For example, addcartbutton.goodsview.goodsviewcontroller.tbapp corresponds to tbapp – Mall module – product details page – adding shopping cart function.
  4. Store the required data
  5. Design a mechanism to wait until the right time to upload data

An example is given to illustrate a complete buried point reporting process

The buried point module is divided into two pod component libraries. Triggerkit is responsible for intercepting system events and obtaining buried point data. Appmonitor is responsible for collecting buried point data, local persistence or memory storage, and uploading buried point data at an appropriate time.

  1. Obtain data through the interface and bind embedded point data to the accessibilityidentifier attribute of the corresponding view

    IOS optimal traceless buried point scheme

    Data from the interface
    IOS optimal traceless buried point scheme

    Bind buried point data to view
  2. Hook system event, click view to get the accessibilityidentifier attribute value

    IOS optimal traceless buried point scheme

    Hook system event to obtain accessibilityidentifier
  3. Send the data to the data center, and the data center processes the data (buried point data combined with app basic information, usertrackdatacenter object on the figure). Store the data in memory or locally according to the situation, and upload it at the right time

    IOS optimal traceless buried point scheme

    After intercepting system events, the data will be handed over to the data center for processing

Original author:Hzk learning with an open mind
Original address:http://002ii.cn/wX76p

Recommended Today

Datawhale pandas punch in – Chapter 4 grouping

Today, I’m learning Chapter 4 – grouping, which I think is a very important and useful knowledge of pandas. This chapter mainly introduces the application of AGG, transform, apply and other functions based on the growpy function.The textbook summarizes the three operations of grouping: aggregation, transformation and filtering. 1. Polymerization: The groupby object has defined […]