IOS设计模式之三(适配器模式,观察者模式)

本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns.

由 @krq_tiger(http://weibo.com/xmuzyq)翻译,如果你发现有什么错误,请与我联系谢谢。

适配器(Adapter)模式

适配器可以让一些接口不兼容的类一起工作。它包装一个对象然后暴漏一个标准的交互接口。

如果你熟悉适配器设计模式,苹果通过一个稍微不同的方式来实现它-苹果使用了协议的方式来实现。你可能已经熟悉UITableViewDelegate,
UIScrollViewDelegate, NSCoding 和
NSCopying协议。举个例子,使用NSCopying协议,任何类都可以提供一个标准的copy方法。

如何使用适配器模式

前面提到的水平滚动视图如下图所示:

为了开始实现它,在工程导航视图中右键点击View组,选择New
File...使用iOS\Cocoa
Touch\Objective-C class 模板创建一个类。命名这个新类为HorizontalScroller,并且设置它是UIView的子类。

打开HorizontalScroller.h文件,在@end 行后面插入如下代码:

Objective-c代码  

  1. @protocolHorizontalScrollerDelegate <NSObject>

  2. // methods declaration goes in here

  3. @end

上面的代码定义了一个名为HorizontalScrollerDelegate的协议,它采用Objective-C 类继承父类的方式继承自NSObject协议。去遵循NSObject协议或者遵循一个本身实现了NSObject协议的类 是一条最佳实践,这使得你可以给HorizontalScroller的委托发送NSObject定义的消息。你不久会意识到为什么这样做是重要的。

在@protocol和@end之间,你定义了委托必须实现以及可选的方法。所以增加下面的方法:

Objective-c代码  

  1. @required
  2. // ask the delegate how many views he wants to present inside the horizontal scroller
  3. - (NSInteger)numberOfViewsForHorizontalScroller:(HorizontalScroller*)scroller;
  4. // ask the delegate to return the view that should appear at <index>
  5. - (UIView*)horizontalScroller:(HorizontalScroller*)scroller viewAtIndex:(int)index;
  6. // inform the delegate what the view at <index> has been clicked
  7. - (void)horizontalScroller:(HorizontalScroller*)scroller clickedViewAtIndex:(int)index;
  8. @optional
  9. // ask the delegate for the index of the initial view to display. this method is optional
  10. // and defaults to 0 if it‘s not implemented by the delegate
  11. - (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller*)scroller;

这里你既有必需的方法也有可选方法。必需的方法要求委托必须实现它,因为它提供一些必需的数据。在这里,必需的是视图的数量,指定索引位置的视图,以及用户点击视图后的行为,可选的方法是初始化视图;如果它没有实现,那么HorizontalScroller将缺省用第一个索引的视图。

下一步,你需要在HorizontalScroller类中引用新建的委托。但是委托的定义是在类的定义之后的,所以在类中它是不可见的,怎么办呢?

解决方案就是前置声明委托协议以便编译器(和Xcode)知道协议的存在。如何做?你只需要在@interface行前面增加下面的代码即可:

@protocolHorizontalScrollerDelegate;

继续在HorizontalScroller.h文件中,在@interface 和@end之间增加如下的语句:

Objective-c代码  

  1. @property (weak) id<HorizontalScrollerDelegate> delegate;

  2. - (void)reload;

这里你声明属性为weak.这样做是为了防止循环引用。如果一个类强引用它的委托,它的委托也强引用那个类,那么你的app将会出现内存泄露,因为任何一个类都不能释放调分配给另一个类的内存。

id意味着delegate属性可以用任何遵从HorizontalScrollerDelegate的类赋值,这样可以保障一定的类型安全。

reload方法在UITableView的reloadData方法之后被调用,它重新加载所有的数据去构建水平滚动视图。

用如下的代码取代HorizontalScroller.m的内容:

Objective-c代码  

  1. #import "HorizontalScroller.h"
  2. // 1
  3. #define VIEW_PADDING 10
  4. #define VIEW_DIMENSIONS 100
  5. #define VIEWS_OFFSET 100
  6. // 2
  7. @interfaceHorizontalScroller () <UIScrollViewDelegate>
  8. @end
  9. // 3
  10. @implementationHorizontalScroller
  11. {
  12. UIScrollView *scroller;
  13. }
  14. @end

让我们来对上面每个注释块的内容进行一一分析:

1. 定义了一系列的常量以方便在设计的时候修改视图的布局。水平滚动视图中的每个子视图都将是100*100,10点的边框的矩形.

2. HorizontalScroller遵循UIScrollViewDelegate协议。因为HorizontalScroller使用UIScrollerView去滚动专辑封面,所以它需要用户停止滚动类似的事件

3.创建了UIScrollerView的实例。

下一步你需要实现初始化器。增加下面的代码:

Objective-c代码  

  1. - (id)initWithFrame:(CGRect)frame
  2. {
  3. self = [super initWithFrame:frame];
  4. if (self)
  5. {
  6. scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
  7. scroller.delegate = self;
  8. [self addSubview:scroller];
  9. UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollerTapped:)];
  10. [scroller addGestureRecognizer:tapRecognizer];
  11. }
  12. return self;
  13. }

