说说NSProxy

在Obective-C中,绝大部分的类都继承自NSObject,但是NSProxy确实例外, 它和NSObject一样,不继承自任何一个类。

那么NSProxy这个类是用来干嘛的?什么时候用到这个类?

我们对比一下NSProxy和NSObject的定义,可以发现,NSProxy有的属性、实例变量和方法,NSObject都有,并且都实现了NSObject协议。所以,从理论上来说,NSProxy能够实现的功能,NSObject都能完成。

那么为什么还需要这个类呢?这个问题先放在这里,我们先来看看一般用NSProxy做什么

 

一、使用NSProxy实现面向切面编程(AOP)

1、概念:AOP:AOP是一种编程范式,其目的是通过横切将功能与程序中的其他组成部分分开,提高程序的模块化程度。

2、实例:

假设有这么一个需求,我需要在一个系统类库中的某个实例方法方法调用前后做一些处理,我们想到的方式可能有三种:

a、直接在调用该方法前后做处理(缺点:每次调用都需要处理,麻烦、不利于代码重用、不优雅……)

b、写一个该类的子类,重载这个方法(缺点:对每一个有该需求的类都需要创建一个子类, 对每一个需要处理的方法都要重载)

c、写一个类别,通过runtime的方法交换来实现(缺点:和b一样,对每一个有该需求的类都需要创建一个分类, 对每一个需要处理的方法都需要交换)

下面我们来看通过AOP的方式实现:

// YQProxy.h
#import <Foundation/Foundation.h>

// 定义一个需要调用的block
typedef void(^YQProxyBlock)(id targete, SEL selector);

@interface YQProxy : NSProxy

// 创建代理
+ (id)proxyWithTarget:(id)target;

// 对某个方法注册相应的block
- (void)registerSelector:(SEL)selector withPreBlock:(YQProxyBlock)preBlock sufBlock:(YQProxyBlock)sufBlock;

@end


// YQProxy.m
#import "YQProxy.h"

@interface YQProxy ()

@property (nonatomic, strong) id targete;   // 实际调用方法的对象
@property (nonatomic, strong) NSMutableDictionary<NSValue *, YQProxyBlock> *preBlocks;  // 目标方法调用之前调用的block
@property (nonatomic, strong) NSMutableDictionary<NSValue *, YQProxyBlock> *sufBlocks;  // 目标方法调用之后调用的block

@end

@implementation YQProxy

+ (id)proxyWithTarget:(id)target{
    YQProxy *proxy = [YQProxy alloc];
    
    proxy.targete = target;
    proxy.preBlocks = [NSMutableDictionary dictionary];
    proxy.sufBlocks = [NSMutableDictionary dictionary];
    
    return proxy;
}

#pragma mark - override
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    return [self.targete methodSignatureForSelector:sel];
}

// 进行消息转发
- (void)forwardInvocation:(NSInvocation *)invocation{
    
    if (![self.targete respondsToSelector:invocation.selector]) {
        return;
    }
    
    NSValue *method = [NSValue valueWithPointer:invocation.selector];
    
    YQProxyBlock preBlock = [self.preBlocks objectForKey:method];
    YQProxyBlock sufBlock = [self.sufBlocks objectForKey:method];
    
    if (preBlock) {
        preBlock(self.targete, invocation.selector);
    }
    
    [invocation invokeWithTarget:self.targete];
    
    if (sufBlock) {
        sufBlock(self.targete, invocation.selector);
    }
}

#pragma mark - public

- (void)registerSelector:(SEL)selector withPreBlock:(YQProxyBlock)preBlock sufBlock:(YQProxyBlock)sufBlock{
    NSValue *method = [NSValue valueWithPointer:selector];
    
    if (preBlock) {
        [self.preBlocks setObject:preBlock forKey:method];
    }else if ([self.preBlocks objectForKey:method]){
        [self.preBlocks removeObjectForKey:method];
    }
    
    if (sufBlock) {
        [self.sufBlocks setObject:sufBlock forKey:method];
    }else if ([self.sufBlocks objectForKey:method]){
        [self.sufBlocks removeObjectForKey:method];
    }
    
}

@end

 

方法调用

#import "YQProxy.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        id array = [YQProxy proxyWithTarget:[NSMutableArray array]];
        [array registerSelector:@selector(addObject:) withPreBlock:^(id targete, SEL selector) {
            NSLog(@"%@:准备插入一个数据到数组中", targete);
        } sufBlock:^(id targete, SEL selector) {
            NSLog(@"%@:插入数据完成", targete);
        }];
        
        id string = [YQProxy proxyWithTarget:[NSMutableString stringWithString:@"hello"]];
        [string registerSelector:@selector(appendString:) withPreBlock:^(id targete, SEL selector) {
            NSLog(@"%@:正在追加字符串", targete);
        } sufBlock:^(id targete, SEL selector) {
            NSLog(@"%@:追加字符串完成", targete);
        }];
        
        
        [array addObject:@"你好"];
        [string appendString:@", world!"];
    }
    return 0;
}

 

打印结果

