iOS AOP简单实现日志打点[Aspects]

前言:本文简述简单使用Aspects实现自动日志打点,仅是简单使用,深层次需要大神来深究

一、知名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`, 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`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (id)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)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 aspect = ...;
[aspect remove];

[转载]大致原理:替换原方法的IMP消息转发函数指针 _objc_msgForward_objc_msgForward_stret,把原方法IMP添加并对应到SEL aspects_originalSelector,将forwardInvocation:IMP替换为参数对齐的C函数__ASPECTS_ARE_BEING_CALLED__(NSObject *self, SEL selector, NSInvocation *invocation)的指针。在__ASPECTS_ARE_BEING_CALLED__函数中,替换invocationselectoraspects_originalSelector,相当于要发送调用原始方法实现的消息。对于插入位置在前面,替换,后面的多个block,构建新的blockInvocation,从invocation中提取参数,最后通过invokeWithTarget:block来完成依次调用。有关消息转发的介绍,不多赘述。持hook类的单个实例对象的方法(类似于KVO的isa-swizzlling)。不适合对每秒钟超过1000次的方法增加切面代码。此外,使用其他方式对Aspect hook过的方法进行hook时,如直接替换为新的IMP,新hook得到的原始实现是_objc_msgForward,之前的aspect_hook会失效,新的hook也将执行异常。

二、Aspects简单使用

1.Pods导入

target 'xiaobaitu' do
    pod 'Aspects'
end

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

对UIViewController开始hook viewDidAppear方法

Logging.h
#import 

NS_ASSUME_NONNULL_BEGIN

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

NS_ASSUME_NONNULL_END
Logging.m
#import "Logging.h"
#import 
#import 
@implementation Logging
+ (void)setupWithConfiguration{
    // Hook Page Impression
    static NSString *className = @"123";
    [UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id 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) {
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                        NSLog(@"------[%@]:%@",event[@"SelectorName"],event[@"EventName"]);
                    });
                }error:NULL];
            }
        }
    }
}
@end

3. 新建配置文件Config.plist

在配置文件中注册相关控制器及相关方法,plist格式和相关方法名称自定义,Logging文件中对应解析即可


iOS AOP简单实现日志打点[Aspects]_第1张图片
Config.plist

Config.plist 简单示例


        Controller
        UserViewController
        Events
        
            
                EventName
                退出登录
                SelectorName
                logOut
            
            
                EventName
                版本更新
                SelectorName
                updateVersion
            
            
                EventName
                意见反馈
                SelectorName
                recommond
            
        
    

4.注册Logging

AppDelegate.swift中注册Logging

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

三、实现效果

  • 1.可以输出所有加载过的Controller
  • 2.可以根据配置文件注册的方法,点击可打印出方法 or 操作

控制台输出如下:

===========TabViewController
===========OneViewController
===========TwoViewController
===========UserViewController
------[updateVersion]:版本更新
------[recommond]:意见反馈
------[logOut]:退出登录

Aspects注意
1.Aspects无法hook类中不存在的方法,或者未实现的方法。
2.Aspects不支持hook类的类方法
3.Aspects对多个类继承自同一基类需要单独处理hook

【备注】
本文仅简单使用Aspects实现简单日志打点功能,如有错误请指出,相关原理和更多操作请参考大神blog

你可能感兴趣的:(iOS AOP简单实现日志打点[Aspects])