面向切面編程

Aspect Oriented Programming (AOP,面向切面編程) 在 Objective-C 社區(qū)內(nèi)沒有那么有名誊册,但是 AOP 在運(yùn)行時可以有巨大威力宰僧。 但是因?yàn)闆]有事實(shí)上的標(biāo)準(zhǔn)笋敞,Apple 也沒有開箱即用的提供,也顯得不重要挪凑,開發(fā)者都不怎么考慮它孕索。

引用 Aspect Oriented Programming 維基頁面:
An aspect can alter the behavior of the base code (the non-aspect part of a program) by applying advice (additional behavior) at various join points (points in a program) specified in a quantification or query called a pointcut (that detects whether a given join point matches). (一個切面可以通過在多個 join points 中附加的行為來改變基礎(chǔ)代碼的行為(程序的非切面的部分) )
在 Objective-C 的世界里,這意味著使用運(yùn)行時的特性來為指定的方法追加 切面 躏碳。切面所附加的行為可以是這樣的:

  • 在類的特定方法調(diào)用前運(yùn)行特定的代碼
  • 在類的特定方法調(diào)用后運(yùn)行特定的代碼
  • 增加代碼來替代原來的類的方法的實(shí)現(xiàn)

有很多方法可以達(dá)成這些目的搞旭,但是我們沒有深入挖掘,不過它們主要都是利用了運(yùn)行時。 Peter Steinberger 寫了一個庫肄渗,Aspects 完美地適配了 AOP 的思路镇眷。我們發(fā)現(xiàn)它值得信賴以及設(shè)計得非常優(yōu)秀,所以我們就在這邊作為一個簡單的例子翎嫡。

對于所有的 AOP庫欠动,這個庫用運(yùn)行時做了一些非常酷的魔法惑申,可以替換或者增加一些方法(比 method swizzling 技術(shù)更有技巧性)

Aspect 的 API 有趣并且非常強(qiáng)大:

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

比如具伍,下面的代碼會對于執(zhí)行 MyClass 類的 myMethod: (實(shí)例或者類的方法) 執(zhí)行塊參數(shù)。

[MyClass aspect_hookSelector:@selector(myMethod:)
                 withOptions:AspectPositionAfter
                  usingBlock:^(id<AspectInfo> aspectInfo) {
            ...
        }
                       error:nil];

換一句話說:任意的 MyClass 類型的對象(或者是類型本身當(dāng)這個 @selector 方法為類方法時)的 @selector 方法執(zhí)行完后硝桩,就會執(zhí)行這個代碼中塊參數(shù)所提供的代碼沿猜。

我們?yōu)?MyClass 類的 myMethod: 方法增加了切面。

通常 AOP 被用來實(shí)現(xiàn)橫向切面碗脊。統(tǒng)計與日志就是一個完美的例子啼肩。

下面的例子里面,我們會用AOP用來進(jìn)行統(tǒng)計衙伶。統(tǒng)計是iOS項(xiàng)目里面一個熱門的特性祈坠,有很多選擇比如 Google Analytics, Flurry, MixPanel, 等等.

大部分統(tǒng)計框架都有教程來指導(dǎo)如何追蹤特定的界面和事件,包括在每一個類里寫幾行代碼。

在 Ray Wenderlich 的博客里有 文章 和一些示例代碼炮捧,通過在你的 view controller 里面加入 Google Analytics 進(jìn)行統(tǒng)計忽冻。

- (void)logButtonPress:(UIButton *)button {
    id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
    [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"UX"
                                                          action:@"touch"
                                                           label:[button.titleLabel text]
                                                           value:nil] build]];
}

上面的代碼在按鈕點(diǎn)擊的時候發(fā)送了特定的上下文事件。但是當(dāng)你想追蹤屏幕的時候會變得很糟躺同。

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker];
    [tracker set:kGAIScreenName value:@"Stopwatch"];
    [tracker send:[[GAIDictionaryBuilder createAppView] build]];
}

對于大部分有經(jīng)驗(yàn)的iOS工程師,這看起來不是很好的代碼丸逸。我們讓 view controller 變得更糟糕了蹋艺。因?yàn)槲覀兗尤肓私y(tǒng)計事件的代碼,但是它不是 view controller 的職能黄刚。你可以反駁捎谨,因?yàn)槟阃ǔS刑囟ǖ膶ο髞碡?fù)責(zé)統(tǒng)計追蹤,并且你將代碼注入了 view controller 憔维,但是無論你隱藏邏輯涛救,問題仍然存在 :你最后還是在viewDidAppear: 后插入了代碼。

我們可以在類的 viewDidAppear: 方法上使用 AOP 來追蹤屏幕业扒,并且我們可以使用同樣的方法在其他我們感興趣的方法上添加事件追蹤检吆。比如當(dāng)用戶點(diǎn)擊某個按鈕時(比如:一般調(diào)用對應(yīng)的 IBAction).

