Preface
AOP (Aspect-oriented programming) translated as "Aspect Oriented Programming", by way of pre-compiled and run-time dynamic agent technology to implement a unified program functions maintenance. AOP can use to isolate each part of the business logic such that the business logic to reduce the degree of coupling between the parts, improve the reusability of the program, while improving efficiency of development.
AOP is currently a more popular topic, even though you may not have heard it, but your project may have infiltrated it, for example: user statistics (do not add a line of code that is to realize the tracking log for all the ViewController).
Aspects as the Objective-C language AOP libraries for iOS and Mac OS X, using the simple pleasant experience, has won the 7k + Star at GitHub. Internal Aspects achieved relatively sound, taking into account the security problems that may occur Hook, very worthy of our study.
Note: this article Aspects reference source version v1.4.1, asking readers to have some knowledge Runtime.
index
- About AOP
- Aspects Introduction
- Aspects structure analysis
- Aspects of the core code analysis
- AOP library should have excellent qualities
- to sum up
About a AOP
At run time, the code to dynamically cut into the specified class methods, programming ideas specified position on the cut surface is oriented programming.
AOP (Aspect-oriented programming), namely "Aspect Oriented Programming" is a programming paradigm, or a kind of programming ideas, it solves the OOP (Object-oriented programming) extension of the problem.
When do you need to use AOP
Generally require the user to do when a page Statistics
Idea: Hook UIViewController's viewWillAppear: and viewWillDisappear: method, after the implementation of the original method of recording statistical information required to be reported.
Note: Simple to by Method Swizzling Hook is not impossible, but there are many security risks!
About two Aspects
Aspects is a simple and pleasant to use AOP library, written using Objective-C for iOS and Mac OS X.
Internal Aspects achieved taking into account many of the problems that may arise Hook, author of the source code in the process of looking to pull in relatively fine, really benefit.
Aspects easy to use, the authors provide examples of Hook and Class achieved by two interfaces exposed in NSObject (Aspects) category were:
@interface NSObject (Aspects)
+ (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
- (id)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
@end
Examples of supports Aspects Hook, compared to other Objective-C AOP library size smaller operable for more diverse scenes. As the user does not need to carry out more operations specified or designated SEL Class instance of Hook, AspectOptions Hook parameter points can be specified, and whether it is withdrawn after a Hook.
2.1 Aspects structure analysis
Although Aspects of less than a thousand lines of source code, but its internal implementation takes into account many Hook safety issues and other details related to the Objective-C AOP compared to other open source projects Aspects more robust, so in my own expense Aspects source also see more carefully.
2.2 Aspects internal structure
- Internal Aspects defines two protocols:
AspectToken
: Hook for cancellation
AspectInfo
: Block embedded in the first parameter Hook
- Internal Aspects also defines four categories:
AspectInfo
: Information section, follow AspectInfo agreement
AspectIdentifier
: section ID, should follow AspectToken protocol (author left out, has been put PR)
AspectsContainer
: cut container
AspectTracker
: section tracker
- A structure
AspectBlockRef
: That _AspectBlock, acts as an internal Block
4. Two internal static global variables:
static NSMutableDictionary *swizzledClassesDict;
static NSMutableSet *swizzledClasses;
2.3 Detailed knowledge points
2.3.1 AspectToken
AspectToken protocol designed to allow users the flexibility to add write-off had previously Hook, internal compliance with the provisions of this Agreement shall be subject implement the remove method.
/// 不透明的 Aspect Token,用于注销 Hook
@protocol AspectToken /// 注销一个 aspect.
/// 返回 YES 表示注销成功,否则返回 NO
- (BOOL)remove;
@end
2.3.2 AspectInfo
AspectInfo agreement designed to regulate an aspect of that aspect of the flaws Hook inside information, we add the first section of Block parameter in Hook when you comply with this agreement.
/// AspectInfo 协议是我们块语法的第一个参数。
@protocol AspectInfo /// 当前被 Hook 的实例
- (id)instance;
/// 被 Hook 方法的原始 invocation
- (NSInvocation *)originalInvocation;
/// 所有方法参数(装箱之后的)惰性执行
- (NSArray *)arguments;
@end
Note: Packing is an expensive operation cost, so used to go execute.
2.3.3 AspectInfo
Note: AspectInfo here is a Class, compliance AspectInfo agreement mentioned above, not to be confused.
AspectInfo class definition:
@interface AspectInfo : NSObject - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
@property (nonatomic, unsafe_unretained, readonly) id instance;
@property (nonatomic, strong, readonly) NSArray *arguments;
@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
@end
Note: About packing for a NSInvocation can get their arguments on this point, ReactiveCocoa team provides a significant contribution (see details inside Aspects NSInvocation classification).
AspectInfo relatively simple, NSInvocation general method parameter reference ReactiveCocoa team can be boxed parameters as NSValue, simply AspectInfo played a role in providing information Hook.
2.3.4 AspectIdentifier
AspectIdentifier class definition:
@interface AspectIdentifier : NSObject
+ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
- (BOOL)invokeWithInfo:(id)info;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, strong) id block;
@property (nonatomic, strong) NSMethodSignature *blockSignature;
@property (nonatomic, weak) id object;
@property (nonatomic, assign) AspectOptions options;
@end
Note: AspectIdentifier actually adding section of the first parameter Block, which should follow AspectToken agreement, in fact is the case, it provides a method of realization remove.
Internal AspectIdentifier should be noted that due to the use Block to write in our Hook added material, generated blockSignature here, in the initialization process AspectIdentifier will go to the Senate to determine compatibility with the object of blockSignature selector resulting methodSignature, compatibility judge the success will successfully initialize.
2.3.5 AspectsContainer
AspectsContainer class definition:
@interface AspectsContainer : NSObject
- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
- (BOOL)removeAspect:(id)aspect;
- (BOOL)hasAspects;
@property (atomic, copy) NSArray *beforeAspects;
@property (atomic, copy) NSArray *insteadAspects;
@property (atomic, copy) NSArray *afterAspects;
@end
As the containers AspectsContainer section, associated with the specified object designation method, internal method specified section of the specified object three queues, each corresponding to the associated receiving AspectOption the Hook:
- NSArray *beforeAspects; - AspectPositionBefore
- NSArray *insteadAspects; - AspectPositionInstead
- NSArray *afterAspects; - AspectPositionAfter
Why say it related? Because AspectsContainer by AssociatedObject method associated with the current target to Hook in NSObject category.
Note: Selector is associated with the target after the Hook, i.e. aliasSelector (SEL original name with a prefix corresponding aspects_ SEL).
2.3.6 AspectTracker
AspectTracker class definition:
@interface AspectTracker : NSObject
- (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent;
@property (nonatomic, strong) Class trackedClass;
@property (nonatomic, strong) NSMutableSet *selectorNames;
@property (nonatomic, weak) AspectTracker *parentEntry;
@end
AspectTracker as section tracker, the principle is as follows:
// Add the selector as being modified.
currentClass = klass;
AspectTracker *parentTracker = nil;
do {
AspectTracker *tracker = swizzledClassesDict[currentClass];
if (!tracker) {
tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
swizzledClassesDict[(id)currentClass] = tracker;
}
[tracker.selectorNames addObject:selectorName];
// All superclasses get marked as having a subclass that is modified.
parentTracker = tracker;
}while ((currentClass = class_getSuperclass(currentClass)));
Note: the value of the global variable corresponding to swizzledClassesDict AspectTracker pointer.
That is AspectTracker and the upper track from the lower, the bottom parentEntry is nil, parentEntry parent class is a subclass of the tracker.
2.3.7 AspectBlockRef
AspectBlockRef, i.e. struct _AspectBlock, defined as follows:
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
Note: __unused macro definition is actually attribute ((unused)) GCC attributive, designed to tell the compiler "If I did not use to this variable in the back and do not warn me."
2.3.8 Aspects static global variables
2.3.8.1 static NSMutableDictionary *swizzledClassesDict;
static NSMutableDictionary * swizzledClassesDict; plays a role has been mixed in Aspects of writing in class dictionary
Internal Aspects provides a method to access the global specialized dictionary:
static NSMutableDictionary *aspect_getSwizzledClassesDict() {
static NSMutableDictionary *swizzledClassesDict;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
swizzledClassesDict = [NSMutableDictionary new];
});
return swizzledClassesDict;
}
This global variable can be understood as a simple recording of the entire Hook Class affect global dictionary contains its track record of SuperClass.
2.3.8.2 static NSMutableSet *swizzledClasses;
static NSMutableSet * swizzledClasses; Aspects play in the role written record has been mixed class
Internal Aspects provides a method for modifying the contents of global variables:
static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
static NSMutableSet *swizzledClasses;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
swizzledClasses = [NSMutableSet new];
});
@synchronized(swizzledClasses) {
block(swizzledClasses);
}
}
Note: 注意 @synchronized(swizzledClasses)。
The global variables recorded forwardInvocation: class names are written in the mix.
Note: Note the use of the static NSMutableDictionary * swizzledClassesDict; distinguish understood.
Three Aspects core code analysis
Aspects of the overall implementation code is not more than a thousand lines, and the situation is considered relatively comprehensive, well worth taking the time to read what we are given to understand part of the core code here.
3.1 Hook Class && Hook Instance
Aspects not only support Hook Class also supports Hook Instance, which provides a smaller particle size control, with Hook's Undo function can be more flexible and precise to do what we want to do -
Aspects to be able to distinguish logical Class and Instance, to achieve a method called aspect_hookClass, I think I deserve to achieve them part of a separate space to explain, readers also feel the need to take the time to understand the logic here is realized.
static Class aspect_hookClass(NSObject *self, NSError **error) {
// 断言 self
NSCParameterAssert(self);
// class
Class statedClass = self.class;
// isa
Class baseClass = object_getClass(self);
NSString *className = NSStringFromClass(baseClass);
// 已经子类化过了
if ([className hasSuffix:AspectsSubclassSuffix]) {
return baseClass;
// 我们混写了一个 class 对象,而非一个单独的 object
}else if (class_isMetaClass(baseClass)) {
// baseClass 是元类,则 self 是 Class 或 MetaClass,混写 self
return aspect_swizzleClassInPlace((Class)self);
// 可能是一个 KVO'ed class。混写就位。也要混写 meta classes。
}else if (statedClass != baseClass) {
// 当 .class 和 isa 指向不同的情况,混写 baseClass
return aspect_swizzleClassInPlace(baseClass);
}
// 默认情况下,动态创建子类
// 拼接子类后缀 AspectsSubclassSuffix
const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
// 尝试用拼接后缀的名称获取 isa
Class subclass = objc_getClass(subclassName);
// 找不到 isa,代表还没有动态创建过这个子类
if (subclass == nil) {
// 创建一个 class pair,baseClass 作为新类的 superClass,类名为 subclassName
subclass = objc_allocateClassPair(baseClass, subclassName, 0);
if (subclass == nil) { // 返回 nil,即创建失败
NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
return nil;
}
// 混写 forwardInvocation:
aspect_swizzleForwardInvocation(subclass);
// subClass.class = statedClass
aspect_hookedGetClass(subclass, statedClass);
// subClass.isa.class = statedClass
aspect_hookedGetClass(object_getClass(subclass), statedClass);
// 注册新类
objc_registerClassPair(subclass);
}
// 覆盖 isa
object_setClass(self, subclass);
return subclass;
}
Note: In fact, the difficulty here lies in the distinction between the .class and object_getClass.
- When the target is .class returns Instance Class, when the target is Class return itself
- It returns a pointer to object_getClass isa
Note: Complete the following steps to dynamically create a Class also we should pay attention.
- objc_allocateClassPair
- class_addMethod
- class_addIvar
- objc_registerClassPair
Realization of 3.2 Hook
In the above aspect_hookClass approach, not just to return a Class Hook, during which also made some details of the operation, regardless of Class or Instance, will call aspect_swizzleForwardInvocation method, the following method for this analysis
static void aspect_swizzleForwardInvocation(Class klass) {
// 断言 klass
NSCParameterAssert(klass);
// 如果没有 method,replace 实际上会像是 class_addMethod 一样
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
// 拿到 originalImplementation 证明是 replace 而不是 add,情况少见
if (originalImplementation) {
// 添加 AspectsForwardInvocationSelectorName 的方法,IMP 为原生 forwardInvocation:
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
}
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}
The above method is to target Class to Hook of forwardInvocation: Mixed wrote, forwardInvocation after mixing wrote: concrete realization in ASPECTS_ARE_BEING_CALLED , the inside can see, there are some other how to achieve different invoke flag is an implementation detail :
// 宏定义,以便于我们有一个更明晰的 stack trace
#define aspect_invoke(aspects, info) \
for (AspectIdentifier *aspect in aspects) {\
[aspect invokeWithInfo:info];\
if (aspect.options & AspectOptionAutomaticRemoval) { \
aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
} \
}
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
// __unsafe_unretained NSObject *self 不解释了
// 断言 self, invocation
NSCParameterAssert(self);
NSCParameterAssert(invocation);
// 从 invocation 可以拿到很多东西,比如 originalSelector
SEL originalSelector = invocation.selector;
// originalSelector 加前缀得到 aliasSelector
SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
// 用 aliasSelector 替换 invocation.selector
invocation.selector = aliasSelector;
// Instance 的容器
AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
// Class 的容器
AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
NSArray *aspectsToRemove = nil;
// Before hooks.
aspect_invoke(classContainer.beforeAspects, info);
aspect_invoke(objectContainer.beforeAspects, info);
// Instead hooks.
BOOL respondsToAlias = YES;
if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
// 如果有任何 insteadAspects 就直接替换了
aspect_invoke(classContainer.insteadAspects, info);
aspect_invoke(objectContainer.insteadAspects, info);
}else { // 否则正常执行
// 遍历 invocation.target 及其 superClass 找到实例可以响应 aliasSelector 的点 invoke
Class klass = object_getClass(invocation.target);
do {
if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
[invocation invoke];
break;
}
}while (!respondsToAlias && (klass = class_getSuperclass(klass)));
}
// After hooks.
aspect_invoke(classContainer.afterAspects, info);
aspect_invoke(objectContainer.afterAspects, info);
// 如果没有 hook,则执行原始实现(通常会抛出异常)
if (!respondsToAlias) {
invocation.selector = originalSelector;
SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
// 如果可以响应 originalForwardInvocationSEL,表示之前是 replace method 而非 add method
if ([self respondsToSelector:originalForwardInvocationSEL]) {
((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
}else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
// 移除 aspectsToRemove 队列中的 AspectIdentifier,执行 remove
[aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}
#undef aspect_invoke
Note: aspect_invoke macro definition of scope.
- Corresponding to the code for the Hook
AspectOptions
Before, Instead and After parameters. - aspect_invoke in
aspectsToRemove
a NSArray, which houses Hook cancellation needs to be, i.e.,AspectIdentifier
(after removal remove calls). - Traverse
invocation.target
its examples may be found superClass response aliasSelector dot invoke the implementation code.
3.3 Block Hook
Aspects Let us when specifying a particular Class or Instance Selector execution, insert our own doing Hook Block according to AspectOptions, and this internal Block have what we want information about the current Target and the Selector, Aspects we look at is how to do to
- (BOOL)invokeWithInfo:(id)info {
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
NSInvocation *originalInvocation = info.originalInvocation;
NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
// 偏执。我们已经在 hook 注册的时候检查过了,(不过这里我们还要检查)。
if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
AspectLogError(@"Block has too many arguments. Not calling %@", info);
return NO;
}
// block 的 `self` 将会是 AspectInfo。可选的。
if (numberOfArguments > 1) {
[blockInvocation setArgument:&info atIndex:1];
}
// 简历参数分配内存 argBuf 然后从 originalInvocation 取 argument 赋值给 blockInvocation
void *argBuf = NULL;
for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
NSUInteger argSize;
NSGetSizeAndAlignment(type, &argSize, NULL);
// reallocf 优点,如果创建内存失败会自动释放之前的内存,讲究
if (!(argBuf = reallocf(argBuf, argSize))) {
AspectLogError(@"Failed to allocate memory for block invocation.");
return NO;
}
[originalInvocation getArgument:argBuf atIndex:idx];
[blockInvocation setArgument:argBuf atIndex:idx];
}
// 执行
[blockInvocation invokeWithTarget:self.block];
// 释放 argBuf
if (argBuf != NULL) {
free(argBuf);
}
return YES;
}
Consider two questions:
- [BlockInvocation setArgument: & info atIndex: 1]; why should insert at index 1 it?
- for (NSUInteger idx = 2; idx <numberOfArguments; idx ++) Why 2 from the index began in the argument it?
to sum up
-
This paper briefly introduces the concept of AOP, I hope you readers to help provide meager understanding of AOP thought.
-
The article analyzes the system's internal structure Aspects of open source libraries, we want to make rapid positioning code location when browsing Aspects source, find the core content.
-
Aspects of the article focuses on the core code, refine some point I think is worth noting, I hope you can provide some guidance when everyone source Pa.
This article reference from Aspects source code What have I learned?