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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

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