方法很簡潔且不具侵入性:

  • view controller 不會被不屬于它的代碼污染
  • 為所有加入到我們代碼的切面指定一個 SPOC 文件 (single point of customization)提供了可能
  • SPOC 應(yīng)該在 App 剛開始啟動的時候用來添加切面
  • 如果SPOC文件異常,至少有一個 selector 或者 類 識別不出來,應(yīng)用將會在啟動時崩潰(對我們來說這很酷).
  • 公司負(fù)責(zé)統(tǒng)計的團(tuán)隊通常會提供統(tǒng)計文檔程储,羅列出需要追蹤的事件咧栗。這個文檔可以很容易映射到一個 SPOC 文件逆甜。
  • 追蹤邏輯抽象化之后,擴(kuò)展到很多其他統(tǒng)計框架會很方便
  • 對于屏幕視圖致板,對于需要定義 selector 的方法交煞,只需要在 SPOC 文件修改相關(guān)的類(相關(guān)的切面會加入到 viewDidAppear: 方法)。如果要同時發(fā)送屏幕視圖和事件斟或,需要(依靠統(tǒng)計提供方)提供一個追蹤的標(biāo)示或者可能還需要提供其他的元信息素征。

我們可能希望一個 SPOC 文件類似下面的(同樣的一個 .plist 文件會適配)

NSDictionary *analyticsConfiguration()
{
    return @{
        @"trackedScreens" : @[
            @{
                @"class" : @"ZOCMainViewController",
                @"label" : @"Main screen"
                }
             ],
        @"trackedEvents" : @[
            @{
                @"class" : @"ZOCMainViewController",
                @"selector" : @"loginViewFetchedUserInfo:user:",
                @"label" : @"Login with Facebook"
                },
            @{
                @"class" : @"ZOCMainViewController",
                @"selector" : @"loginViewShowingLoggedOutUser:",
                @"label" : @"Logout with Facebook"
                },
            @{
                @"class" : @"ZOCMainViewController",
                @"selector" : @"loginView:handleError:",
                @"label" : @"Login error with Facebook"
                },
            @{
                @"class" : @"ZOCMainViewController",
                @"selector" : @"shareButtonPressed:",
                @"label" : @"Share button"
                }
             ]
    };
}

提及的架構(gòu)托管 在 Github 的EF Education First 中.

- (void)setupWithConfiguration:(NSDictionary *)configuration
{
    // screen views tracking
    for (NSDictionary *trackedScreen in configuration[@"trackedScreens"]) {
        Class clazz = NSClassFromString(trackedScreen[@"class"]);

        [clazz aspect_hookSelector:@selector(viewDidAppear:)
                       withOptions:AspectPositionAfter
                        usingBlock:^(id<AspectInfo> aspectInfo) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
            ^{
                NSString *viewName = trackedScreen[@"label"];
                [tracker trackScreenHitWithName:viewName];
                });
            }
            error:nil];
        }];

    }

    // events tracking
    for (NSDictionary *trackedEvents in configuration[@"trackedEvents"]) {
        Class clazz = NSClassFromString(trackedEvents[@"class"]);
        SEL selektor = NSSelectorFromString(trackedEvents[@"selector"]);

        [clazz aspect_hookSelector:selektor
                       withOptions:AspectPositionAfter
                        usingBlock:^(id<AspectInfo> aspectInfo) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
                    ^{
                        UserActivityButtonPressedEvent *buttonPressEvent = \
                        [UserActivityButtonPressedEvent \
                                eventWithLabel:trackedEvents[@"label"]];
                        [tracker trackEvent:buttonPressEvent];
                    });
                }
            error:nil];
        }];

    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市萝挤,隨后出現(xiàn)的幾起案子御毅,更是在濱河造成了極大的恐慌,老刑警劉巖怜珍,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件端蛆,死亡現(xiàn)場離奇詭異,居然都是意外死亡酥泛,警方通過查閱死者的電腦和手機(jī)今豆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柔袁,“玉大人呆躲,你說我怎么就攤上這事〈匪鳎” “怎么了插掂?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腥例。 經(jīng)常有香客問我辅甥,道長,這世上最難降的妖魔是什么燎竖? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任肆氓,我火速辦了婚禮,結(jié)果婚禮上底瓣,老公的妹妹穿的比我還像新娘。我一直安慰自己蕉陋,他們只是感情好捐凭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凳鬓,像睡著了一般茁肠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上缩举,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天垦梆,我揣著相機(jī)與錄音匹颤,去河邊找鬼。 笑死托猩,一個胖子當(dāng)著我的面吹牛印蓖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播京腥,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼赦肃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了公浪?” 一聲冷哼從身側(cè)響起他宛,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎欠气,沒想到半個月后厅各,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡预柒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年队塘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卫旱。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡人灼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顾翼,到底是詐尸還是另有隱情投放,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布适贸,位于F島的核電站灸芳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拜姿。R本人自食惡果不足惜烙样,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蕊肥。 院中可真熱鬧谒获,春花似錦、人聲如沸壁却。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽展东。三九已至赔硫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盐肃,已是汗流浹背爪膊。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工权悟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人推盛。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓峦阁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親小槐。 傳聞我的和親對象是個殘疾皇子拇派,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容