成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

iOS 統(tǒng)計(jì)打點(diǎn)那些事

移動(dòng)開(kāi)發(fā)
單來(lái)說(shuō),就是可以動(dòng)態(tài)的在函數(shù)調(diào)用的前后插一段代碼。iOS 可以使用 Pete Steinberger 開(kāi)發(fā)的 Aspects 這個(gè)庫(kù),大致原理是在 runtime 層,通過(guò) swizzle method 來(lái)實(shí)現(xiàn)的。 來(lái)看一個(gè)小 Demo

[[148848]]

統(tǒng)計(jì)打點(diǎn)是 App 開(kāi)發(fā)里很重要的一個(gè)環(huán)節(jié),App 的運(yùn)行狀態(tài)、改版后的效果、用戶的各種行為等都需要打點(diǎn),市面上也有不少可供選擇的第三方庫(kù)。 假設(shè)產(chǎn)品有這么個(gè)需求:當(dāng)用戶在詳情頁(yè)點(diǎn)擊購(gòu)買(mǎi)按鈕時(shí),記錄一下事件。我們實(shí)現(xiàn)起來(lái)大概會(huì)是這樣

  1. // DetailViewController.m 
  2.  
  3. - (void)onBuyButtonTapped:(UIButton *)button 
  4. // do some stuff, maybe send a request to server 
  5. [XXXAnalytics event:kSomeEventYouDefined]; 

這個(gè)需求就這樣輕松搞定了,但細(xì)細(xì)想想還是有不少問(wèn)題的:

頁(yè)面上會(huì)有其他的 Button,可能每個(gè) Button 都要放上這么一段代碼。

這些統(tǒng)計(jì)其實(shí)跟具體的業(yè)務(wù)無(wú)關(guān),沒(méi)必要跟業(yè)務(wù)代碼混雜在一起,不優(yōu)雅。

當(dāng)改版或者重構(gòu)時(shí),有可能忘了把相應(yīng)的打點(diǎn)代碼遷移過(guò)去。

所以需要一種更好的方式來(lái)做這件事,這就是使用 AOP(Aspect-Oriented-Programming),翻譯過(guò)來(lái)就是「面向切面編程」

通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)。

簡(jiǎn)單來(lái)說(shuō),就是可以動(dòng)態(tài)的在函數(shù)調(diào)用的前后插一段代碼。iOS 可以使用 Pete Steinberger 開(kāi)發(fā)的 Aspects 這個(gè)庫(kù),大致原理是在 runtime 層,通過(guò) swizzle method 來(lái)實(shí)現(xiàn)的。

來(lái)看一個(gè)小 Demo

  1. [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(idaspectInfo, BOOL animated) { 
  2. NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated); 
  3. } error:NULL]; 

這樣在 UIViewController 的 viewWillAppear: 被調(diào)用后,還會(huì)再調(diào)一下我們定義的 Block,這段日志就會(huì)被輸出。而打點(diǎn)正好符合這種場(chǎng)景:正事干完之后,額外干一些跟業(yè)務(wù)無(wú)關(guān)的事情。

上面的例子,我們通過(guò) AOP 來(lái)做的話,大概就是這樣

  1. // DetailViewController.m 
  2. - (void)onBuyButtonTapped:(UIButton *)button 
  3. // do some stuff, maybe send a request to server 
  4. // no need to call [XXXAnalytics event:] 
  5.  
  6. // AppDelegate.m 
  7. - (void)setupAnalytics 
  8. [DetailViewController aspect_hookSelector:@selector(onBuyButtonTapped:) withOptions:AspectPositionAfter usingBlock:^(idaspectInfo, BOOL animated) { 
  9. [XXXAnalytics event:kSomeEventYouDefined]; 
  10. } error:NULL]; 

這樣統(tǒng)計(jì)代碼就從業(yè)務(wù)代碼中剝離出來(lái)了。但是又產(chǎn)生了一個(gè)新問(wèn)題,多個(gè) Button Event,豈不是要寫(xiě)很多行這樣的代碼,「重復(fù)」這樣的事情,作為一個(gè)程序員怎么能忍,簡(jiǎn)單,造一個(gè)方法

  1. - (void)trackEventWithClass:(Class)klass selector:(SEL)selector event:(NSString *)event 
  2. [klass aspect_hookSelector:@selector(selector) withOptions:AspectPositionAfter usingBlock:^(idaspectInfo, BOOL animated) { 
  3. [XXXAnalytics event:event]; 
  4. } error:NULL]; 