滚动视图完全充满了HorizontalScroller。UITapGestureRecognizer检测滚动视图的触摸事件,它将检测专辑封面是否被点击了。如果专辑封面被点击了,它会通知HorizontalScroller的委托。

现在,增加下面的代码:

Objective-c代码  

  1. - (void)scrollerTapped:(UITapGestureRecognizer*)gesture
  2. {
  3. CGPoint location = [gesture locationInView:gesture.view];
  4. // we can‘t use an enumerator here, because we don‘t want to enumerate over ALL of the UIScrollView subviews.
  5. // we want to enumerate only the subviews that we added
  6. for (int index=0; index<[self.delegate numberOfViewsForHorizontalScroller:self]; index++)
  7. {
  8. UIView *view = scroller.subviews[index];
  9. if (CGRectContainsPoint(view.frame, location))
  10. {
  11. [self.delegate horizontalScroller:self clickedViewAtIndex:index];
  12. [scroller setContentOffset:CGPointMake(view.frame.origin.x - self.frame.size.width/2 + view.frame.size.width/2, 0) animated:YES];
  13. break;
  14. }
  15. }
  16. }

Gesture对象被当做参数传递,让你通过locationInView:导出点击的位置。

接下来,你调用了numberOfViewsForHorizontalScroller:委托方法,HorizontalScroller实例除了知道它可以安全的发送这个消息给委托之外,它不知道其它关于委托的信息,因为委托必须遵循HorizontalScrollerDelegate协议。

对于滚动视图中的每个子视图,通过CGRectContainsPoint方法发现被点击的视图。当你已经找到了被点击的视图,给委托发送horizontalScroller:clickedViewAtIndex:消息。在退出循环之前,将被点击的视图放置到滚动视图的中间。

现在增加下面的代码去重新加载滚动视图:

Objective-c代码  

  1. - (void)reload
  2. {
  3. // 1 - nothing to load if there‘s no delegate
  4. if (self.delegate == nil) return;
  5. // 2 - remove all subviews
  6. [scroller.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  7. [obj removeFromSuperview];
  8. }];
  9. // 3 - xValue is the starting point of the views inside the scroller
  10. CGFloat xValue = VIEWS_OFFSET;
  11. for (int i=0; i<[self.delegate numberOfViewsForHorizontalScroller:self]; i++)
  12. {
  13. // 4 - add a view at the right position
  14. xValue += VIEW_PADDING;
  15. UIView *view = [self.delegate horizontalScroller:self viewAtIndex:i];
  16. view.frame = CGRectMake(xValue, VIEW_PADDING, VIEW_DIMENSIONS, VIEW_DIMENSIONS);
  17. [scroller addSubview:view];
  18. xValue += VIEW_DIMENSIONS+VIEW_PADDING;
  19. }
  20. // 5
  21. [scroller setContentSize:CGSizeMake(xValue+VIEWS_OFFSET, self.frame.size.height)];
  22. // 6 - if an initial view is defined, center the scroller on it
  23. if ([self.delegate respondsToSelector:@selector(initialViewIndexForHorizontalScroller:)])
  24. {
  25. int initialView = [self.delegate initialViewIndexForHorizontalScroller:self];
  26. [scroller setContentOffset:CGPointMake(initialView*(VIEW_DIMENSIONS+(2*VIEW_PADDING)), 0) animated:YES];
  27. }
  28. }

