26、iOS开发中的代理与备忘录模式解析

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开发中更加灵活地运用这两种模式,提高应用的性能和可维护性。在未来的开发中,我们可以根据具体的业务需求,进一步探索这两种模式的组合使用,以实现更加高效和复杂的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值