三方解析iOS接下来要研究的知识点

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

2019-03-12  本文已影响73人  梦蕊dream

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

三、实现效果

控制台输出如下:

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

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

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

上一篇 下一篇

猜你喜欢

热点阅读