我们来一步步的分析代码中有注释的地方:

1. 如果没有委托,那么不需要做任何事情,仅仅返回即可。

2. 移除之前添加到滚动视图的子视图

3. 所有的视图的位置从给定的偏移量开始。当前的偏移量是100,它可以通过改变文件头部的#DEFINE来很容易的调整。

4. HorizontalScroller每次从委托请求视图对象,并且根据预先设置的边框来水平的放置这些视图。

5. 一旦所有视图都设置好了以后,设置UIScrollerView的内容偏移(contentOffset)以便用户可以滚动的查看所有的专辑封面。

6. HorizontalScroller检测是否委托实现了initialViewIndexForHorizontalScroller:方法,这个检测是需要的,因为这个方法是可选的。如果委托没有实现这个方法,0就是缺省值。最后设置滚动视图为协议规定的初始化视图的中间。

当数据已经发生改变的时候,你要执行reload方法。当增加HorizontalScroller到另外一个视图的时候,你也需要调用reload方法。增加下面的代码来实现后面一种场景:

Objective-c代码  

  1. - (void)didMoveToSuperview

  2. {
  3. [self reload];
  4. }

didMoveToSuperview方法会在视图被增加到另外一个视图作为子视图的时候调用,这正式重新加载滚动视图的最佳时机。

最后我们需要确保所有你正在浏览的专辑数据总是在滚动视图的中间。为了这样做,当用户的手指拖动滚动视图的时候,你将需要做一些计算。

再一次在HorizontalScroller.m中增加如下方法:

Objective-c代码  

  1. - (void)centerCurrentView
  2. {
  3. int xFinal = scroller.contentOffset.x + (VIEWS_OFFSET/2) + VIEW_PADDING;
  4. int viewIndex = xFinal / (VIEW_DIMENSIONS+(2*VIEW_PADDING));
  5. xFinal = viewIndex * (VIEW_DIMENSIONS+(2*VIEW_PADDING));
  6. [scroller setContentOffset:CGPointMake(xFinal,0) animated:YES];
  7. [self.delegate horizontalScroller:self clickedViewAtIndex:viewIndex];
  8. }

为了计算当前视图到中间的距离,上面的代码考虑了滚动视图当前的偏移量,视图的尺寸以及边框。最后一行代码是重要的,一当子视图被置中,你将需要将这种变化通知委托。

为了检测用户在滚动视图中的滚动,你必需增加如下的UIScrollerViewDelegate方法:

Objective-c代码  

  1. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
  2. {
  3. if (!decelerate)
  4. {
  5. [self centerCurrentView];
  6. }
  7. }
  8. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
  9. {
  10. [self centerCurrentView];
  11. }

scrollViewDidEndDragging:willDecelerate:方法在用户完成拖动的时候通知委托。如果视图还没有完全的停止,那么decelerate参数为true.当滚动完全停止的时候,系统将会调用scrollViewDidEndDecelerating.在两种情况下,我们都需要调用我们新增的方法去置中当前的视图,因为当前的视图在用户拖动以后可能已经发生了变化。

你的HorizontalScroller现在已经可以使用了。浏览你刚刚写的代码,没有涉及到任何与Album或AlbumView类的信息。这个相对的棒,因为这意味着这个新的滚动视图是完全的独立和可复用的。

构建的工程确保每个资源可以正确编译。

现在HorizontalScroller完整了,是时候去在app使用它了。打开ViewController.m 增加下面的导入语句:

Objective-c代码  

  1. #import "HorizontalScroller.h"

  2. #import "AlbumView.h"

增加HorizontalScrollerDelegate协议为ViewController遵循的协议:

Objective-c代码  

  1. @interfaceViewController ()<UITableViewDataSource, UITableViewDelegate, HorizontalScrollerDelegate>

在类的扩展中增加下面的实例变量:

HorizontalScroller *scroller;

现在你可以实现委托方法;你可能会感到惊讶,因为只需要几行代码就可以实现大量的功能啦。

在ViewController.m中增加下面的代码:

