美文网首页三方解析iOS接下来要研究的知识点
iOS AOP简单实现日志打点[Aspects]

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

作者: 梦蕊dream | 来源:发表于2019-03-12 17:33 被阅读73次

    前言:本文简述简单使用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<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];
    

    [转载]大致原理:替换原方法的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 <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

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


    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
    }
    

    三、实现效果

    • 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]

        本文链接:https://www.haomeiwen.com/subject/uqvnpqtx.html