美文网首页
iOS设计模式七(享元,代理,备忘录)

iOS设计模式七(享元,代理,备忘录)

作者: oldSix_Zhu | 来源:发表于2017-06-06 13:30 被阅读79次

    承接上文iOS设计模式六(模板,策略,命令)
    本文为性能与对象访问-获取源码

    目录
    1 享元模式
    2 代理模式
    2.1 NSProxy实现代理模式
    3 备忘录模式


    1 享元模式

    实现享元模式需要两个关键组件,一个是要共享的享元对象,一个是保存它们的池,通过工厂方法返回实例.

    看到这里是不是有种很熟悉的感觉,没错,Cocoa框架中也有这种模式的大量应用
    我们经常用的UITableView以及UICollectionView创建cell的时候,就是在缓存池中拿cell,如果没有,才去创建一个

    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellId"];
        if (!cell)
        {
            cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellId"];
        }
        return cell;
    }
    

    我们可以模仿一下这种缓存池策略
    定义一个要共享的花类OSZFlower.h

    typedef enum : NSUInteger {
        Type1,
        Type2,
        Type3,
        AllTypesCount,//巧妙显示枚举的长度
    } FlowerType;
    
    #import <Foundation/Foundation.h>
    @interface OSZFlower : NSObject
    
    //花头
    @property (nonatomic, copy) NSString *flowerHead;
    //花茎
    @property (nonatomic, copy) NSString *flowerRod;
    
    @end
    

    OSZFlower.m中没有东西,简单的定义一个类
    另一个重要组件工厂类OSZFlowerFactory.h

    #import <Foundation/Foundation.h>
    #import "OSZFlower.h"
    @interface OSZFlowerFactory : NSObject
    
    //缓存池
    @property (nonatomic, strong) NSMutableDictionary *flowerPool;
    //根据花的类型创建花
    - (OSZFlower *)flowerViewWithType:(FlowerType)type;
    
    @end
    

    OSZFlowerFactory.m

    #import "OSZFlowerFactory.h"
    
    @implementation OSZFlowerFactory
    
    - (OSZFlower *)flowerViewWithType:(FlowerType)type
    {
        //懒加载花朵池
        if (self.flowerPool == nil)
        {
            self.flowerPool = [[NSMutableDictionary alloc]initWithCapacity:AllTypesCount];
        }
        //先取一朵花
        OSZFlower *flower = [self.flowerPool objectForKey:[NSNumber numberWithInteger:type]];
        //取不到就根据类型创建一个并保存在池子中
        if (flower == nil)
        {
            //实例化并配置
            flower = [[OSZFlower alloc]init];
            switch (type) {
                case Type1:
                    flower.flowerHead = @"红花";
                    flower.flowerRod = @"绿杆";
                    break;
                case Type2:
                    flower.flowerHead = @"黄花";
                    flower.flowerRod = @"绿杆";
                    break;
                case Type3:
                    flower.flowerHead = @"蓝花";
                    flower.flowerRod = @"绿杆";
                    break;
                default:
                    break;
            }
            //存入缓存池
            [self.flowerPool setObject:flower forKey:[NSNumber numberWithUnsignedInteger:type]];
        }
        return flower;
    }
    
    @end
    

    控制器OSZSixteenVC.m

    #import "OSZSixteenVC.h"
    #import "OSZFlower.h"
    #import "OSZFlowerFactory.h"
    
    @interface OSZSixteenVC ()
    
    @end
    
    @implementation OSZSixteenVC
    
    - (void)viewDidLoad {
        [super viewDidLoad]; 
        //创建保存花朵的数组
        NSMutableArray *flowers = [[NSMutableArray alloc]init];
        OSZFlowerFactory *factory = [[OSZFlowerFactory alloc]init];
        //创建大量随机的花
        for (int i = 0; i < 1000000; i++)
        {
            FlowerType flowerType = arc4random_uniform(AllTypesCount);
    
            //正常创建对象 : 100w:58m  500w:184m
    //        OSZFlower *flower = [[OSZFlower alloc]init];
            //使用有缓存池的工厂方法:  100w:25m  500w:峰值74m,稳定25m
            OSZFlower *flower = [factory flowerViewWithType:flowerType];
    
            [flowers addObject:flower];
        }
        NSLog(@"%lu",(unsigned long)flowers.count);
    }
    @end
    

    可以观察到,当数据量为100万与500万时,内存峰值的变化,
    用享元模式创建对象,可以很大程度上地减少内存消耗
    如果享元模式创建出来的对象太单一,不能满足需求,我们可以在享元模式创建出来的对象的基础上,再与其它对象组装,这样就减少了一部分内存消耗,达到了复用的目的
    当然,使用要具体情况具体分析

    分享是人类的美德.


    2 代理模式

    代理模式的思想是使用一个基本上跟实体对象行为相同的代理
    代理是一种替代或者占位,它控制着对另一些对象的访问,而这些对象可能是远程对象,对安全性有要求的对象,或者创建开销较大的对象
    这本书只讲了虚拟代理.

    用虚拟代理懒加载图像:
    在用户滚动一个全为缩略图的视图时加载真正图像的机制
    OSZLoadmageProxy.h

    #import <UIKit/UIKit.h>
    @interface OSZLoadmageProxy : UIView
    
    //真实图像路径
    @property (nonatomic, copy) NSString *imagePath;
    //图像
    @property (nonatomic, weak) UIImage *image;
    
    @end
    

    OSZLoadmageProxy.m

    #import "OSZLoadmageProxy.h"
    @interface OSZLoadmageProxy  ()
    
    //保存加载后的真实图像
    @property (nonatomic, weak) UIImage *realImage;
    //用于控制转发真实图像的加载
    @property (nonatomic, assign) BOOL loadingHasLaunched;
    //在后台加载真正的图片
    - (void)forwardImageLoading;
    
    @end
    
    @implementation OSZLoadmageProxy
    
    //如果不需要对象显示在视图上,可以使用这个方法来转发真实图像的加载
    -(UIImage *)image
    {
        if (_realImage == nil)
        {
            UIImage *img = [[UIImage alloc]initWithContentsOfFile:self.imagePath];
            _realImage = img;
        }
        return _realImage;
    }
    
    - (void)drawRect:(CGRect)rect {
        
        //如果realImageView中没有真实图像,就绘制一幅空白图框,作为占位图像代理
        if (self.realImage == nil)
        {
            //绘制空白图框代码
            UIView *white = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
            white.backgroundColor = [UIColor blueColor];
            [self addSubview:white];
        }
    
        //如果没有进行加载,就启动一个线程进行加载真正的图像
        if (!self.loadingHasLaunched)
        {
            [self performSelectorInBackground:@selector(forwardImageLoading) withObject:nil];
            self.loadingHasLaunched = YES;
        }
        //如果加载实际内容了,就绘制
        else
        {
            [self.realImage drawInRect:rect];
        }
    }
    
    //加载真实图像
    -(void)forwardImageLoading
    {
        //转发给上面的方法
        [self image];
        //然后重新调用drawRect
        [self performSelectorInBackground:@selector(setNeedsDisplay) withObject:nil];
    }
    
    @end
    

    2.1 NSProxy实现代理模式

    OC不支持多继承,
    当我们想很快地把两个类的功能集合在一个类中的时候,
    我们可以用NSProxy来实现类似多继承

    NSProxy是Cocoa框架中如NSObject一样的根类,
    它实现了NSObject协议,所以本质上还是NSObject类型,
    但它是一个抽象基类,没有自己的初始化方法
    主要作用只有一个:当代理
    是为其他对象的替身对象定义一个API,发给代理对象的消息会转发给实体对象,或代理加载实体对象,或代理变成实体对象

    比如有一个可以拿到书的类和可以拿到笔的类,想在一个类里面有这两个方法,就要实现类似多继承了

    有一个可以拿到书方法的类OSZBookProvider.h

    #import <Foundation/Foundation.h>
    @protocol OSZBookProviderProtocol <NSObject>
    - (void)getBook:(NSString *)bookTitle;
    @end
    
    @interface OSZBookProvider : NSObject<OSZBookProviderProtocol>
    @end
    

    OSZBookProvider.m

    #import "OSZBookProvider.h"
    @implementation OSZBookProvider
    
    -(void)getBook:(NSString *)bookTitle{
        NSLog(@"%@",bookTitle);
    }
    
    @end
    

    有一个可以拿到笔方法的类OSZPenProvider.h

    #import <Foundation/Foundation.h>
    @protocol OSZPenProviderProtocol <NSObject>
    - (void)getPen:(NSString *)bookTitle;
    @end
    
    @interface OSZPenProvider : NSObject<OSZPenProviderProtocol>
    @end
    

    OSZPenProvider.m

    #import "OSZPenProvider.h"
    @implementation OSZPenProvider
    
    -(void)getPen:(NSString *)bookTitle{
        NSLog(@"%@",bookTitle);
    }
    
    @end
    

    接下来是把两个类的方法集成在一个类中,关键是要实现协议
    OSZProviderProxy.h

    #import <Foundation/Foundation.h>
    #import "OSZBookProvider.h"
    #import "OSZPenProvider.h"
    
    @interface OSZProviderProxy : NSProxy<OSZBookProviderProtocol,OSZPenProviderProtocol>
    
    + (instancetype)initProxy;
    
    @end
    

    然后运用运行时给方法签名调用的特性,转发到这两个类去调用方法

    #import "OSZProviderProxy.h"
    #import <objc/runtime.h>
    
    @interface OSZProviderProxy ()
    @property (nonatomic, strong) OSZBookProvider *bookProvider;
    @property (nonatomic, strong) OSZPenProvider *penProvider;
    @property (nonatomic, strong) NSMutableDictionary *methodsDic;
    @end
    
    @implementation OSZProviderProxy
    
    + (instancetype)initProxy{
        //NSProxy类是没有init方法的,它是一个虚基类
        return [[OSZProviderProxy alloc]init];
    }
    
    - (instancetype)init{
        self.methodsDic = [NSMutableDictionary dictionary];
        self.bookProvider = [[OSZBookProvider alloc] init];
        self.penProvider = [[OSZPenProvider alloc] init];
        
        //映射target及其对应方法名
        [self _registerMethodsWithTarget:self.bookProvider];
        [self _registerMethodsWithTarget:self.penProvider];
        
        return self;
    }
    
    - (void)_registerMethodsWithTarget:(id )target{
        
        unsigned int numberOfMethods = 0;
        
        //获取target方法列表
        Method *method_list = class_copyMethodList([target class], &numberOfMethods);
        
        for (int i = 0; i < numberOfMethods; i ++) {
            //获取方法名并存入字典
            Method temp_method = method_list[i];
            SEL temp_sel = method_getName(temp_method);
            const char *temp_method_name = sel_getName(temp_sel);
            [self.methodsDic setObject:target forKey:[NSString stringWithUTF8String:temp_method_name]];
        }
        
        free(method_list);
    }
    
    
    //方法签名
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
        //获取选择子方法名
        NSString *methodName = NSStringFromSelector(sel);
        
        //在字典中查找对应的target
        id target = self.methodsDic[methodName];
        
        //检查target
        if (target && [target respondsToSelector:sel]) {
            return [target methodSignatureForSelector:sel];
        } else {
            return [super methodSignatureForSelector:sel];
        }
    }
    
    //执行
    - (void)forwardInvocation:(NSInvocation *)invocation{
        //获取当前选择方法 
        SEL sel = invocation.selector;
        
        //获取选择子方法名
        NSString *methodName = NSStringFromSelector(sel);
        
        //在字典中查找对应的target
        id target = self.methodsDic[methodName];
        
        //检查target
        if (target && [target respondsToSelector:sel]) {
            [invocation invokeWithTarget:target];
        } else {
            [super forwardInvocation:invocation];
        }
    }
    
    @end
    

    控制器中调用

    #import "OSZSeventeenVC.h"
    #import "OSZProviderProxy.h"
    
    @interface OSZSeventeenVC ()
    @end
    
    @implementation OSZSeventeenVC
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        OSZProviderProxy *proxy = [OSZProviderProxy initProxy];
        [proxy getBook:@"书本"];//打印书本
        [proxy getPen:@"钢笔"];//打印钢笔
    }
    
    //  它的运行过程是这样的:
    //1.判断OSZProviderProxy实例化对象是否能响应者两个方法,
    //2.不能,在崩溃前,runtime会做消息转发,向实例化对象发送methodSignatureForSelector消息,取得被转发的消息的正确方法签名
    //3.取得签名后,构造一个NSInvocation实例,使用forwardInvocation让代理对象把调用转发给其他对象
    
    在一个函数找不到时,Objective-C提供了三种方式去补救:

    1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数
    2、调用forwardingTargetForSelector让别的对象去执行这个函数
    3、调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行
    4、如果都不中,调用doesNotRecognizeSelector抛出异常。

    所以代理模式跟内存优化有什么关系呢,

    其实就是对占用内存大的类,在实例化时,使用懒加载,同时使用虚拟代理提供轻量的信息(缩略图之类的)
    很多模式都在Cocoa框架与开发中不知不觉使用了,只不过是没有好好归纳总结,此笔记就是总结一下,并没有实际应用

    扩展:
    继承自NSObject的不常用又很有用的函数(2)
    NSProxy——少见却神奇的类


    3 备忘录模式

    很像玩游戏退出时存档一下,CocoaTouch框架中的归档,属性列表序列化,核心数据等采用了备忘录模式

    这个模式有三个关键角色:原发器(Originator)、备忘录(Memento)、看管人(caretaker)
    三者的基本关系是:原发器创建一个包含其状态的备忘录,并传给看管人;
    看管人不知道如何与备忘录交互,但会把备忘录放在一个安全之处保管好;
    原发器不知道这个备忘录被如何保存,看管人不知道这个备忘录里面是什么

    管理者OSZCaretaker.h

    #import <Foundation/Foundation.h>
    #import "OSZMemo.h"
    @interface OSZCaretaker : NSObject
    //备忘录
    @property (nonatomic, strong) OSZMemo *memo;
    
    @end
    

    OSZCaretaker.m

    #import "OSZCaretaker.h"
    @implementation OSZCaretaker
    
    - (void)setMemo:(OSZMemo *)memo
    {
        //归档
        //1:准备路径
        NSString *path = NSHomeDirectory();
        path = [path stringByAppendingString:@"memo.plist"];
        //2:准备存储数据对象(用可变数组进行接收)
        NSMutableData *data = [NSMutableData new];
        //3:创建归档对象
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
        //4:开始归档
        [archiver encodeObject:memo forKey:@"caretaker"];
        //5:完成归档
        [archiver finishEncoding];
        //6:写入文件
        BOOL result = [data writeToFile:path atomically:YES];
        if (result) {
            NSLog(@"归档成功:%@",path);
        }
    }
    
    - (OSZMemo *)memo
    {
        //反归档
        //1:获取解档路径
        NSString *path = NSHomeDirectory();
        path = [path stringByAppendingString:@"memo.plist"];
        NSData *MyData = [NSData dataWithContentsOfFile:path];
        //2:创建反归档对象
        NSKeyedUnarchiver  *unarchiver= [[NSKeyedUnarchiver alloc]initForReadingWithData:MyData];
        //3:反归档
        OSZMemo *memo = [[OSZMemo alloc]init];
        memo = [unarchiver decodeObjectForKey:@"caretaker"];
        //4:结束归档
        [unarchiver finishDecoding];
        NSLog(@"%@,%@",memo.name,memo.number);
        
        return memo;
    }
    
    @end
    

    备忘录OSZMemo.h

    #import <Foundation/Foundation.h>
    @interface OSZMemo : NSObject<NSCoding>
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *number;
    
    //便利构造器
    - (instancetype)initWithName:(NSString *)name andPhoneNumber:(NSString *)number;
    
    @end
    

    被归档保存的对象必须遵守<NSCoding>并实现两个方法:
    initWithCoder
    encodeWithCoder
    OSZMemo.m

    #import "OSZMemo.h"
    @implementation OSZMemo
    
    - (instancetype)initWithName:(NSString *)name andPhoneNumber:(NSString *)number
    {
        self.name = name;
        self.number = number;
        return self;
    }
    
    //归档
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        [aCoder encodeObject:self.name forKey:@"name"];
        [aCoder encodeObject:self.number forKey:@"number"];
    }
    
    //解档
    -(instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        if (self = [super init])
        {
            self.name = [aDecoder decodeObjectForKey:@"name"];
            self.number = [aDecoder decodeObjectForKey:@"number"];
        }
        return self;
    }
    

    OSZPerson.h

    #import <Foundation/Foundation.h>
    #import "OSZMemo.h"
    @interface OSZPerson : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *number;
    
    //把信息保存至通讯录
    - (OSZMemo *)createMemo;
    //从通讯录提取保存的信息
    - (void)resumeFromMemo:(OSZMemo *)memo;
    
    @end
    

    OSZPerson.m

    #import "OSZPerson.h"
    @implementation OSZPerson
    
    - (OSZMemo *)createMemo
    {
        return [[OSZMemo alloc]initWithName:self.name andPhoneNumber:self.number];
    }
    
    - (void)resumeFromMemo:(OSZMemo *)memo
    {
        self.name = memo.name;
        self.number = memo.number;
    }
    
    @end
    

    控制器OSZEighteenVC.m

    #import "OSZEighteenVC.h"
    #import "OSZMemo.h"
    #import "OSZCaretaker.h"
    #import "OSZPerson.h"
    
    @interface OSZEighteenVC ()
    @end
    
    @implementation OSZEighteenVC
    - (void)viewDidLoad {
        [super viewDidLoad];
      
        //有一个人,想保存信息至备忘录
        OSZPerson *p = [[OSZPerson alloc]init];
        p.name = @"张三";
        p.number = @"13812345678";
        NSLog(@"%@,%@",p.name,p.number);
        
        //管理者管理这个人保存的备忘录,仅仅负责保存的功能
        OSZCaretaker *caretaker = [[OSZCaretaker alloc]init];
        //可以通过重写set方法,选择多种方式把通讯录保存,比如归档
        caretaker.memo = [p createMemo];
        
        //重新编辑他的信息
        p.name = @"李四";
        p.number = @"13700000000";
        NSLog(@"%@,%@",p.name,p.number);
        
        //回档
        [p resumeFromMemo:caretaker.memo];
        NSLog(@"%@,%@",p.name,p.number);
        /*
         -[OSZEighteenVC viewDidLoad]   张三,13812345678
         -[OSZCaretaker setMemo:]       归档成功:/Users/Mac/Library/Developer/CoreSimulator/Devices/0008B684-4C8A-44E9-AF68-9CDF558768AC/data/Containers/Data/Application/7104B870-E90B-455A-AA7B-B22E2870763Fmemo.plist
         -[OSZEighteenVC viewDidLoad]   李四,13700000000
         -[OSZCaretaker memo]           张三,13812345678
         -[OSZEighteenVC viewDidLoad]   张三,13812345678
         */
    }
    @end
    

    扩展:
    归档的三种方式
    iOS设计模式之备忘录模式


    至此,<Object-C编程之道 | iOS设计模式解析>就全部读完了,但具体如何应用,日后再更新吧

    相关文章

      网友评论

          本文标题:iOS设计模式七(享元,代理,备忘录)

          本文链接:https://www.haomeiwen.com/subject/slnotxtx.html