iOS - Learn Aspects source

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

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
  1. Internal Aspects defines two protocols:

AspectToken: Hook for cancellation
AspectInfo: Block embedded in the first parameter Hook

  1. 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

  1. 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 AspectOptionsBefore, Instead and After parameters.
  • aspect_invoke in aspectsToRemovea NSArray, which houses Hook cancellation needs to be, i.e., AspectIdentifier(after removal remove calls).
  • Traverse invocation.targetits 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?


Recommended Papers

* Vibrato effect to achieve

* Audio and video learning from zero to full

* BAT- latest iOS face questions summary

Guess you like

Origin blog.csdn.net/qq_42792413/article/details/95336097