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<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__
函数中,替换invocation
的selector
为aspects_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文件中对应解析即可
![](https://img.haomeiwen.com/i1915113/9dcd813e9ad556bd.png)
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