Objective-c代码  

  1. #pragma mark - HorizontalScrollerDelegate methods
  2. - (void)horizontalScroller:(HorizontalScroller *)scroller clickedViewAtIndex:(int)index

  3. {

  4. currentAlbumIndex = index;
  5. [self showDataForAlbumAtIndex:index];
  6. }

它设置保存当前专辑数据的变量,然后调用showDataForAlbumAtIndex:方法显示专辑数据。

注意:在#pragma mark 指令后面写方法代码是一种通用的实践。c
编译器会忽略调这些行,但是如果你通过Xcode的弹出框的时候,你将看到这些指令会帮你把代码组织成有独立和粗体标题的组。这可以帮你使得你的代码更方便在Xcode中导航。

接下来,增加下面的代码:

Objective-c代码  

  1. - (NSInteger)numberOfViewsForHorizontalScroller:(HorizontalScroller*)scroller

  2. {

  3. return allAlbums.count;

  4. }

正如你意识到的,这个是返回滚动视图所有子视图数量的协议方法。因为滚动视图要显示所有专辑的封面,这个数量就是专辑记录的数量。

现在,增加下面的代码:

Objective-c代码  

  1. - (UIView*)horizontalScroller:(HorizontalScroller*)scroller viewAtIndex:(int)index

  2. {
  3. Album *album = allAlbums[index];
  4. return [[AlbumView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) albumCover:album.coverUrl];
  5. }

这里你创建了一个新的AlbumView,并且将它传递给HorizontalScroller。

够了,仅仅三个简短的方法就可以显示一个漂亮的水平滚动视图。

是的,你任然需要创建滚动视图,并且把它增加到你的主视图中,但是在这样做之前,你增加下面的方法先:

Objective-c代码  

  1. - (void)reloadScroller
  2. {
  3. allAlbums = [[LibraryAPI sharedInstance] getAlbums];
  4. if (currentAlbumIndex < 0) currentAlbumIndex = 0;
  5. else if (currentAlbumIndex >= allAlbums.count) currentAlbumIndex = allAlbums.count-1;
  6. [scroller reload];
  7. [self showDataForAlbumAtIndex:currentAlbumIndex];
  8. }

这个方法通过LibraryAPI加载专辑数据,然后根据当前视图的索引设置当前显示的视图。如果当前的视图索引小于0,意味着当前没有选定任何视图,此时可以选择第一个专辑来显示,否则下面一个专辑将会显示。

现在在viewDidLoad的[self
showDataForAlbumAtIndex:0]之前增加下面的代码来初始化滚动视图:

Objective-c代码  

  1. scroller = [[HorizontalScroller alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 120)];
  2. scroller.backgroundColor = [UIColor colorWithRed:0.24f green:0.35f blue:0.49f alpha:1];
  3. scroller.delegate = self;
  4. [self.view addSubview:scroller];
  5. [self reloadScroller];

上面的代码简单的创建了一个HorizontalScroller类的实例,设置它的背景色,委托,增加它到主视图,然后加载所有子视图去显示专辑数据。

注意:如果一个协议变得特别冗长,包含太多的方法。你应该考虑将它氛围更家细粒度的协议。UITableViewDelegate 和
UITableViewDataSource是一个好的例子。因为它们都是UITableView的协议。试着设计你的协议以便每个协议都关注特定的功能。

构建并运行你的on过程,查看一下你帅气十足的水平滚动视图吧:

对了,等等。水平滚动视图没问题,但是为什么没有显示封面呢?

是的,那就对了-你还没有实现下载封面的代码。为了实现这个功能,你需要去新增一个下载图片的方法。因为所有对服务的访问都通过LibraryAPI,那我们就可以在LibraryAPI中实现新的方法。然而我们首先需要虑一些事情:

1. AlbumView不应该直接和LibraryAPI交互。你不想混淆显示逻辑和网络交互逻辑。

2. 同样的原因,LibraryAPI也不应该知道AlbumView。

3. 一旦封面已经下载,LibraryAPI需要通知AlbumView,因为AlbumView显示专辑封面。

听上去是不是挺糊涂的?不要灰心。你将学习如何使用观察者模式来实现它。

观察者(Observer)模式

