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