2017-08-10 10:14:29.090402+0800 NSProxyTest[57689:7732479] (
):准备插入一个数据到数组中
2017-08-10 10:14:29.090638+0800 NSProxyTest[57689:7732479] (
    "\U4f60\U597d"
):插入数据完成
2017-08-10 10:14:29.090724+0800 NSProxyTest[57689:7732479] hello:正在追加字符串
2017-08-10 10:14:29.090752+0800 NSProxyTest[57689:7732479] hello, world!:追加字符串完成
Program ended with exit code: 0

 

可以看到,通过这种方式比较优雅的有较少的代码就完成了这种需求。

 

二、使用NSProxy模拟多继承

多继承在编程中可以说是比较有用的特性。举个例子,原本有两个相互独立的类A和类B,它们各自继承各自的父类,项目进行地好好的,突然有一天产品经理过来告诉你,我要在下个版本加一个xxxxx的特性,非常紧急。一脸懵逼的你发现如果要实现这个特性,你需要对类A以及其父类作很大的修改,代价非常之高。突然你意识到原来类B的父类已经有类似的功能,你只需要让类A继承于类B的父类并重写其某些方法就能实现,这样做高效且低风险,于是你屁颠屁颠地撸起了代码。
 

可是,Objective-C却不支持这样一个强大的特性。不过NSProxy可以帮我们在某种程度上(这只是一个模拟的多继承,并不是完全的多继承)解决这个问题:

现在假设我们想要去买书,但是我懒癌犯了,不想直接去书店(供应商)买,如果有一个跑腿的人(经销商)帮我去书店买完,我再跟他买。同时,我买完书又想买件衣服,我又可以很轻松地在他那里买到一件衣服(多继承)。

 

创建一个书店

//YQBookStore.h

#import <Foundation/Foundation.h>

@interface YQBookStore : NSObject

- (void)fetchBookWithTitle:(NSString *)title;

@end

// YQBookStore.m

#import "YQBookStore.h"

@implementation YQBookStore

- (void)fetchBookWithTitle:(NSString *)title{
    if (!title) {
        NSLog(@"请确定书名");
        return;
    }
    NSLog(@"这是您好的书:%@", title);
}

@end

 

创建一个服装店

// YQClothesStore.h

#import <Foundation/Foundation.h>

@interface YQClothesStore : NSObject

- (void)buyCloths;

@end

// YQClothesStore.m

#import "YQClothesStore.h"

@implementation YQClothesStore

- (void)buyCloths{
    NSLog(@"您的衣服");
}

@end

 

创建一个既能卖书、又能卖衣服的代理商

// YQStoreProxy.h
#import <Foundation/Foundation.h>

#import "YQBookStore.h"
#import "YQClothesStore.h"

@interface YQStoreProxy : NSProxy

- (instancetype)init;

@end

// YQStoreProxy.m

#import "YQStoreProxy.h"

@interface YQStoreProxy ()

@property (nonatomic, strong) YQBookStore *bookStore;
@property (nonatomic, strong) YQClothesStore *clothesStore;

@end

@implementation YQStoreProxy

- (instancetype)init{
    _bookStore = [[YQBookStore alloc] init];
    _clothesStore = [[YQClothesStore alloc] init];
    
    return self;
}

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

- (void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:[self targetWithSeletor:invocation.selector]];
}

#pragma mark - private
- (id)targetWithSeletor:(SEL)selector{
    if ([self.bookStore respondsToSelector:selector]) {
        return self.bookStore;
    }else if ([self.clothesStore respondsToSelector:selector]){
        return self.clothesStore;
    }
    
    return nil;
}

@end

 

使用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        id store = [[YQStoreProxy alloc] init];
        [store buyBookWithTitle:@"鬼吹灯"];
        [store buyCloths];
    }
    return 0;
}

 

运行结果

2017-08-10 10:54:11.629856+0800 NSProxyTest[58903:7889918] 这是您好的书:鬼吹灯
2017-08-10 10:54:11.630060+0800 NSProxyTest[58903:7889918] 您的衣服

 

从上面的实例中可以看到,通过NSProxy结合消息转发机制, 很好的实现在面向切面编程和Objective-C的多继承。

所以, 我们常常通过NSProxy来转发消息。回到上面的问题,理论上,NSProxy能实现的功能,NSObject都能实现,而在上面的两个案例中,把NSProxy改为NSObject也完全没有问题。那么,为什么还需要NSProxy类呢?以下是知乎上的回答

简单说就是不需要实现那么多杂七杂八的东西,多了反而可能引起冲突,比如KVC,NSProxy根本就不需要了。NSProxy主要目的是用forwardInvocation:方法来进行消息转发,如果继承NSObject容易导致冲突,所以你看看NSProxy的API就会发现相比NSObject来说太简洁了。而NSProxy是遵循了NSObject协议(注意这里是NSObject协议,不是类)的,里面声明了一套所有的根类都可以实现的基础方法。
 
参考:
http://www.jianshu.com/p/8d42cc3a6296

https://www.zhihu.com/question/38622888/answer/77406280

 

posted @ 2017-08-10 11:07  恋~时光  阅读(574)  评论(0编辑  收藏  举报