在观察者模式中,一个对象任何状态的变更都会通知另外的对改变感兴趣的对象。这些对象之间不需要知道彼此的存在,这其实是一种松耦合的设计。当某个属性变化的时候,我们通常使用这个模式去通知其它对象。

此模式的通用实现中,观察者注册自己感兴趣的其它对象的状态变更事件。当状态发生变化的时候,所有的观察者都会得到通知。苹果的推送通知(Push Notification)就是一个此模式的例子。

如果你要遵从MVC模式的概念,你需要让模型对象和视图对象在不相互直接引用的情况下通信。这正是观察者模式的用武之地。

Cocoa通过通知(Notifications)和Key-Value
Observing(KVO)来实现观察者模式。

通知(Notifications

不要和远程推送以及本地通知所混淆,通知是一种基于订阅-发布模式的模型,它让发布者可以给订阅者发送消息,并且发布者不需要对订阅者有任何的了解。

通知在苹果官方被大量的使用。举例来说,当键盘弹出或者隐藏的时候,系统会独立发送UIKeyboardWillShowNotification/UIKeyboardWillHideNotification通知。当你的应用进入后台运行的时候,系统会发送一个UIApplicationDidEnterBackgroundNotification通知。

注意:打开UIApplication.h,在文件的末尾,你将看到一个由系统发出的超过20个通知组成的列表。

如何使用通知(Notifications

打开AlbumView.m,在initWithFrame:albumCover::方法的[self addSubview:indicator];语句之后加入如下代码:

Objective-c代码  

  1. [[NSNotificationCenterdefaultCenter] postNotificationName:@"BLDownloadImageNotification"

  2. object:self

  3. userInfo:@{@"imageView":coverImage, @"coverUrl":albumCover}];

这行代码通过NSNotificationCenter单例发送了一个通知。这个通知包含了UIImageView和需要下载的封面URL,这些是你下载任务所需要的所有信息。

在LibraryAPI.m文件init方法的isOnline=NO之后,增加如下的代码:

Objective-c代码  

  1. [[NSNotificationCenterdefaultCenter] addObserver:self selector:@selector(downloadImage:) name:@"BLDownloadImageNotification" object:nil];

这个是观察者模式中两部分的另外一部分:观察者。每次AlbumView发送一个BLDownloadImageNotification通知,因为LibraryAPI已经注册为同样的通知的观察者,那么系统就会通知LibraryAPI,LibraryAPI又会调用downloadImage:来响应。

然而在你实现downloadImage:方法之前,你必须在你的对象销毁的时候,退订所有之前订阅的通知。如果你不能正确的退订的话,一个通知发送给一个已经销毁的对象会导致你的app崩溃。

在Library.m中增加下面的代码:

Objective-c代码  

  1. - (void)dealloc

  2. {

  3. [[NSNotificationCenterdefaultCenter] removeObserver:self];
  4. }

当对象被销毁的时候,它将移除所有监听通知的观察者。

还有一件事情需要去做,将已经下载的封面图片本地存储起来是个不错的主意,这样可以避免每次都重新下载相同的封面。

打开PersistencyManager.h文件,增加下面两个方法原型:

Objective-c代码  

  1. - (void)saveImage:(UIImage*)image filename:(NSString*)filename;
  2. - (UIImage*)getImage:(NSString*)filename;

在PersistencyManager.m文件中,增加方法的实现:

Objective-c代码  

  1. - (void)saveImage:(UIImage*)image filename:(NSString*)filename
  2. {
  3. filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename];
  4. NSData *data = UIImagePNGRepresentation(image);
  5. [data writeToFile:filename atomically:YES];
  6. }
  7. - (UIImage*)getImage:(NSString*)filename
  8. {
  9. filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename];
  10. NSData *data = [NSDatadataWithContentsOfFile:filename];
  11. return [UIImage imageWithData:data];
  12. }

上面的代码相当直接。下载的图片会被保存在文档(Documents)目录,如果在文档目录不存在指定的文件,getImage:方法将返回nil.

现在在LibraryAPI.m中增加下面的方法:

