NSObject 与 NSProxy

NSProxy是和NSObject并列的Root Type

明确这一点很重要,我们习惯性地认为所有的OC对象都是NSObject的基类,然而还有NSProxy.从字面意思上来讲,Proxy意为代理人,委托.从命名上也可以窥见,NSProxy一般作为NSObject的补充.
NSObject很了解了,那么NSProxy到底是什么样的定位呢,翻下苹果官方的解释.

An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet

一个抽象超类,用于定义一个对象,该对象旨在充当其他对象或不存在对象的替身.
看下NSProxy的头文件

#import <Foundation/NSObject.h>

@class NSMethodSignature, NSInvocation;

NS_ASSUME_NONNULL_BEGIN

NS_ROOT_CLASS
@interface NSProxy <NSObject> {
    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 NS_UNAVAILABLE;
- (BOOL)retainWeakReference NS_UNAVAILABLE;

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

@end

NS_ASSUME_NONNULL_END
  • Class定义中只有一个isa指针,所以可以用作任意对象的代理.
  • 没有init方法,避免当作NSOjbect
  • 实现了-forwardInvocation:-methodSignatureForSelector:这两个方法,暗示很明显了,就是为了让你处理转发消息

重温Objective-C中的消息转发机制

多啰嗦几句
看一下runtime中是如何在C中定义结构体Class的

typedef struct objc_class *Class;
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

oc通过objc_msgSend来进行消息的运行时转发,向上遍历整个继承链寻找方法的selector,如果一直找到NSOjbect都没有找到,就会进行消息转发,依次通过寻找resolveInstanceMethod,forwardingTargetForSelector, forwardInvocation来尝试对消息进行处理,最后找不到抛出异常.见下图

所以,如果我们

使用NSProxy来实现多继承

  • 自定义一个包含两个object的NSProxy类
@interface TargetProxy : NSProxy {
    id realObject1;
    id realObject2;
}
- (id)initWithTarget1:(id)t1 target2:(id)t2;
@end
@implementation TargetProxy
 
- (id)initWithTarget1:(id)t1 target2:(id)t2 {
    realObject1 = t1 ;
    realObject2 = t2 ;
    return self;
}
  • 所以使用initWithTarget1:target2:方法创建的NSProxy对象,通过消息转发,可以当作继承了targe1和target2对象的一个对象,实现多继承的模拟.
NSMutableString *string = [[NSMutableString alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
id proxy = [[TargetProxy alloc] initWithTarget1:string target2:array];
[proxy appendString:@"This "];
[proxy addObject:string];
  • 关键就在于两个消息转发相关的方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig;
    sig = [realObject1 methodSignatureForSelector:aSelector];
    if (sig) return sig;
    sig = [realObject2 methodSignatureForSelector:aSelector];
    return sig;
}
 
// Invoke the invocation on whichever real object had a signature for it.
- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [realObject1 methodSignatureForSelector:[invocation selector]] ? realObject1 : realObject2;
    [invocation invokeWithTarget:target];
}

使用NSProxy来实现AOP面向切面编程

在上一步模拟多重继承时,在forwardInvocation:方法中,可以对特殊的方法进行AOP操作

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [realObject1 methodSignatureForSelector:[invocation selector]] ? realObject1 : realObject2;
    
    NSLog(@"Before calling: %@",target);
    [invocation invokeWithTarget:target];
    NSLog(@"After calling: %@",target);
}

使用NSProxy来避免一些特殊情况的内存泄漏

将上面的自定义NSProxy改一下,改为内置一个Object,并且标为__Weak,这样,在一些强引用环成立的情况下,使用自定义的NSProxy持有关键对象,就可以实现引用环的解套,如下

@interface TargetProxy : NSProxy {
    __weak id realObject1;
}
- (id)initWithTarget1:(id)t1 ;
@end
@implementation TargetProxy
 
- (id)initWithTarget1:(id)t1{
    realObject1 = t1 ;;
    return self;
}

然后找一个常见的引用持有环引起的内存泄漏场景,NSTimer在ViewController中的使用

_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[[TargetProxy alloc] initWithTarget:self] selector:@selector(timerFire:) userInfo:nil repeats:YES];

这样就避免了NSTimer强引用ViewController导致的内存泄漏.

总结 当需要用到消息转发去解决一些问题时,可以考虑NSProxy

标签: Objective-C, NSProxy, 消息转发, NSObject, forwardInvocation, methodSignatureForSelector, 循环引用

添加新评论