NSProxy

Posted by Maqy on December 5, 2018

iOS里有一个特别神奇的存在,他就是NSProxy,一个孤单的存在,我们可以看到它的定义

@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");

....

@end

它基本就只有上面的一个消息转发的方法,其他的基本可以忽略,通过这个消息转发我们可以模拟很多场景,也可以实现很多炫酷的事情,下面就举举例子吧。

多重继承

我们知道OC里是没有多重继承的,那要想实现多重继承的效果,需要怎么办呢?

首先我们要知道所谓多重继承,其实就是让一个对象既拥有类A的特性,又拥有类B的特性,那么这是不是和组合特别像呢?而且设计模式里也有提过组合优于继承,我们是不是可以用NSProxy的转发机制来模拟多重继承呢?

比如,我们有类A,和类B,那我们如何实现一个具有A和B的类呢?

@interface AB : NSProxy
@property (nonatomic) A *a;
@property (nonatomic) B *b;
@end 

@implementation AB
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    NSMethodSignature *methodSignature;
    if (!methodSignature) {
        methodSignature = [self.a methodSignatureForSelector:sel];    
    }
    if (!methodSignature) {
        methodSignature = [self.b methodSignatureForSelector:sel];    
    }
    return methodSignature;
}
 
- (void)forwardInvocation:(NSInvocation *)invocation {
    if([self.a respondsToSelector:anInvocation.selector]) {
        [invocation setTarget:self.a];
        [invocation invoke];
    } else if([self.b respondsToSelector:anInvocation.selector]) {
        [invocation setTarget:self.b];
        [invocation invoke];
    }
}
@end

如此,我们声明一个包含A和B两个属性的Proxy类AB,当我们给AB调用一个方法的时候来进行转发,转发的时候,看哪个对象拥有该方法的方法签名,然后进行转发,让具体的对象来进行响应,如此AB就拥有了A和B的特性啦!

解循环引用

假设下面场景:

VC持有Timer,然后Timer的响应者是VC,那么当VC释放时,如果Timer没有停止,其实是释放不掉的,而且VC的dealloc也不会调用,那么怎么用NSProxy来解除循环引用呢?其实还是消息转发啦!

正常情况下的引用关系是 VC –> Timer –> VC

而使用NSProxy之后的引用关系是 VC –> Timer –> NSProxy , NSProxy 来弱持有VC,从而避免循环引用

@interface WeakProxy ()
@property (weak,nonatomic,readonly)id target;
@end
@implementation WeakProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    // 生成方法签名
    return methodSignature;
}
 
- (void)forwardInvocation:(NSInvocation *)invocation {
    // 转发给target
}
@end

// 接下来VC调用的时候方式就是
self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerInvoked:) userInfo:nil repeats:YES];

通过这种方式,VC持有了Timer,Timer的响应者是WeakProxy,而WeakProxy是弱持有的VC,且是通过消息转发的方式调用给VC的,所以Timer的使用和普通的在类内直接响应的方式是差不多的,这样就可以避免循环引用了。

AOP

面向切面变成,也就是我们在不改变原有功能的情况下增加新的功能,或对原有的功能进行改变,其实还是消息转发的应用啦,其定义和上面的WeakProxy类似,只不过在做转发的时候会通过方法替换或者直接在forward里添加功能的方式来进行AOP编程。

订阅者模式

订阅者模式通常有个调度中心,我们可以通过消息转发来讲消息中心的事件化整为零,直接转发给订阅者,省心又省力

@protocol Actions 
- (void)event1;
- (void)event2;
@end

@interface SubscribeCenter : NSProxy <Actions>
@property (nonatomic) NSArray *subscribers;
@end
@implementation SubscribeCenter
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    // 生成方法签名
    return methodSignature;
}
 
- (void)forwardInvocation:(NSInvocation *)invocation {
    [@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [invocation setTarget:obj];
        [invocation invoke];
    }];
}
@end

这样就可以把消息都转发到对应的接收者上去啦~~~