Objective-c代码  

  1. - (void)downloadImage:(NSNotification*)notification
  2. {
  3. // 1
  4. UIImageView *imageView = notification.userInfo[@"imageView"];
  5. NSString *coverUrl = notification.userInfo[@"coverUrl"];
  6. // 2
  7. imageView.image = [persistencyManager getImage:[coverUrl lastPathComponent]];
  8. if (imageView.image == nil)
  9. {
  10. // 3
  11. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  12. UIImage *image = [httpClient downloadImage:coverUrl];
  13. // 4
  14. dispatch_sync(dispatch_get_main_queue(), ^{
  15. imageView.image = image;
  16. [persistencyManager saveImage:image filename:[coverUrl lastPathComponent]];
  17. });
  18. });
  19. }
  20. }

下面是以上代码分段描述:

1.     
downloadImage方法是通过通知被执行的,所以通知对象会当作参数传递。UIImageView和图片URL都会从通知中获取。

2.     
如果图片已经被下载过了,直接从PersistencyManager方法获取。

3.     
如果图片还没有被下载,通过HTTPClient去获取它。

4.     
当图片下载的时候,将它显示在UIImageView中,同时使用PersistencyManager保存到本地。

再一次,你使用了门面(Facade)模式隐藏了下载图片的复杂性。通知的发送者不需要关心图片是来自网络还是来自本地文件系统。

构建并运行你的应用,看看那些在滚动视图中的漂亮封面吧:

停止你的应用再一次运行它,你会注意到不会存在加载图片的延迟,因为它们都已经被保存到了本地。甚至你可以断开网络,你的应用也可以完美地运行。然而这里有点奇怪,图片上的提示转盘一直在转动,出了什么问题呢?

当开始下载图片的时候,你启动了提示图片正在加载的旋转提示器,但是你还没有实现图片下载完成后停止它的逻辑。你应该在每次图片下载完成的时候发送一个通知,但是这里你使用KVO这种观察者模式。

Key-Value
Observing(KVO)
模式

在KVO中,一个对象可以要求在它自身或者其它对象的属性发送变化的时候得到通知。如果你对KVO感兴趣的话,你可以更进一步的阅读这篇文章:Apple’s KVO Programming
Guide
.

如何使用KVO

正如上面所说的,KVO机制让对象可以感知到属性的变化。在本例中,你可以使用KVO去观察UIImageView的image属性的变化。

打开AlbumView.m文件,在initWithFrame:albumCover:方法[self addSubview:indicator]这一行后,增加下面的代码:

Objective-c代码  

  1. [coverImage addObserver:self forKeyPath:@"image" options:0 context:nil];

这里它增加了它自己(当前的类)作为image属性的观察者。

当完成的时候,你同样需要注销相应的观察者。仍然在AlbumView.m中增加下面的代码:

Objective-c代码  

  1. - (void)dealloc

  2. {

  3. [coverImage removeObserver:self forKeyPath:@"image"];
  4. }

最后增加下面的方法:

Objective-c代码  

  1. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  2. {
  3. if ([keyPath isEqualToString:@"image"])
  4. {
  5. [indicator stopAnimating];
  6. }
  7. }

你必须在每个观察者类中实现这个方法。系统会在被观察的属性发送变化的时候通知观察者。在上面的代码中,当image属性变化的时候,你停止了封面上面的旋转提示器。这样以来,当图片加载完后,旋转提示器将会停止。

构建并运行的你的工程。旋转提示器应该会消失:

注意:你要总是记得去移除已经销毁的观察者,否则当给不存在的观察者发送消息的时候,你的应用可能会崩溃。

如果你玩一回你的应用后终止它,你会发现你的应用状态没有被保存,你上次查看的专辑不是下次启动时候的缺省专辑。

为了修正这个问题,你可以使用列表中的下个模式:备忘录(Memento)模式.

IOS设计模式之三(适配器模式,观察者模式),布布扣,bubuko.com

时间: 2024-08-03 11:29:42

IOS设计模式之三(适配器模式,观察者模式)的相关文章

IOS设计模式之三:MVC模式

