iOS开发中的代理与备忘录模式解析
1. 代理模式在缩略图视图中的应用
在开发中,实现一种“神奇”的缩略图视图,关键在于使用占位图像代理和在后台加载真实图像的机制,以确保整个过程流畅。
1.1 代理设计基础
ScribbleThumbnailViewImageProxy和UIImage的类图基本结构体现了原始代理模式的精髓,它们经过定制以满足TouchPainter应用的需求。ScribbleThumbnailViewImageProxy和UIImage都实现了相似的draw*接口,用于在UIView对象上绘制内容。ScribbleThumbnailView本身是UIView的子类,其子类可与UITableViewCell配合使用以进行显示。
1.2 图像预加载机制
ScribbleThumbnailViewImageProxy的图像预加载机制是,若真实图像尚未加载,则加载并显示在屏幕上。运行时,客户端访问ScribbleThumbnailView的子类ScribbleThumbnailViewImageProxy实例,UIImage实例在客户端请求时才创建。这意味着在请求加载之前,真实图像数据一直存于磁盘。若未触发实际加载,内存中仅存在ScribbleThumbnailViewImageProxy对象。这种延迟加载方法可用于加载任何需要在UIView上显示的昂贵资源。
1.3 ScribbleThumbnailView类的实现
以下是ScribbleThumbnailView类的声明:
@interface ScribbleThumbnailView : UIView
{
@protected
NSString *imagePath_;
}
@property (nonatomic, readonly) UIImage *image;
@property (nonatomic, copy) NSString *imagePath;
@end
该类主要关注用于加载和返回真实图像的实际路径。作为抽象基类,它维护抽象的image和imagePath属性,这些属性对后续的虚拟代理操作至关重要。其实现如下:
#import "ScribbleThumbnailView.h"
@implementation ScribbleThumbnailView
@dynamic image;
@synthesize imagePath=imagePath_;
@end
1.4 ScribbleThumbnailViewImageProxy类的实现
ScribbleThumbnailViewImageProxy类的声明如下:
#import "ScribbleThumbnailView.h"
@interface ScribbleThumbnailViewImageProxy : ScribbleThumbnailView
{
@private
UIImage *realImage_;
BOOL loadingThreadHasLaunched_;
}
@property (nonatomic, readonly) UIImage *image;
@end
其中,realImage_用于保存加载后的真实图像引用,loadingThreadHasLaunched_用于控制真实图像的加载过程。其实现如下:
#import "ScribbleThumbnailViewImageProxy.h"
// A private category for a forward loading thread
@interface ScribbleThumbnailViewImageProxy ()
- (void) forwardImageLoadingThread;
@end
@implementation ScribbleThumbnailViewImageProxy
@dynamic imagePath;
// Clients can use this method directly
// to forward-load a real image
// if there is no need to show this object
// on a view.
- (UIImage *) image
{
if (realImage_ == nil)
{
realImage_ = [[UIImage alloc] initWithContentsOfFile:imagePath_];
}
return realImage_;
}
// A forward call will be established
// in a separate thread to get a real payload from
// a real image.
// Before a real payload is returned,
// drawRect: will handle the background
// loading process and draw a placeholder frame.
// Once the real payload is loaded,
// it will redraw itself with the real one.
- (void)drawRect:(CGRect)rect
{
// if is no real image available
// from realImageView_,
// then just draw a blank frame
// as a placeholder image
if (realImage_ == nil)
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
// draw a placeholder frame
// with a 10-user-space-unit-long painted
// segment and a 3-user-space-unit-long
// unpainted segment of a dash line
CGContextSetLineWidth(context, 10.0);
const CGFloat dashLengths[2] = {10,3};
CGContextSetLineDash (context, 3, dashLengths, 2);
CGContextSetStrokeColorWithColor(context, [[UIColor darkGrayColor] CGColor]);
CGContextSetFillColorWithColor(context, [[UIColor lightGrayColor] CGColor]);
CGContextAddRect(context, rect);
CGContextDrawPath(context, kCGPathFillStroke);
// launch a thread to load the real
// payload if it hasn't done yet
if (!loadingThreadHasLaunched_)
{
[self performSelectorInBackground:@selector(forwardImageLoadingThread)
withObject:nil];
loadingThreadHasLaunched_ = YES;
}
}
// otherwise pass the draw*: message
// along to realImage_ and let it
// draw the real image
else
{
[realImage_ drawInRect:rect];
}
}
- (void) dealloc
{
[realImage_ release];
[super dealloc];
}
#pragma mark -
#pragma mark A private method for an image forward loading thread
- (void) forwardImageLoadingThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// forward loading the real
// payload
[self image];
// redraw itself with the newly loaded image
[self performSelectorInBackground:@selector(setNeedsDisplay) withObject:nil];
[pool release];
}
@end
整个代理操作可总结如下:
- image属性方法:若realImage_未加载,则使用
[[UIImage alloc] initWithContentsOfFile:imagePath_]
加载真实图像并返回。
- drawRect:方法:若真实图像未加载,绘制占位图像并启动线程加载真实图像;若已加载,则绘制真实图像。
- forwardImageLoadingThread方法:执行image属性方法加载真实图像,然后触发视图重绘。
2. 代理模式在Cocoa Touch框架中的应用
由于Objective - C不支持多重继承,若代理对象不是Cocoa Touch框架中特定类的子类,可考虑使用NSProxy作为占位符或替代对象。
2.1 NSProxy简介
NSProxy是Cocoa框架中的根类,与NSObject类似,实现了NSObject协议。它是抽象基类,无自身的初始化方法,调用其未知方法会抛出异常。其主要目的是为其他对象或尚未存在的对象定义API,消息会转发给真实对象或使代理加载/转换为真实对象。
2.2 关键方法
NSProxy有两个关键实例方法:forwardInvocation:和methodSignatureForSelector:。当NSProxy子类对象无法响应某个方法时,Objective - C运行时会发送methodSignatureForSelector:消息获取方法签名,然后构造NSInvocation实例并发送forwardInvocation:消息进行转发。
2.3 邮件应用示例
在iOS邮件应用中,附件最初仅显示基本信息,如文件名和大小。用户点击占位图标开始加载内容时,图标会被进度视图替代,加载完成后显示实际图像。这体现了代理模式的应用,节省了网络资源、内存和等待时间。
3. 备忘录模式概述
备忘录模式可类比为便签,用于保存对象状态并在需要时恢复。
3.1 备忘录模式定义
备忘录模式是在不违反封装性的前提下,捕获并外部化对象的内部状态,以便后续恢复。该模式有三个关键角色:发起者(Originator)、备忘录(Memento)和管理者(Caretaker)。
3.2 角色交互
发起者创建包含自身状态的备忘录并传递给管理者,管理者保存备忘录但不知其内容,发起者也不知备忘录的保存方式。备忘录类有两个接口,一个供发起者使用,另一个供其他对象使用,以确保其隐私性。
3.3 使用场景
考虑使用备忘录模式的场景包括:
- 需要保存对象状态的快照或部分状态,以便后续恢复。
- 需要隐藏获取状态时可能暴露实现细节的接口。
4. 备忘录模式在TouchPainter中的应用
在TouchPainter应用中,需要将CanvasView上的涂鸦保存到文件系统。
4.1 问题提出
若直接使用NSKeyedUnarchiver的
archivedDataWithRootObject:
方法保存Mark复合对象,会面临Mark对象不知保存路径的问题,且修改Mark会影响其所有实现类。此外,还需考虑保存复合结构的部分内容以及恢复节点位置等问题。
4.2 解决方案
引入Scribble类管理Mark复合对象,它不仅包含Mark实例,还能创建其快照作为备忘录对象,并由ScribbleManager作为管理者保存。保存Scribble时,CanvasViewController发送saveScribble:消息给ScribbleManager,ScribbleManager要求Scribble创建ScribbleMemento对象。使用备忘录对象而非直接使用NSData的原因在于,备忘录数据可包含不同信息,便于恢复操作,且可在应用其他区域复用,避免单纯使用NSData进行状态保存时的性能损耗。
以下是相关对象交互的流程:
graph LR
A[CanvasViewController] -->|saveScribble: Scribble| B[ScribbleManager]
B -->|create ScribbleMemento| C[Scribble]
C -->|ScribbleMemento| B
B -->|Save as NSData| FileSystem
综上所述,代理模式和备忘录模式在iOS开发中都有重要应用,代理模式可优化资源加载,备忘录模式可有效保存和恢复对象状态。开发者可根据具体需求合理运用这两种模式,提升应用性能和可维护性。
iOS开发中的代理与备忘录模式解析(续)
5. 代理模式关键要点总结
为了更清晰地理解代理模式在iOS开发中的应用,我们对前面提到的关键要点进行总结,以下是一个表格形式的梳理:
| 关键要点 | 详情 |
| ---- | ---- |
| 核心机制 | 使用占位图像代理和后台加载真实图像机制,确保缩略图视图加载过程流畅 |
| 类结构 | ScribbleThumbnailViewImageProxy和UIImage实现相似draw*接口,ScribbleThumbnailView是UIView子类 |
| 延迟加载 | UIImage实例在客户端请求时才创建,节省内存资源 |
| NSProxy应用 | 当代理对象不是特定子类时,可使用NSProxy作为占位或替代对象,通过forwardInvocation:和methodSignatureForSelector:方法进行消息转发 |
另外,代理模式在缩略图视图应用中的操作步骤可以总结为以下流程:
graph LR
A[客户端访问ScribbleThumbnailViewImageProxy] -->|请求图像| B{realImage_是否为空}
B -- 是 --> C[加载真实图像]
B -- 否 --> D[返回realImage_]
C --> E[绘制真实图像]
F[drawRect:方法] -->|无真实图像| G[绘制占位图像]
G -->|未启动加载线程| H[启动线程加载真实图像]
H --> E
6. 备忘录模式关键要点总结
同样,对于备忘录模式,我们也进行详细的要点总结,以表格形式呈现:
| 关键要点 | 详情 |
| ---- | ---- |
| 模式定义 | 在不违反封装性的前提下,捕获并外部化对象内部状态,以便后续恢复 |
| 角色职责 | 发起者创建备忘录,管理者保存备忘录,备忘录有宽窄两个接口确保隐私性 |
| 使用场景 | 需要保存对象状态快照或部分状态,且要隐藏获取状态的接口 |
| TouchPainter应用 | Scribble类管理Mark复合对象,ScribbleManager作为管理者保存ScribbleMemento对象 |
备忘录模式在TouchPainter应用中的操作步骤可以用以下流程图表示:
graph LR
A[CanvasViewController] -->|saveScribble: Scribble| B[ScribbleManager]
B -->|create ScribbleMemento| C[Scribble]
C -->|ScribbleMemento| B
B -->|转换为NSData| D[NSData]
D -->|保存到文件系统| E[FileSystem]
7. 两种模式的对比
代理模式和备忘录模式在iOS开发中各有其独特的作用,我们通过以下表格进行对比:
| 模式 | 主要作用 | 应用场景 | 关键操作 |
| ---- | ---- | ---- | ---- |
| 代理模式 | 优化资源加载,实现延迟加载 | 缩略图视图、邮件附件加载等 | 占位图像代理、后台加载、消息转发 |
| 备忘录模式 | 保存和恢复对象状态 | 游戏状态保存、TouchPainter涂鸦保存等 | 发起者创建备忘录、管理者保存备忘录 |
8. 实际开发中的注意事项
在实际开发中使用这两种模式时,需要注意以下几点:
-
代理模式
:
- 合理使用延迟加载,避免不必要的资源浪费。例如,在缩略图视图中,只有当用户真正需要查看图像时才加载真实图像。
- 注意线程安全问题,特别是在多线程加载图像时,要确保数据的一致性。
- 对于NSProxy的使用,要正确实现forwardInvocation:和methodSignatureForSelector:方法,避免出现异常。
-
备忘录模式
:
- 确保备忘录类的隐私性,避免其他对象直接访问内部状态。
- 考虑备忘录对象的存储方式,如文件系统存储或内存存储,根据实际需求选择合适的方式。
- 在处理复杂对象状态时,要注意状态的完整性和一致性,避免出现恢复错误。
通过深入理解代理模式和备忘录模式的原理、应用场景和操作步骤,开发者可以在iOS开发中更加灵活地运用这两种模式,提高应用的性能和可维护性。在未来的开发中,我们可以根据具体的业务需求,进一步探索这两种模式的组合使用,以实现更加高效和复杂的功能。
超级会员免费看

被折叠的 条评论
为什么被折叠?



