IOS Runtime Runtime usage scenario one: dot stats user behavior, deep decoupling

Source: Internet
Author: User
Tags uicontrol
<span id="Label3"></p>Transferred from: http://www.jianshu.com/p/0497afdad36d<br>User Statistics. JPEG<p><p><br>User behavior Statistics (users Behavior Statistics, UBS) has always been an essential part of mobile internet products, also known as buried Point. In order to ensure that the mobile traffic will not be greatly affected, the PM always hope that the coverage of the more extensive the Better. The current practice is to encapsulate the Buried-point code into a tool class, where buried points are embedded in the project, such as click events and Page Jumps. Once the project becomes more and more complex, you will find that the buried code is scattered all over the program and is not conducive to maintenance and reuse. The purpose of this paper is to explore a reusable, decoupled and Easy-to-maintain user statistics scheme using Ios's runtime Mechanism. The discussion is after all a discussion, welcome to the message discussion in the Jane Book. Although this article is long but is the intention of the work, I hope you have patience to read.</p></p> <blockquote> <blockquote> <p>Note: This article requires some iOS runtime basics</p> </blockquote> </blockquote><p><p>The completion of this program will use the following knowledge:</p></p> <blockquote> <blockquote> <ul> <li>Method swizzling (Hook)</li> <li>Unit Test</li> </ul> </blockquote> </blockquote>I. Conventional burial practices<p><p>Let's go back to the beginning of the topic, and we'll start by looking at how the mainstream burying point is done. I coarse buried points are divided into two kinds: 1, page statistics, including page Dwell time, page entry times, 2, Interactive event statistics, including click, double-tap, gesture interaction and so On.</p></p>1) General page Statistics burying Point<p><p>Take the Count of page entry times as an example, the simplest and most brutal approach is to bury the dots on all the pages <code>viewDidAppear:</code> <code>viewDidDisappear:</code> , and pass their corresponding PageID to the Server. The code is probably long Jiangzi:</p></p><pre class="hljs less"><pre class="hljs less"><code class="less"><span class="hljs-variable">@implementation HomeViewController<span class="hljs-comment">//...other methods- (void)<span class="hljs-attribute">viewDidAppear:(BOOL)animated{ <span class="hljs-selector-attr">[super viewWillAppear:animated]; <span class="hljs-selector-attr">[WUserStatistics sendEventToServer:@"PAGE_EVENT_HOME_ENTER"];}- (void)<span class="hljs-attribute">viewDidDisappear:(BOOL)animated{ <span class="hljs-selector-attr">[super viewDidDisappear:animated]; <span class="hljs-selector-attr">[WUserStatistics sendEventToServer:@"PAGE_EVENT_HOME_LEAVE"];}<span class="hljs-variable">@end</span></span></span></span></span></span></span></span></span></code></pre></pre><p><p><code>+[WUserStatistics sendEventToServer:]</code>Encapsulates the network request, passing the ID to the Server. The above scenario has the following drawbacks:</p></p> <blockquote> <blockquote> <p>1, the reuse of Poor. This part of the buried code is hard to reuse for other Projects.<br>2, the workload is Large. Especially when there are more pages, you need to modify more code<br>3, the introduction of "dirty code", difficult to maintain</p> </blockquote> </blockquote><p><p>The "dirty code" referred to in the 3rd means that user behavior analysis is not very much related to the main business, <code>不应该保持如此高的耦合度</code> because the code interferes with our maintenance of the Project's main business. This is my personal opinion.</p></p>2) Common Interactive Event burying point<p><p>The usual practice is to get the ID of the event in the selector of the interactive event and pass it on to the server, the code is probably long Jiangzi:</p></p><pre class="hljs groovy"><pre class="hljs groovy"><code class="groovy">- (IBAction)<span class="hljs-string">onFavBtnPressed:(id)sender{ [WUserStatistics <span class="hljs-string">sendEventToServer:@<span class="hljs-string">"CTRL_EVENT_HOME_FAV"]; <span class="hljs-comment">//...do other things}</span></span></span></span></code></pre></pre><p><p>A slightly larger app, if this is the case, The buried code of that sort will be everywhere. Its shortcomings refer to the page statistics buried part, its reusability is basically zero, that is, in the new project simply can not reuse the buried point Code.</p></p><p><p>Small sum up, the use of conventional methods, although intuitive and convenient, but in terms of reusability, maintainability and other aspects of the lack of. In my opinion, these shortcomings can be well avoided by the use of the Runtime.</p></p>second, Method swizzling, Hook and code injection<p><p>Since the runtime knowledge is not the focus of this article, it is simply introduced Here.<br>In ios, we can replace the implementation of two methods at runtime to "hook" a method and inject the Code. The specific approach is:</p></p> <blockquote> <blockquote> <p>Overload Class's "+ (void) load" method, which uses <code>method_exchangeImplementations</code> interfaces such as runtime to exchange the implementation of the method (set to M) when the program is loaded into Memory. When the method M is called, it is hooked (hook), executing our method.</p> </blockquote> </blockquote><p><p>This technique is also known as <code>Method Swizzling</code> an implementation of aspect-oriented programming (aspect-oriented programming).</p></p><p><p>Replace the implementation of two methods, the code is generally long jiangzi:</p></p><pre class="hljs monkey"><code class="monkey">@<span class="hljs-class"><span class="hljs-keyword">Interface<span class="hljs-title">Whookutility:<span class="hljs-title">nsobject+ (void) Swizzlinginclass: (<span class="hljs-class"><span class="hljs-keyword">Class)<span class="hljs-title">Cls<span class="hljs-title">Originalselector: (<span class="hljs-title">SEL)<span class="hljs-title">Originalselector<span class="hljs-title">Swizzledselector: (<span class="hljs-title">SEL)<span class="hljs-title">swizzledselector;@<span class="hljs-keyword">End@implementation whookutility+ (void) swizzlinginclass: (<span class="hljs-class"><span class="hljs-keyword">Class)<span class="hljs-title">Cls<span class="hljs-title">Originalselector: (<span class="hljs-title">SEL)<span class="hljs-title">Originalselector<span class="hljs-title">Swizzledselector: (<span class="hljs-title">SEL)<span class="hljs-title">swizzledselector{<span class="hljs-class"><span class="hljs-keyword">Class<span class="hljs-title">class =<span class="hljs-title">CLs<span class="hljs-function"><span class="hljs-keyword">Method<span class="hljs-title">Originalmethod = Class_getinstancemethod ( <span class="hljs-class"><span class="hljs-keyword"><span class="hljs-keyword">class, <span class="hljs-title">originalselector); <span class="hljs-function"><span class="hljs-keyword">method <span class="hljs-title">swizzledMethod = Class_ Getinstancemethod (<span class="hljs-class"><span class="hljs-keyword">class, <span class="hljs-title"> swizzledselector); BOOL Didaddmethod = Class_addmethod (<span class="hljs-class"><span class="hljs-keyword">class, originalSelector, Method_getimplementation (swizzledmethod), method_gettypeencoding (swizzledmethod)); <span class="hljs-keyword">if (didaddmethod) {class_replacemethod (<span class="hljs-class"> <span class="hljs-keyword">class, swizzledselector, method_getimplementation (originalmethod), method_ Gettypeencoding (originalmethod)); } <span class="hljs-keyword">else {method_exchangeimplementations (originalmethod, swizzledmethod);}} @<span class="hljs-keyword">end </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre><p><p>This <code>WHookUtility</code> tool class will be used Later. For example, now we want to hook <code>UIViewController</code> <code>viewWillAppear:</code> up the method, can do this:</p></p><pre class="hljs objectivec"><code class="objectivec"><span class="hljs-class"><span class="hljs-keyword">@implementation<span class="hljs-title">Uiviewcontroller (<span class="hljs-title">Userstastistics) + (<span class="hljs-keyword">Void) Load {<span class="hljs-keyword">Static<span class="hljs-built_in"><span class="hljs-built_in">dispatch_once_t oncetoken; <span class="hljs-built_in">dispatch_once (&oncetoken, ^{SEL originalselector = <span class="hljs-keyword">@ Selector (viewwillappear:); SEL swizzledselector = <span class="hljs-keyword"> @selector (swiz_viewwillappear:); [whookutility swizzlinginclass:[<span class="hljs-keyword">self <span class="hljs-keyword">class] Originalselector:originalselector swizzledselector:swizzledselector]; });} <span class="hljs-meta"> #pragma mark-method Swizzling-(<span class="hljs-keyword">void) swiz_viewwillappear: ( Span class= "hljs-built_in" >bool) animated{<span class="hljs-comment">//insert code to execute <span class="hljs-string" nslog (<span>@ "i secretly inserted a piece of code before viewwillappear execution"); <span class="hljs-comment">//can not interfere with the original code flow, insert code at the end of the code should be executed to continue execution [<span class="hljs-keyword">self swiz_ viewwillappear:animated];} <span class="hljs-keyword"> @end </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span></span></span></span></span></span></span></span></code></pre><p><p>For more information on runtime, method swizzling, and aspect-oriented programming, please refer to here</p></p>three, based on the operating time of the buried point scheme<p><p>To facilitate the description below, first introduce a simple project, a total of two pages ( <code>HomeViewController</code> , <code>DetailViewController</code> ), as Follows:</p></p><br>1.gif<p><p>Demand is</p></p> <blockquote> <blockquote> <ol> <li>Count the number of impressions and departures for two pages</li> <li>Count favorites, share the number of click events</li> <li>Less impact on existing engineering code, better</li> </ol> </blockquote> </blockquote>1) count the number of impressions and departures of two pages<p><p>This part should be more intuitive, the way to discard the buried point in each controller, we add category to Uiviewcontroller to hook to <code>viewWillAppear:</code> <code>viewWillDisappear:</code> . Inject the buried point code into these two methods:</p></p><br>Buried Point code injection. jpg<p><p>At this point, each page in the project will have its own page event number (pageeventid), where the buried code knows what to send Pageeventid to the server? Easily sacrifice <code>if-else</code> Artifacts:</p></p><pre class="hljs objectivec"><code class="objectivec">- (<span class="hljs-built_in">NSString *) pageeventid: (<span class="hljs-built_in">BOOL) benterpage{<span class="hljs-built_in"><span class="hljs-built_in">nsstring *selfclassname = <span class="hljs-built_in">nsstringfromclass ([<span class="]" hljs-keyword ">self <span class=" Hljs-keyword ">class]); <span class="hljs-built_in">nsstring *pageeventid = <span class="hljs-literal">nil; <span class="hljs-keyword">if ([selfclassname isequaltostring:<span class="hljs-string">@ "HomeViewController"]) { Pageeventid = benterpage? <span class="hljs-string">@ "event_home_enter_page": <span class="hljs-string">@ "event_home_leave_page";} <span class="hljs-keyword">else <span class="hljs-keyword">if ([selfclassname isequaltostring: <span class="hljs-string">@ "detailviewcontroller"]) {pageeventid = benterpage? <span class="hljs-string">@ "event_detail_enter_page": <span class="hljs-string">@ "event_detail_leave_page"; Span class= "hljs-comment" >//else if (< #expression #>) ...} </span> </span> </span> </span> </span> </span> </span> </span> </span></span></span></span></span></span></span></span></span></span></code></pre><p><p>Of course, we can have a more elegant way, such as using a configuration table instead of a long list of <code>if</code> judgments, so that no matter how many pages are added, the code is always a small segment. We create a new <code>WGlobalUserStatisticsConfig.plist</code> configuration table to hold the Pageeventid of each page as it enters and leaves, with the following structure:</p></p><br>Configure the table Structure. png<p><p><br>therefore, the code that gets the Pageeventid in the page entry and exit statistics is always the following sentence:</p></p><pre class="hljs objectivec"><code class="objectivec">- (<span class="hljs-built_in">NSString *) pageeventid: (<span class="hljs-built_in">BOOL) benterpage{<span class="hljs-built_in">Nsdictionary *configdict = [<span class="hljs-keyword">Self dictionaryfromuserstatisticsconfigplist];<span class="hljs-built_in"><span class="hljs-built_in">nsstring *selfclassname = <span class="hljs-built_in">nsstringfromclass ([<span class="hljs-keyword">self <span class="hljs-keyword">class]); <span class="hljs-keyword">return configdict[selfclassname][<span class="hljs-string">@ "pageeventids"][benterpage? <span class="hljs-string">@ "Enter": <span class="hljs-string">@ "Leave"];} -(<span class="hljs-built_in">nsdictionary *) dictionaryfromuserstatisticsconfigplist{ <span class="hljs-built_in">nsstring *filepath = [[<span class="hljs-built_in">nsbundle mainBundle] Pathforresource:<span class="hljs-string">@ "wglobaluserstatisticsconfig" ofType:<span class="hljs-string">@ "plist"]; <span class="hljs-built_in">nsdictionary *dic = [<span class="hljs-built_in">nsdictionary dictionarywithcontentsoffile:filepath]; <span class="hljs-keyword">return dic;}</span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span></span></span></span></span></span></code></pre><p><p>The effect is as Follows:</p></p><br>Page Burying Point. gif<p><p>This is the completion of the page in and out of the statistics of the buried point, and reached our 3rd expectation: there is no impact on the existing Code. By way of method swizzling the existing works do not even need <code>import</code> any files! The only plist configuration table that needs to be maintained in Late-stage code Changes.</p></p>2) statistics collection, Sharing the number of click events<p><p>In line with the previous section, it is clear that decoupling needs to be achieved by CATEGORY+HOOK. The collection and sharing in this demo is UIButton type, you can consider adding UIButton catogory. But the better way is to add the Uicontrol category, so that the buried point code can be covered in all Uicontrol subclasses, such as button, switch, segment, etc., Improve Reusability.<br>Since to hook, it is necessary to know exactly what to hook <code>UIControl</code> (a few) method, only part of the method is to meet the requirements of the buried point, it is best to hook the method can provide target, ActionName and other Information. This is a process of trying.<br><code>UIControl</code>The method list has the Following:</p></p><br>List of Uicontrol methods. png<p><p><br>By observing the name and parameters of the method, we have reason to suspect that it is the second-lowest, because it carries a lot of seemingly valuable information:</p></p><pre class="hljs objectivec"><pre class="hljs objectivec"><code class="objectivec">- (<span class="hljs-keyword">void)sendAction:(SEL)action to:(<span class="hljs-keyword">nullable <span class="hljs-keyword">id)target forEvent:(<span class="hljs-keyword">nullable <span class="hljs-built_in">UIEvent *)event;</span></span></span></span></span></code></pre></pre><p><p>Then write the test code to See:</p></p><pre class="hljs objectivec"><code class="objectivec"><span class="hljs-class"><span class="hljs-keyword">@implementation<span class="hljs-title">Uicontrol (<span class="hljs-title">Userstastistics) + (<span class="hljs-keyword">Void) Load {<span class="hljs-keyword">Static<span class="hljs-built_in">dispatch_once_t oncetoken;<span class="hljs-built_in">Dispatch_once (&oncetoken, ^{SEL originalselector =<span class="hljs-keyword">@selector (sendAction:to:forEvent:); SEL Swizzledselector =<span class="hljs-keyword">@selector (swiz_sendAction:to:forEvent:); [whookutility swizzlinginclass:[<span class="hljs-keyword">Self<span class="hljs-keyword">class] Originalselector:originalselector swizzledselector:swizzledselector]; });}<span class="hljs-meta"><span class="hljs-meta">#pragma mark-method swizzling-(<span class="hljs-keyword">void) swiz_sendaction: (SEL) action to: (<span class="hljs-keyword">id) target forevent: (<span class="hljs-built_in">uievent *) Event { <span class="hljs-comment">//insert embed code [self<span class="hljs-keyword">performuserstastisticsaction:action to:target forevent:event]; [self<span class="hljs-keyword">swiz_sendaction:action to:target forevent:event];} -(<span class="hljs-keyword">void) performuserstastisticsaction: (SEL) action to: (<span class="hljs-keyword">id) target forevent: (<span class="hljs-built_in">uievent *) event; { <span class="hljs-built_in">NSLog (<span class="hljs-string">@ "\n***hook success.\n[1]action:%@\n[2]target:%@ \n[3]event:%ld", <span class="hljs-built_in">nsstringfromselector ( action), target, (<span class="hljs-keyword">Long) event); <span class="hljs-keyword">@end</span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span> </span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre><p><p>Log such as:</p></p><br>Log.png<p><p>As you can see, the Category+method swizzling has successfully hook up to all click events without modifying any code in the existing project, and in the hook code we know that a click event is the <code>target</code> viewcontroller, Also know the Click event Response function name, know the click <code>TouchSet</code> . This information has been able to meet the requirements of the Buried Point.<br>Similar to the page statistics buried point, We also use the plist configuration table to avoid a long list of <code>if-else</code> judgments:</p></p><br>Click event Configuration Table Structure. png<p><p><br>With this configuration table it is easy to get the event ID (controleventid) of a single click Event:</p></p><pre class="hljs markdown"><pre class="hljs markdown"><code class="markdown">NSString *actionString = NSStringFromSelector(action);//获取SEL stringNSString *targetName = NSStringFromClass([target class]);//viewController nameNSDictionary *configDict = [self dictionaryFromUserStatisticsConfigPlist];eventID = configDict[<span class="hljs-string">targetName][<span class="hljs-symbol">@"ControlEventIDs"][<span class="hljs-string">actionString];</span></span></span></code></pre></pre> <blockquote> <blockquote> <p>In fact, I have divided all the event IDs of a page cell into two categories: the page Event ID (pageeventids, page entry and exit, etc.), the interactive event ID (controleventids, click, double-tap, gesture, etc.). Classification helps to automate post-maintenance using Unit Test.</p> </blockquote> </blockquote><p><p>Buried Point effect</p></p><br>Click the embedded point effect. gif<p><p>Here first to do a periodic summary, the ideas presented in this paper have the following advantages:</p></p> <blockquote> <blockquote> <ul> <li>Basic decoupling from engineering code to avoid introducing "dirty code"</li> <li>Even if the post-engineering code is refactored, only the plist configuration table needs to be modified</li> <li>Maintaining the configuration table is easier than maintaining the code scattered across the project</li> </ul> </blockquote> </blockquote>Iv. post-maintenance based on unit testing<p><p>As the saying goes, entrepreneurship is difficult yeh more Difficult. The previous idea basically can complete the preliminary burying point Demand. however, code refactoring is very frequent in Real-world Projects. This means that in Multi-person collaborative development, code refactoring frequently in projects that respond to event methods or even page names can be broken, resulting in the event ID not get the buried point Failure.<br>The code changes are nothing more than the following (here are only the cases where the response to the event has changed):</p></p> <blockquote> <blockquote> <p>1. Response Event Method name change or delete</p> </blockquote> </blockquote><p><p>For example, The collection event was originally <code>onFavBtnPressed:</code> , then changed to <code>onFavouriteBtnPressed:</code> . The code changes but the Plist configuration table has been modified because the developer inadvertently forgot to synchronize. This kind of negligence in the development of pressure in the case of large progress is a great probability of happening. Because the code does not match the configuration table, it causes EventID to be Nil. In this case, unit testing is necessary, and the use of complete test cases can detect this mismatch before the release to avoid the failure of the buried Point.<br>In the unit test we first read the plist configuration file and traverse all the Pages. Traverse all the Controleventids within a page to determine the name of each response function <code>respondsToSelector:</code> :</p></p><br>Unit Test Introduction. PNG<p><p>The single test code is as Follows:</p></p><pre class="hljs objectivec"><code class="objectivec">- (<span class="hljs-keyword">Void) testifuserstatisticsconfigplistvalid{<span class="hljs-built_in">Nsdictionary *configdict = [<span class="hljs-keyword">Self dictionaryfromuserstatisticsconfigplist];<span class="hljs-built_in">Xctassertnotnil (configdict,<span class="hljs-string">@ "wglobaluserstatisticsconfig.plist Load failed"); [configdict enumeratekeysandobjectsusingblock:^ (<span class="hljs-built_in">NSString * _nonnull key,<span class="hljs-keyword">ID _nonnull obj,<span class="hljs-built_in">BOOL * _nonnull STOP) {<span class="hljs-built_in">Xctassert ([obj iskindofclass:[<span class="hljs-built_in">Nsdictionary<span class="hljs-keyword">class]],<span class="hljs-string">@ "plist file structure may have changed, please confirm");<span class="hljs-built_in">NSString *targetpagename = key; Class Pageclass =<span class="hljs-built_in">Nsclassfromstring (targetpagename);<span class="hljs-keyword">ID pageinstance = [[pageclass alloc] init];<span class="hljs-comment">A pagedict corresponds to a page, storing pageid, all action and corresponding EventID<span class="hljs-built_in">Nsdictionary *pagedict = (<span class="hljs-built_in">Nsdictionary *) obj;<span class="hljs-comment">Page configuration information<span class="hljs-built_in">Nsdictionary *pageeventiddict = pagedict[<span class="hljs-string">@ "pageeventids"];<span class="hljs-comment">Interactive configuration information<span class="hljs-built_in">Nsdictionary *controleventiddict = pagedict[<span class="hljs-string">@ "controleventids"];<span class="hljs-built_in">Xctassert (pageeventiddict,<span class="hljs-string">@ "the plist file does not contain a PageID field or the field value is empty");<span class="hljs-built_in">Xctassert (controleventiddict,<span class="hljs-string">@ "the plist file does not contain a eventids field or the field value is empty"); [pageeventiddict enumeratekeysandobjectsusingblock:^ (<span class="hljs-built_in">NSString * _nonnull key,<span class="hljs-keyword">ID _nonnull value,<span class="hljs-built_in">BOOL * _nonnull STOP) {<span class="hljs-built_in">Xctassert ([value iskindofclass:[<span class="hljs-built_in">NSString<span class="hljs-keyword">class]],<span class="hljs-string">@ "plist file structure may have changed, please confirm");<span class="hljs-built_in">Xctassertnotnil (value, <span class="hljs-string"><span class="hljs-string">@ "event_id is empty, please confirm"); }]; [controleventiddict enumeratekeysandobjectsusingblock:^ (<span class="hljs-built_in">NSString * _Nonnull key, <span class="hljs-keyword">id _nonnull value, <span class="hljs-built_in">bool * _nonnull stop) { <span class="hljs-built_in">xctassert ([value iskindofclass:[<span class="hljs-built_in">nsstring <span class=" Hljs-keyword ">class]", <span class="hljs-string">@ "plist file structure may have changed, please confirm"); <span class="hljs-built_in">nsstring *actionname = key; SEL Actionsel = <span class="hljs-built_in">nsselectorfromstring (actionname); <span class="hljs-built_in">xctassert ([pageinstance respondstoselector:actionsel], <span class="hljs-string">@ " The code does not match the plist file function, please confirm:-[%@%@] ", targetpagename, actionname); <span class="hljs-comment">//event_id cannot be empty <span class="hljs-built_in">xctassertnotnil (value, <span class="hljs-string">@ "event_id is empty, please confirm"); }]; }];}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre><p><p>Let's test it, if we <code>HomeViewController</code> change it to a <code>onFavBtnPressed:</code> <code>onMyFavBtnPressed:</code> later unit test the result is:</p></p><br>Unit tests do not pass. png<p><p>This change is easy to capture for a single measurement,</p></p> <blockquote> <blockquote> <p>As long as the Xctassert log is detailed enough, maintenance is actually quite easy.</p> </blockquote> </blockquote><p><p>The log in has clearly indicated that <code>-[HomeViewController onFavBtnPressed:]</code> the method has Changed.</p></p> <blockquote> <blockquote> <p>2. New response event in code</p> </blockquote> </blockquote><p><p>This situation is common in new releases where there are new requirements for embedding. If a response event is added to the code and the response event is in the list of buried points required by the pm, plist may miss the Event. This is a tricky situation. The previous case is based on the Plist list to verify the code, here is the reverse, according to the code to verify whether the plist is Missing. But the problem is that the response function in a project is often very much, and not all response functions need to be buried. There is no difference between the response function that needs to be buried and the other response functions.<br>One way to do this is to tighten the code review to avoid forgetting to add a buried point to the configuration table (this is nonsense); one is: a method name that requires a Buried-point response function contains a contract string, such as a collection Event's method name to indicate that the <code>onFavBtnPressed_UA:</code> event needs to be Buried. The Run-time API is then used in the unit test to <code>class_copyMethodList</code> take out all the marked <code>_UA</code> functions, followed by the presence of Lieutenant Colonel Plist. Does not exist indicates that the test case does not pass, prompting the developer to Verify.</p></p><p><p>Code SLIGHTLY. If you are unfamiliar with unit tests, you can refer to Unit tests</p></p><p><p>Small summary:<br>The reasonable unit test can alleviate the considerable burden for the later maintenance of the scheme, the completeness of test case is very important, and it needs careful design.</p></p>V. Conclusion<p><p>The above is the combination of the operation of the design of the user statistical ideas of the whole content. It should be said that the scheme of reusability and decoupling is good, both for the new project, but also suitable for the project has been Created. Seems to be more content, in fact, summed up just a few steps: plist configuration table +hook+ Unit Test. It is also reasonable to use method swizzling to centralize the management of buried point code, which is advantageous to the development, tracking and maintenance of special personnel. of course, The above ideas only consider the simple situation, more complex situations need to be flexible, but the general idea is the Case.<br>The idea may not be perfect, but as an attempt it is possible. The roads are all coming Out.</p></p><p><p>This is the demo address, remember star oh!</p></p><p><p>IOS Runtime Runtime usage scenario one: dot stats user behavior, deep decoupling</p></p></span>

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.