IOS设计模式之三:MVC模式 提到ios中的mvc不得不提2011秋季斯坦福课程的老头,他的iphone开发公开课是所有描述ios中mvc模式最为准确并且最为浅显易懂的. 模型-视图-控制器 这个模式其实应该叫做MCV,用控制器把model与view隔开才对,也就是model与view互相不知道对方的存在,没有任何瓜葛,他们就像一个团队里吵了架的同事,如果有项目需要他俩来参与,那么最好有第三者来管理他俩之间的沟通与协调.这个第三者就是控制器. 既然管理,那么姑且就把这个控制器提做项目经理吧,这

iOS设计模式之适配器模式

一,适配器的定义 定义 将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 需求场景 需要使用以前开发的“一些现存的对象”,但是新环境中要求的接口是这些现存对象所不满足的 二,适配器的结构图 实现步骤: 定义接口,规范适配器的功能 定义适配器父类,便于各个子类的对于接口功能的实现 子类适配器继承父类适配器,实现子类下的接口功能 父类指针指向子类,调用子类的实现方法 结构图: 三,代码示例 接口协议 CellPhoneProtocol.h

IOS设计模式之一(MVC模式,单例模式)

本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns. 由 @krq_tiger(http://weibo.com/xmuzyq)翻译,如果你发现有什么翻译错误,请与我联系谢谢. iOS 设计模式-你可能已经听说过这个词,但是你真正理解它意味着什么吗?虽然大多数的开发者可能都会认为设计模式是非常重要的,然而关于设计模式这一主题的文章却不多,并且有时候我们开发者在写代码的时候也不会太关注它. 在软件设计领域,设计模式是对通用问题

IOS设计模式-观察者模式

前言:23种软件设计模式中的观察者模式,也是在软件开发中,挺常用的一种设计模式.而在苹果开发中,苹果Cocoa框架已经给我们实现了这个设计模式,那就是通知和KVO(Key-Value Observing),本篇博文将会先讲解通知和KVO的常用方法和使用示例,然后讲解观察者模式以及对观察者模式的实现,最后来一个自己实现cocoa框架的KVO. 文章内容大纲: 1. 2. 3. 正文:

iOS 设计模式

Ios 设计模式,你可能听说过,但是你真正知道这是什么意思么?大部分的开发者大概都同意设计模式很重要,但是关于这一部分却没有很多的文章去介绍它,我们开发者很多时候写代码的时候也并不重视设计模式. 设计模式是在软件设计上去解决普通问题的可重用的方法.他们是是帮助你让所写的代码更加容易理解和提高可重用性的模板.它们还可以帮你创建松散耦合的代码是你能不费很大功夫就可以改变或者替代你的代码中的一部分. 如果你对设计模式感到生疏,那么我有个好消息告诉你!首先,你已经用了很多ios设计模式多亏了Cocoa

iOS设计模式 - 概述

最近可自由安排的时间比较多, iOS应用方面, 没什么好点子, 就先放下, 不写了.花点时间学学设计模式. 之后将会写一系列博文, 记录设计模式学习过程. 当然, 因为我自己是搞iOS的, 所以之后设计代码部分, 将尽量与objective-c相结合. iOS设计模式 - 概述           by Colin丶 转载请注明出处:              http://blog.csdn.net/hitwhylz/article/details/40372113 一.定义 设计模式(Des

IOS设计模式之四(备忘录模式,命令模式)

本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns. 由 @krq_tiger(http://weibo.com/xmuzyq)翻译,如果你发现有什么错误,请与我联系谢谢. 备忘录(Memento)模式 备忘录模式快照对象的内部状态并将其保存到外部.换句话说,它将状态保存到某处,过会你可以不破坏封装的情况下恢复对象的状态,也就是说原来对象中的私有数据仍然是私有的. 如何使用备忘录模式 在ViewController.m中增加

iOS设计模式

iOS设计模式,很多开发这都是听得多,但是有时候自己即使用过也不会很在意,开发者在写代码的时候也不会注意它. 在软件设计领域,设计模式是对通过问题的可复用的解决方案.设计模式是一系列帮你写出可理解和复用的模板,设计模式帮你创建松耦合的代码,你不需要花费太多就可以改变或者替换代码中的组件. (1)代理模式 应用场景:当一个类的某些功能需要由其他别的类别来实现的,但是又不确定是哪个类 优势:松耦合 实例:tableView的数据源delegate,通过和protocol的配合,完成委托. 列表row

设计模式之适配器模式(Adapter)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以