zoukankan      html  css  js  c++  java
  • 说说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] (
        "U4f60U597d"
    ):插入数据完成
    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
  • 相关阅读:
    C对字符串的部分操作
    <string> <string.h>
    最常见的HTTP错误
    python面试题
    玩转type类型(牛逼克拉斯 )
    django路由系统之反向生成url
    django事物回滚
    django中admin路由系统工作原理
    django中的django admin插件
    ajax跨域资源共享
  • 原文地址:https://www.cnblogs.com/yueyuanyueyuan/p/7338149.html
Copyright © 2011-2022 走看看