Contents

NSProxy AOP

iOS AOP文章系列

前导知识:

AOP框架:

介绍

iOS 有一个原生的 AOP 方法,就是利用 NSProxy 代理类!,我们先看下官网介绍:

Quote

Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.

NSProxy implements the basic methods required of a root class, including those defined in the NSObjectProtocol protocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override the forwardInvocation(_:) and methodSignatureForSelector: methods to handle messages that it doesn’t implement itself.

说明两点:

  1. NSProxy 本身就是用来做代理转发消息的
  2. NSProxy 的子类必须通过 forwardInvocationmethodSignatureForselector 来创建方法
Note
还记得我们在方法查找和消息转发文章中最后讲的慢速消息转发过程吗?

NSProxy 的声明:

NS_ROOT_CLASS
@interface NSProxy <NSObject> {
    __ptrauth_objc_isa_pointer Class    isa;
}

+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;

- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);

// - (id)forwardingTargetForSelector:(SEL)aSelector;

@end

包含 isa 指针,说明其本身就是一个对象。并且遵守 NSObjcet 协议,还存在 forwardInvocationmethodSignatureForSelector 方法。

🌰 🌰 在目

新增一个 MyProxy 类继承 NSProxy,并在其中实现一个目标对象的代理引用,同时实现两个关键方法:

// MyProxy.h
@interface MyProxy : NSProxy {
    id _target;
}

+ (id)proxyWithTarget:(id)target;

@end

// MyProxy.m
@implementation MyProxy

+ (id)proxyWithTarget:(id)target {
    MyProxy *mp = [MyProxy alloc];
    mp->_target = target;
    return mp;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    if (invocation && [_target respondsToSelector:invocation.selector]) {
        NSString *sn = NSStringFromSelector(invocation.selector);
        NSLog(@"do something before %@", sn);
        [invocation invokeWithTarget:_target];
        NSLog(@"do something after %@", sn);
    }
}

@end

// main.m 中添加代码
//        ==== NSProxy ====
Person *p = [MyProxy proxyWithTarget:[[Person alloc] init]];
[p sleep];
[p walk];

原理

proxyWithTarget: 实现将一个对象引用到自实现的代理类中,在真正调用对象方法时,代理类没有方法定义,找不到对应的 IMP,就会进入消息转发流程,从而执行 forwardInvocation: ,而我们在 forwardInvocation: 中执行原方法前后加入额外逻辑实现 AOP

这里不仅可以打印方法名和修改方法逻辑,因为 NSInvocation 中封装了方法的全部信息,因此还能够打印或修改入参返回值