Objective-c is a simple language, 95% C. I just added some keywords and grammar at the language level. What really makes Objective-C so powerful is its runtime. It’s small but powerful. At its core is message distribution. The runtime sends a message to the object. An object’s class holds a list of methods. So how are these messages mapped to methods, and how are these methods executed? The answer to the first question is simple. The list of methods for a class is actually a dictionary with key for selectors and IMPs for values. An IMP refers to the in-memory implementation of a method. It’s important to note that the relationship between selector and IMP is determined at run time, not compile time. So we can do something different. This time we are using the runtime to configure the burial point. First of all, what is the burial point: The so-called burial point is to collect some information in the specific process of the application, to track the status of the application, and then to further optimize the product or provide operational data support, including Visits, visitors, Time On Site, Page Views. Also known as page view and Bounce Rate. Such information collection can be roughly divided into two types: page statistics (Track this virtual Page View) and statistical operation (Track this button by an event). The normal practice is to add code to transmit data to the server for statistics in the viewWillAppear and button click of the respective page. Although this way saves brain, it consumes time and is not convenient for later maintenance. The Aspects framework is a lightweight aspect Oriented programming (AOP) framework for the iOS platform. The Aspects framework consists of only two methods: a class method and an instance method. The core principle is:
- (void)trackEvent {
// Hook viewcontroller
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"KZWList" ofType:@"plist"];
NSDictionary *configs = [NSDictionary dictionaryWithContentsOfFile:filePath];
[UIViewController aspect_hookSelector:@selector(viewWillAppear:)
withOptions:AspectPositionAfter
usingBlock:^(id<AspectInfo> aspectInfo) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *className = NSStringFromClass([[aspectInfo instance] class]);
NSString *pageImp = configs[className][@"KZWTrackPageName"];
if (pageImp) {
id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
[tracker set:kGAIScreenName value:pageImp]; [tracker send:[[GAIDictionaryBuilder createScreenView] build]]; }}); } error:NULL]; // Hook Eventsfor (NSString *className in configs) {
Class clazz = NSClassFromString(className);
NSDictionary *config = configs[className];
NSString *pageImp = configs[className][@"KZWTrackPageName"];
if (config[@"KZWTrackEvents"]) {
for (NSDictionary *event in config[@"KZWTrackEvents"]) {
SEL selekor = NSSelectorFromString(event[@"KZWEventSelector"]); [clazz aspect_hookSelector:selekor withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) { Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker]; [tracker send:[[GAIDictionaryBuilder createEventWithCategory:pageImp action:event[@"KZWEventAction"]
label:event[@"KZWEventName"] value:nil] build]]; }); } error:NULL]; }}}}Copy the code
Here are some of the drawbacks:
- Not all events are generated by controls inherited from UIControl, such as gestures and clicking on cells.
- It’s not like all the buttons are clicked and you need to upload them right away, right? Maybe the button’s response method goes through layers of if(){} else{} before you need to bury it.
- If you have parameters
- What about proxy methods?
- What if there are multiple buttons corresponding to one event?
- The event processing methods in the project are not the same, the number of parameters of the method is not the same, and the return value of the method is not the same, how to handle them uniformly? Let’s tackle them one by one. Problem 1: We can hooK events that are not emitted by subclasses of UIControl, but in a different way. We wrote an embedded code in UIControl’s classification, which does hook up the system UIButton click event, because UIButton itself calls this method on UIControl. But for the click event, this is a method that we wrote ourselves, it’s not in the parent UIViewController class, so when we execute our own click event method the method that we’re going to embed in the UIViewController class is not going to get called, so what do we do, We can dynamically add a method to the ViewController we want to hook, and then hook. The concrete method of adding, can refer to the example code of this article.
Question 2: In the case of whether the upload is related to the specific business logic, we can mark it with an attribute value of the class in which the method is located, which is written in the.m file (KVC can obtain the attribute value in the.m file). Let’s hook the method of the class we want to hook, and then handle it according to the tags configured in the PList. (The attribute value here is also unnecessary, we can generate a unique key based on the hash of the class name and method name strings.) It then uses Runtime to automatically associate the mF_condition attribute of the class, which is a dictionary whose key is the one you just generated, and value is the value you get after running the method, which is then compared to the configuration in plIST.
Problem 3: For the buried point data that is closely related to the event class, such as the product ID corresponding to a certain page, such as the ID of the model corresponding to the cell after the cell is clicked on a certain page. At this point we can refer to method 2, add a property, with a property value to store the specific data to be uploaded.
Question 4: proxy method is the same as the gesture of processing, since a class implements a proxy method, then the [someInstance respondsToSelector: someSelector] returns the Boolean value should be YES, and then the other and gestures of treatment is the same.
Problem 5: In the case that many buttons correspond to one response event, we can use RunTime to dynamically add a button property, such as buttonIdentifier, so that we can configure the corresponding buried point processing in plIST.
Problem 6: The problem is to hook all the methods and add the same piece of code to them. In this case, we can use the third party framework Aspects:
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error {
return aspect_add((id)self, selector, options, block, error);
}
Copy the code
Call this interface, because the object that calls this interface in the CLASS of UIViewController is different, and we’re configuring the hook in a different selector from the PList, but we’re executing the same block at the end, so that’s a good solution. It is not good to bury part of the buried point, you can choose a method to bury the point.