iOS AOP簡單實(shí)現(xiàn)日志打點(diǎn)[Aspects]

前言:本文簡述簡單使用Aspects實(shí)現(xiàn)自動日志打點(diǎn),僅是簡單使用,深層次需要大神來深究

一、知名AOP庫 Aspects

https://github.com/steipete/Aspects
一款比較成熟的面向切面庫

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// Deregister an aspect.
/// @return YES if deregistration is successful, otherwise NO.
id<AspectToken> aspect = ...;
[aspect remove];

[轉(zhuǎn)載]大致原理:替換原方法的IMP消息轉(zhuǎn)發(fā)函數(shù)指針 _objc_msgForward_objc_msgForward_stret,把原方法IMP添加并對應(yīng)到SEL aspects_originalSelector,將forwardInvocation:IMP替換為參數(shù)對齊的C函數(shù)__ASPECTS_ARE_BEING_CALLED__(NSObject *self, SEL selector, NSInvocation *invocation)的指針。在__ASPECTS_ARE_BEING_CALLED__函數(shù)中,替換invocationselectoraspects_originalSelector,相當(dāng)于要發(fā)送調(diào)用原始方法實(shí)現(xiàn)的消息。對于插入位置在前面,替換,后面的多個(gè)block,構(gòu)建新的blockInvocation,從invocation中提取參數(shù),最后通過invokeWithTarget:block來完成依次調(diào)用。有關(guān)消息轉(zhuǎn)發(fā)的介紹,不多贅述。持hook類的單個(gè)實(shí)例對象的方法(類似于KVO的isa-swizzlling)。不適合對每秒鐘超過1000次的方法增加切面代碼。此外,使用其他方式對Aspect hook過的方法進(jìn)行hook時(shí),如直接替換為新的IMP,新hook得到的原始實(shí)現(xiàn)是_objc_msgForward,之前的aspect_hook會失效,新的hook也將執(zhí)行異常。

二、Aspects簡單使用

1.Pods導(dǎo)入

target 'xiaobaitu' do
    pod 'Aspects'
end

2.新建NSObject用于Aspects配置【demo】

對UIViewController開始hook viewDidAppear方法

Logging.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Logging : NSObject
+ (void)setupWithConfiguration;
@end

NS_ASSUME_NONNULL_END
Logging.m
#import "Logging.h"
#import <UIKit/UIKit.h>
#import <Aspects/Aspects.h>
@implementation Logging
+ (void)setupWithConfiguration{
    // Hook Page Impression
    static NSString *className = @"123";
    [UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
        className = NSStringFromClass([[aspectInfo instance] class]);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            className = NSStringFromClass([[aspectInfo instance] class]);
            NSLog(@"===========%@",className);
        });
    } error:NULL];
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Config" ofType:@"plist"];
    NSMutableArray *configs = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
    
    
    for (NSDictionary *classItem in configs){
        Class class = NSClassFromString(classItem[@"Controller"]);
        if (classItem[@"Events"]){
            for (NSDictionary *event in classItem[@"Events"]) {
                SEL selector = NSSelectorFromString(event[@"SelectorName"]);
                
                [class aspect_hookSelector:selector withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                        NSLog(@"------[%@]:%@",event[@"SelectorName"],event[@"EventName"]);
                    });
                }error:NULL];
            }
        }
    }
}
@end

3. 新建配置文件Config.plist

在配置文件中注冊相關(guān)控制器及相關(guān)方法,plist格式和相關(guān)方法名稱自定義,Logging文件中對應(yīng)解析即可


Config.plist

Config.plist 簡單示例

<dict>
        <key>Controller</key>
        <string>UserViewController</string>
        <key>Events</key>
        <array>
            <dict>
                <key>EventName</key>
                <string>退出登錄</string>
                <key>SelectorName</key>
                <string>logOut</string>
            </dict>
            <dict>
                <key>EventName</key>
                <string>版本更新</string>
                <key>SelectorName</key>
                <string>updateVersion</string>
            </dict>
            <dict>
                <key>EventName</key>
                <string>意見反饋</string>
                <key>SelectorName</key>
                <string>recommond</string>
            </dict>
        </array>
    </dict>

4.注冊Logging

AppDelegate.swift中注冊Logging

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    Logging.setupWithConfiguration()
    return true
}

三、實(shí)現(xiàn)效果

  • 1.可以輸出所有加載過的Controller
  • 2.可以根據(jù)配置文件注冊的方法,點(diǎn)擊可打印出方法 or 操作

控制臺輸出如下:

===========TabViewController
===========OneViewController
===========TwoViewController
===========UserViewController
------[updateVersion]:版本更新
------[recommond]:意見反饋
------[logOut]:退出登錄

Aspects注意
1.Aspects無法hook類中不存在的方法,或者未實(shí)現(xiàn)的方法。
2.Aspects不支持hook類的類方法
3.Aspects對多個(gè)類繼承自同一基類需要單獨(dú)處理hook

【備注】
本文僅簡單使用Aspects實(shí)現(xiàn)簡單日志打點(diǎn)功能,如有錯(cuò)誤請指出,相關(guān)原理和更多操作請參考大神blog

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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