iOS-使用AOP统计用户行为(一)

一、AOP简介

AOP(Aspect Oriented Programming,面向方面编程),通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态添加功能的一种技术。

其思想核心是将业务逻辑(核心关注点,系统的主要功能)与公共功能(横切关注点,如日志、事物等)进行分离,降低复杂性,提高软件系统模块化、可维护性和可重用性。其中核心关注点采用OOP方式进行代码的编写,横切关注点采用AOP方式进行编码,最后将这两种代码进行组合形成系统。

二、AOP技术的应用

应用范围:日志记录,性能统计,安全控制,事务处理,异常处理等等。

三、iOS开发中的AOP

在iOS中AOP的实现是基于Objective-C的Runtime机制。

很多第三方的iOS库都是基于Runtime来实现AOP, 即在不改变接口的情况下, 动态地修改实现。

四、示例

以UIButton为例:

#import “UIButton+ButtonRunTime.h”

#import <objc/runtime.h>

@implementation UIButton (ButtonRunTime)

– (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

//获得viewController的生命周期方法的selector

SEL systemSel = @selector(sendAction:to:forEvent:);

//自己实现的将要被交换的方法的selector

SEL swizzSel = @selector(hy_sendAction:to:forEvent:);

//两个方法的Method

Method systemMethod = class_getInstanceMethod([self class], systemSel);

Method swizzMethod = class_getInstanceMethod([self class], swizzSel);

//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败

BOOL isAdd = class_addMethod([self class], systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));

if (isAdd) {

//如果成功,说明类中不存在这个方法的实现

//将被交换方法的实现替换到这个并不存在的实现

class_replaceMethod([self class], swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));

}else{

//否则,交换两个方法的实现

method_exchangeImplementations(systemMethod, swizzMethod);

}

});

[super addTarget:target action:action forControlEvents:controlEvents];

}

– (void)hy_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event

{

UIViewController *vc = (UIViewController *)target;

if ([HYLogger sharedInstance].isRuntimeLogEffective) {

HYLogInfo(@”************UIButton************\nsender       = %@\nselector     = %s\nsender title = %@\n***********************”, [vc class],sel_getName(action),[self titleForState:UIControlStateNormal]);

}

[super sendAction:action to:target forEvent:event];

}

class_addMethod解释:

要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现 originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 orginalSelector ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。

示例打印结果: %e6%89%93%e5%8d%b0%e7%bb%93%e6%9e%9c

五、Runtime术语

SEL

Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。

SEL也是@selector的类型,用来表示OC运行时的方法的名字。来看一下OC中的定义:

Defines an opaque type that represents a method selector.

Declaration

typedef struct objc_selector *SEL;

本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。

我们可以在运行时添加新的selector,也可以在运行时获取已存在的selector。

IMP

IMP实际上是一个函数指针,指向方法实现的首地址,定义如下:A pointer to the start of a method implementation.

Declaration

id (*IMP)(id, SEL, …)

关于IMP的几点说明:

·使用当前CPU架构实现的标准的C调用约定;

·第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针);

·第二个参数是方法选择器(selector);

·从第三个参数开始是方法的实际参数列表;

通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些,当然必须说明的是,这种方式只适用于极特殊的优化场景,如效率敏感的场景下大量循环的调用某方法。

Method

An opaque type that represents a method in a class definition.

Declaration

typedef struct objc_method *Method;

struct objc_method {

SEL method_name                                          OBJC2_UNAVAILABLE;

char *method_types                                       OBJC2_UNAVAILABLE;

IMP method_imp                                           OBJC2_UNAVAILABLE;

}

objc_method 存储了方法名,方法类型和方法实现:

方法名类型为 SEL;

方法类型 method_types是个char指针,存储方法的参数类型和返回值类型;

method_imp 指向了方法的实现,本质是一个函数指针。

Method = SEL + IMP + method_types,相当于在SEL和IMP之间建立了一个映射。

其他术语的数据结构:

id

id 是一个参数类型,它是指向某个类的实例的指针。定义如下:

typedef struct objc_object *id;

struct objc_object { Class isa; };

以上定义,看到 objc_object 结构体包含一个 isa 指针,根据 isa 指针就可以找到对象所属的类。

注意:

isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的 -class 方法。

Cache

Cache 定义如下:

typedef struct objc_cache *Cache

struct objc_cache {

unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;

unsigned int occupied                                    OBJC2_UNAVAILABLE;

Method buckets[1]                                        OBJC2_UNAVAILABLE;

};

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 系统会把被调用的

六、iOS方法调用流程

方法调用的核心是objc_msgSend方法:

Sends a message with a simple return value to an instance of a class.

Declaration

id objc_msgSend(id self, SEL op, …)

Parameters

self

A pointer that points to the instance of the class that is to receive the message.

op

The selector of the method that handles the message.

A variable argument list containing the arguments to the method.

举例:

我们在代码中写这样一行代码:[receiver message];底层运行时会被编译器转化为:objc_msgSend(receiver,selector)。

如果其还有参数比如:[receiver message:(id)arg];底层运行时会被编译器转化为:objc_msgSend(receiver,selector,arg1,arg2,…)。

具体的过程如下:

1、先找到selector 对应的方法实现(IMP),因为同一个方法可能在不同的类中有不同的实现,所以需要receiver的类来找到确切的IMP

◦IMP class_getMethodImplementation(Class class, SEL selector)

◦如同其文档所说:The function pointer returned may be a function internal to the runtime instead of an actual method implementation. For example, if instances of the class do not respond to the selector, the function pointer returned will be part of the runtime’s message forwarding machinery.

◦具体来说,当找不到IMP的时候,方法返回一个 _objc_msgForward 对象,用来标记需要转入消息转发流程,我们现在用的AOP框架也是利用了这个机制来人为的制造找不到IMP的假象来触发消息转发的流程

2、根据查找结果

◦找到了IMP,调用找到的IMP,传入参数

◦没找到IMP,转入消息转发流程

3、将IMP的返回值作为自己的返回值

补充说明一下IMP的查找过程,消息传递的关键在于objc_class结构体中的以下几个东西:

·Class *isa

·Class *super_class

·objc_method_list **methodLists

·objc_cache *cache

当消息发送给一个对象时,objc_msgSend通过对象的isa获取到类的结构体,然后在cache和methodLists中查找,如果没找到就找其父类,以此类推知道找到NSObject类,如果还没找到,就走消息转发流程。

未完待续。。。

分享到: 更多
Separator image Posted in IOS.