使用起來(lái)就像這樣

  1. - (void)setupAnalytics 
  2. [self trackEventWithClass:DetailViewController selector:@seletor(onBuyButtonTapped:) event:kSomeEventYouDefined]; 
  3. [self trackEventWithClass:ListViewController selector:@seletor(followButtonTapped:) event:kAnotherEventYouDefined]; 
  4. // ... 

看起來(lái)又干凈了些。這時(shí),產(chǎn)品經(jīng)理又提了個(gè)需求:當(dāng)這個(gè)按鈕點(diǎn)擊時(shí),如果已經(jīng)登錄了,發(fā)送 EventA,如果沒(méi)有登錄則發(fā)送 EventB,也就是說(shuō),不再只是 [XXXAnalytics event:] 這么簡(jiǎn)單了,還需要加上額外的邏輯,這也難不倒我們,加上一個(gè) block 即可。

  1. - (void)trackEventWithClass:(Class)klass 
  2. selector:(SEL)selector 
  3. eventHandler:(void (^)(idaspectInfo))eventHandler 
  4. [klass aspect_hookSelector:@selector(selector) withOptions:AspectPositionAfter usingBlock:^(idaspectInfo, BOOL animated) { 
  5. if (eventHandler) { 
  6. eventHandler(aspectInfo); 
  7. } error:NULL]; 
  8.  
  9. // 使用 
  10. [self trackEventWithClass:DetailViewController selector:@seletor(onBuyButtonTapped:) eventHandler:^(idaspectInfo){ 
  11. user.loggedIn ? [XXXAnalytics event:EventA] : [XXXAnalytics event:EventB]; 
  12. }]; 

好了,現(xiàn)在只要不是太復(fù)雜的打點(diǎn)邏輯(那些需要方法上下文變量的)我們都能應(yīng)付了,接下來(lái)就該等產(chǎn)品來(lái)驗(yàn)收了。產(chǎn)品搬了個(gè)凳子坐在身邊,然后點(diǎn)一下 Button,看一下 Console,被幾輪蹂躪后,產(chǎn)品也慢慢地接受了這種驗(yàn)收方式。后來(lái)某一天,忽然發(fā)現(xiàn)某一項(xiàng)或某幾項(xiàng)數(shù)據(jù)有異常,然后找到開(kāi)發(fā),瞄了一眼:哦,這個(gè)方法被重構(gòu)了。或者新加的方法忘了加統(tǒng)計(jì)了。只能等到下個(gè)版本再加上了,如果只是一般的統(tǒng)計(jì)數(shù)據(jù)倒還好,跟錢(qián)相關(guān)的就麻煩了。

那么有沒(méi)有一種直觀的驗(yàn)證方式呢?當(dāng)然,程序員是***的呀。一個(gè)理想的狀況是,產(chǎn)品打開(kāi) App 后,開(kāi)啟某個(gè)開(kāi)關(guān)就能看到所有會(huì)發(fā)送 Event 的按鈕,就像這樣

 

其中數(shù)字代表了 EventID。如何實(shí)現(xiàn)呢?還記得注冊(cè)事件時(shí),我們有傳入 class 和 selector 么,一般我們都會(huì)有一個(gè) BaseViewController,那么就可以在 BaseViewController 的 viewDidAppear: 里做點(diǎn)文章了。

  1. // BaseViewController.m 
  2. - (void)viewDidAppear:(BOOL)animated 
  3. [super viewDidAppear:animated]; 
  4. // 獲取已經(jīng)注冊(cè)過(guò)的 classes 
  5. NSDictionary *registeredClasses = [OurAnalytics sharedInstance].registeredClasses; 
  6.  
  7. [registeredClasses enumerateKeysAndObjectsUsingBlock:^(NSString *className, NSArray *selectors, BOOL *stop) { 
  8. if ([self isKindOfClass:NSClassFromString(className)]) { 
  9. // 如何根據(jù) selector 找到它的宿主? 
  10. }]; 

所以現(xiàn)在問(wèn)題就剩下,如何根據(jù) selector 找到對(duì)應(yīng)的 Button,這里要注意,有些 Button 可能要等網(wǎng)絡(luò)請(qǐng)求完成才會(huì)出現(xiàn),比如 TableViewCell 里的 Button。

沒(méi)有想到太方便的方法,簡(jiǎn)單粗暴點(diǎn)就是設(shè)置個(gè) Timer 每隔一段時(shí)間掃一下 subviews,如果是 button 或 包含 tapGesture 的,就拿它們的 action 對(duì)比一下,如果 match 就可以高亮那個(gè) button / view 了。

EventID 也一樣,之前在注冊(cè)時(shí)也會(huì)傳一個(gè) EventID 過(guò)來(lái),這里直接顯示出來(lái)即可。對(duì)于那些傳 eventHandler 的就不行了。

所以理論上是可行的,性能上會(huì)稍微有點(diǎn)損耗,尤其是當(dāng) subViews 的結(jié)構(gòu)比較復(fù)雜時(shí),不過(guò)只是內(nèi)部用來(lái)做驗(yàn)證,所以這也不是什么問(wèn)題。

看起來(lái)效果已經(jīng)不錯(cuò)了,有沒(méi)有可能讓這套體系再靈活一些?比如可以從后端制定打點(diǎn)規(guī)則?客戶端只是讀取一個(gè)配置文件,就像這樣

  1. - (void)setupAnalytics 
  2. // analyticsRules 是從配置文件中讀取出來(lái)的 
  3. [analyticsRules enumerateObjectsUsingBlock:^(NSDictionary *rules, NSUInteger idx, BOOL *stop) { 
  4. Class klass = NSClassFromString(rules[@"class"]); 
  5. SEL selector = NSSelectorFromString(rules[@"selector"]); 
  6. NSString *eventID = rules[@"eventID"]; 
  7. [self trackEventWithClass:klass seletor:seletor event: eventID]; 
  8. }]; 

那如果在后臺(tái)的時(shí)候填錯(cuò)了 Class 或 Selector 怎么辦?還好有 objc_getClassList 和 class_copyMethodList 這兩個(gè)運(yùn)行時(shí)方法,有了它們就可以在 App 啟動(dòng)時(shí)掃一遍已注冊(cè)的類(過(guò)濾掉 UI / NS 開(kāi)頭的),然后將它們的 seletor 也一并保存下來(lái)發(fā)送給服務(wù)端,當(dāng)然這種操作只需在適當(dāng)?shù)臅r(shí)機(jī)做一下就可以了,比如集成打包時(shí)。

現(xiàn)在,這套體系就比較完整了。當(dāng)然這只是我的一些構(gòu)想,并沒(méi)有在實(shí)踐中嘗試過(guò),所以肯定會(huì)踩到各種各樣的坑,不過(guò)至少看起來(lái)是個(gè)可行的方案。

責(zé)任編輯:chenqingxiang 來(lái)源: Limboy
相關(guān)推薦

2017-01-10 13:33:51

iOS編程throttle

2021-07-27 10:52:27

iOS WKWebView容器

2017-05-15 21:50:54

Linux引號(hào)

2024-02-04 17:03:30

2011-05-19 16:47:50

軟件測(cè)試

2012-05-01 08:06:49

手機(jī)

2010-07-26 11:02:19

Perl模式匹配

2011-09-19 15:40:35

2020-07-29 08:14:59

云計(jì)算云遷移IT

2009-07-29 10:36:04

北電收購(gòu)

2012-01-02 19:30:22

iPad

2011-07-04 15:30:24

Qt 布局 GridLayout

2011-06-30 14:34:17

QT Tablewidge QTableWidg

2015-05-28 14:02:09

JavaJava日志性

2011-08-22 16:42:43

SqliteiPad

2011-12-02 10:32:23

Java

2014-06-06 16:08:17

初志科技

2021-10-19 21:39:51

Unsafe構(gòu)造器內(nèi)存

2020-09-23 09:07:16

特權(quán)賬號(hào)管理PAM網(wǎng)絡(luò)安全

2012-05-31 09:53:38

IT風(fēng)云15年
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 精品国产一区二区在线 | 国产人成在线观看 | 国产 日韩 欧美 中文 在线播放 | 99视频在线播放 | 国产精品久久久久久久久久免费看 | 天天干天天干 | 在线播放国产视频 | 精品视频一区二区 | 欧美一级精品片在线看 | 91视频精选 | 狠狠躁天天躁夜夜躁婷婷老牛影视 | 古装人性做爰av网站 | 一级黄色录像毛片 | 欧美涩| av中文在线播放 | 激情a| 91视在线国内在线播放酒店 | 国产最新视频在线 | 欧美精品免费观看二区 | 在线观看国产视频 | 麻豆毛片| 天天操夜夜操 | 日韩精品区 | 精品麻豆剧传媒av国产九九九 | 国产人成在线观看 | 日韩一区二区三区在线播放 | 毛片一级黄色 | 日本成人福利 | 艹逼网 | 日韩综合在线 | 综合色久 | 国产精品视频一区二区三区不卡 | 久久大陆| 老外几下就让我高潮了 | 亚洲在线 | 国产精品亚洲第一区在线暖暖韩国 | 无码日韩精品一区二区免费 | 成人欧美一区二区三区在线播放 | a级毛片免费高清视频 | 日韩精品一区二区三区视频播放 | 久久久久亚洲 |