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

承接上文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设计模式解析>就全部读完了,但具体如何应用,日后再更新吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 开发仿真系统## 考虑这样一个仿真应用,功能是:模拟运行针对某个具体问题的多个解决方案,记...
    七寸知架构阅读 2,184评论 1 50
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,978评论 1 15
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,948评论 18 139
  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,880评论 3 14
  • 人生呐,人生 有的时候,生活了一辈子,夫妻年过半百。 也许都没有真正的敞开心扉,和对方说出自己的心里话。 难以启齿...
    辣嘴电影阅读 451评论 0 1