Ios 设计模式,你可能据说过,可是你真正知道这是什么意思么?大部分的开发者大概都赞成设计模式很重要,可是关于这一部分却没有不少的文章去介绍它,咱们开发者不少时候写代码的时候也并不重视设计模式.ios
设计模式是在软件设计上去解决普通问题的可重用的方法.他们是是帮助你让所写的代码更加容易理解和提升可重用性的模板.它们还能够帮你建立松散耦合的代码是你能不费很大功夫就能够改变或者替代你的代码中的一部分.objective-c
若是你对设计模式感到生疏,那么我有个好消息告诉你!首先,你已经用了不少ios设计模式多亏了Cocoa 内建的方法。其次,这个教程会带你加快对在Cocoa中最经常使用的ios设计模式的认识。设计模式
这个教程会分割成几部分,每一部分都是一个设计模式,在每个部分,你都会读到如下内容:这是什么设计模式,为何使用这个设计模式,怎么使用这个设计模式,当使用这个设计模式时要注意的常见的陷阱。api
在这个教程中,你会建立一个音乐库app-会显示专辑和他们相应的信息。数组
在开发这款app的过程当中,你将会熟悉如下Cocoa中最多见的设计模式。xcode
1建立类型的:单例模式,抽象工厂模式安全
2结构化类型的:MVC, Decorator, Adapter, Facade and Composite服务器
3行为类型的:Observer, Memento, Chain of Responsibility andCommand。网络
不要觉得这个是关于理论的一篇文章,你将会学到如何使用这些设计模式的大部分去建立这款app。在这个教程结束后你的app将会是这个样子:数据结构
开始:
下载开始工程,这只是默认的ViewController和一个简单的包含空的实现文件的HTTP客户端。
常识:你知道当你建立一个空的Xcode文件工程那你的代码已经充满了设计模式?MVC,Delegate,Protocol,Singleton。
在你涉足到第一个设计模式时,你要建立两个类去hold和展现专辑的数据。
按住Command + N键去建立一个名为Album的object-c文件,继承于NSObject。打开Album.h文件,加入如下的属性和方法。
注意到这些属性是只读的,由于当Album建立之后没有必要去改变他们。
这个方法是这个对象的初始化方法,当你建立了一个专辑,你会传给它专辑的名字,艺术家的名字,专辑封面的URL和专辑的年份。
打开Album.m而后将下面代码添加到 @implementation和@end之间。
这里没有什么神奇的; 只是一个简单的init方法来建立新Album的实例。
再次,导航到文件\新建\文件...。选择Cocoa Touch,而后Objective-C类,而后单击下一步。将类名AlbumView,但此次设定的子类UIView的。单击下一步,而后建立。
打开ALbumView.h文件,在imlementation.h中添加如下代码:
在这里你须要注意的第一件事情是有一个实例变量名为coverImage,它表明了专辑的封面图片,第二个变量是indicator,它的旋转去指示图片正在被下载。
在实现部分的初始化中将背景设为黑色,建立的image view有5像素边缘,而后将指示器indicator添加到试图中。
小贴士:为何将私有变量定义在实现部分而不是接口部分?这是由于外部的类不须要知道这些变量的存在由于它们只是在这个类中的实现部分被使用。这个约定是十分重要的,若是你在建立一个库或者框架给其余开发者使用。
编译你的工程(Comamand +B)确保没有问题,接下来准备好去接受你的第一个设计模式吧。
MVC -设计模式之王
Model View Controller 是Cocoa的基石之一,且毫无疑问的是全部设计模式中最经常使用的设计模式,它根据你的应用中的通常角色去分类对象,鼓励在彻底分离的模式下分角色。
Model:这个对象hold住你的应用数据,且定义如何去操做它,例如本例中就是Album类。
View:这个对象掌管了Model的可视化显示,和控制用户的交互,基本上全部的都是UIView和它的子类。在本例中这个就是被分离成的AlbumView类。
Controller:控制器是调节全部工做的调节器,它访问模型中的数据,而后用视图去显示它,根据要求监听事件和操做数据。你能想象在这个哪一个是Controller么,就是ViewController.
视图和模型经过控制器去交流的场景能够被描述成如下图:
若是在Model中有任何数据变化,那么它就会通知Controller,反过来,Controller更新在View中的数据,View能够通知Controller关于用户的行为,而后Controller要么根据须要或者检索要求的数据去更新Model。
你也许会怀疑为何不仅是建立了Controller而后将View和Model一块儿放到里面去实现?那样看起来不是更容易么?
这全部的全部都是为了是代码分离化和提升可重用性。理想状况下,视图应该会从Model中彻底分离出来,若是视图不依赖于某个具体的Model的实现部分,那么它能够用不一样的Model去展现其余一些数据来实现它的可重用性。
例如:若是未来你想添加一些电影和书籍到你的库中去,你仍然可使用相同的AlbumView去展现你的电影和书籍的对象,更进一步说,若是你想去建立一个工程去处理专辑,你能够很简单的去重用你的Album类,由于它不依赖于任何一个视图。这就是MVC的魔力。
首先,你须要确保你的工程中的每个类都是Controller,或者View,或者Model,不要讲任何两个中的角色的任务链接在一块,经过建立Album和AlbumV类你已经作了一个很好的工做。
其次,为了确保遵照这个工做方法,你应该建立三个工程组去hold住你的代码,每类一个分组。
按住Command+option+N键,建立一个组,名为Model,一样建立View和Controller,将Album.h和Album.m拖入Model中,拖动AlbumView.h和AlbumView.m的视图组,最后拖ViewController.h和ViewController.m到控制器组.
这时候你的工程结构应该看起来是这样的:
如今看起来没有那些文件浮在四周,看着好多了。显然你能够有其余的组和类,可是这个应用中的核心就是包含在这三个类中的。
既然你的组成部分已经被组织起来了,你须要从别的地方去得到album的数据,你将会建立一个API类去在所有的代码中去管理这些数据-这将会在你的下一个设计模式-单例中获得展现。
单例模式确保为一个肯定的类只有一个实例存在,并且有一个全局的访问指针只想它,他常用延时加载去在第一次使用的时候建立一个简单的实例。
小贴士:苹果使用这个方法很频繁。好比:[NSUserDefaults standarUserDefaults], [UIApplicationsharedApplication],[UIScreen mainScreen],[NSFileManager defaultManager],都返回一个单例。
你可能会觉得为何你会介意一个类周围会有不止一个实例,代码和内存都很简化,对不对?
有一些状况下一个类只有一个实例会颇有意义。好比:没有必要去有不少的Logger实例,除非你想要同时写入几个日记文件,或者,拥有一个全聚德配置控制的类,很容易就实现一个线程安全的单个共享资源,好比配置文件,而不是多个类同时修改同一个配置文件。
看一下右边的这个图表:
它显示了一个Logger类只有一个属性,(也是一个实例),和两个方法init和sharedInstance;
第一次客户端发送sharedInstance消息,这和属性的实例尚未初始化,因此你要建立这个类的一个新的实例而后返回一个指针指向它。
下一次你调用sharedInstance方法,实例就会立马返回而不须要任何的初始化,这个逻辑保证任什么时候候都只有一个实例存在。
你将要实现这和模式经过建立一个单例的类去管理全部的album 数据。
你将会意识到在这个组中有一个叫作API的租在这个工程中,这是你将会将全部的提供给你的app服务的类放到其中。在这个组中建立一个叫作LibraryAPI的类,继承于NSObject。
打开LibraryAPI.h,加入如下方法:
在LibraryAPI.m中加入这些方法:
这个简单的方法中有不少内容:
1. 定义了一个静态变量去hold住类中的实例,确保它是全局可用的在你的类中。
2. 定义了一个静态变量dispatch_once_t类型的去确保这个初始化代码只执行一次。
3. 使用Grand Central Dispatch(GCD)去执行一个初始化LibraryAPI的实例的block,这就是单例设计模式的本质:这个初始化永远不会再次被调用一旦这个类被实例化。
下次你调用sharedInstance方法是,这个dispatch_once里面的block代码不会被执行了,你会获得一个先前建立这个LibraryAPI的一个引用。
如今你有了一个单例对象做为albums的入口指针,更进一步去建立一个类去处理库中数据的持久化。
在API组中建立一个PersistencyManager的继承于NSObject的类。打开它的.h文件,加入
#import “Album.h”再加入如下代码:
以上是加入了三个方法去处理数据的。
打开.m文件在 @implemetation的上方加入这些代码:
上面的代码添加了一个类扩展,这是另外一个添加私有变量和方法带类中因此外部的类并不仅带他们,在这里,你声明了一个可变数组去hold住album的数据,这个数据是可变的以便你能够轻松地添加和删除专辑。
如今添加下面代码到PersistencyManager.m中去
在init这个初始化函数中你用五个实例转会填充了这个数组,其余几个方法容许你去获得,添加额删除albums。编译你的工程确保仍能正确编译。
在这个时候你可能会疑问为何PersistenceManager不是单例,它和LibraryAPI的关系会下下一部分的外观(Façade)设计模式中见到。
外观设计模式向复杂的子系统提供了简单的接口,相比将一系列的类和他们的接口暴露给用户,你只须要暴露一些简单的未定义的API。
接下来的图片解释了这一律念。
使用这些API接口的人彻底没有意识到你这下面隐藏的复杂性,在有一系列类,特别是他们使用很复杂或者难以理解的时候,这个模式是很是好的。
外观设计模式使用从接口层面去使用,在实现技术上隐藏而将代码解藕了。它也减小了你外部的代码对于内部子系统代码的依赖性。它在外观模式可能要进行改变的状况下也是颇有用的,由于外观的类仍然能够保持相同的API当背后的状况发生了变化时。好比,有一天你想改变背后的服务代码,你不用去改变这些代码由于这些API不会改变。
目前你有PersistencyManager类去本地保存album的数据,而HTTPClient去处理远程的数据交流。工程里面的其余代码不该该意识到这个逻辑。
要实现这个LiabraryAPI你应该hold住PersistencyManager和HTTPClient的一个实例。而后LiabraryAPI会暴露一个简单的接口去访问这些服务。
小贴士:一般一个单例会在app的整个生命周期都会存在,你不该该持有过多的单例指针指向其余物体,由于它们在app关闭以前不会被释放。这个设计应该是像下面的这个图这样。
LiabraryAPI 会暴露给其余代码,可是会隐藏PersistenceManager和HTTPClient针的复杂性。
打开LiabraryAPI.h,添加#import “album.h”,接下来添加这些方法的人声明到里面。
从如今开始,这些方法将会被你暴露给外部使用。
打开LiabraryAPI.m文件。加入
这是你导入这些类惟一的地方,记住:你的复杂的系统只能由你的API惟一的访问。如今,添加这些私有变量经过类扩展。(在@implementation上方)。
isOnline决定了服务器石油应该根据album表中的变化进行更新,例如添加或者删除albums.
你须要在init里面对它们进行初始化。在LiabraryAPI.m文件中添加下面代码:
HTTPClient这个并不会真正的和一个服务器配合工做,在这里只是为了演示外观设计模式,因此isOnline老是NO。接下来在LiabraryAPI.m文件中添加这些方法。
看一下这个
方法,这个类首先本地更新数据,而后若是由网络链接,就远程更新,这就是外观模式的魅力,若是在你系统之外的类加入了一些新的album,它不知道,也不须要去知道,这些复杂性都被隐藏在下面了。
提醒:当为你的子系统中的类设计一个外观模式,记住没有什么作什么去阻止客户端去直接访问这些隐藏的属性,不要假设全部的外部客户端都须要向你在外观模式下使用它们的方法那样去使用它们。
编译和运行你的程序,你会看见以下的空白的黑色屏幕。
你须要作点事情去在屏幕上去显示album的数据-那就是接下来的下一个设计模式:装饰设计模式。Decorator.
装饰设计模式动态的添加一些行为和任务到一个对象中且不须要去修改它的代码。固然你也能够选择用继承的方式-经过包装成另外一个对象去改变它的行为。
在objective-c中由两个很是经常使用的实现方式:分类和代理。(Category, Delegate)
分类是一种很是有用的机制,它容许你去添加一些方法到已经存在的类中且不用去继承它。这些新方法会在编译的时候添加上去,且能够像这个被扩展的类中的其余方法同样被执行。它和典型的装饰设计模式由一点轻微的不一样,由于它并不hold住它所扩展的类的实例。
小贴士:除了向你本身的类去添加方法,你还能够向cocoa中的类去添加方法。
想像这是你想要在tableView中显示album数据的一种方案。
这些album titles从哪里来?Album是一个模型对象,因此它并不在乎你是怎么样显示数据的,你将要须要一些额外的代码去将这些功能添加到Album类中去,可是不要直接去修改这些类。
你将要建立一个分类去扩展Album。它要定义一个新的方法去返回一个数据结构来被UITableViews轻松的使用。这些数据结构看起来应该是这样的。
为了添加一个分类到Album中,新建一个文件选择Objective-C category 模版,在category field中键入TableRepresentation,在Category On上写入Album。
在Album+TableRepresentation.h加入如下方法的声明。
注意到这里有一个tr_在方法名的前面,就像一个分类的名字的前缀。这样的命名约定有利于防止和其余方法冲突。
注意:若是在分类中声明的方法名字和在原来的类中的方法名字同样,或者和另外一个类扩展中的方法名字同样,那么就会显示未定义,由于这些方法的实现是在运行时,若是你用分类去扩展你本身定义的类,那么出现问题的几率不大,可是若是你用分类添加方法到Cocoa或者CocoaTouch的类中,那有可能会产生很严重的问题。
在Album+TableRepresentation.m添加下面的方法
想一下这个设计模式有多强大:
1你能够直接使用Album中的属性。
2 你能够不继承就能够向一个类添加方法。固然若是你想继承的话,也能够继承。
3 它能够简单的让你返回一个UITableView-ish的Album的显示类型,而不用修改Album的代码。
苹果公司使用分类在不少在基础的类上。要想去看他们是怎样实现的,能够打开NSString.h,找到@interface NSString ,那你将会看到这个类的定义和一下三个分类牢牢联系在一块儿:
NSStirngExtensionMethods, NSExtendedStringPropertyListParsing,NSStringDeprecated。分类把这些方法有序的组织起来而又分红几部分。
另外一个装饰设计模式就是代理,就是一个对象能够表明或者协助另外一个对象的一种机制。例如,当你使用UITableView,其中你必须实现的方法就是tableView:number numberOfRowsInSection.
你不能期望UITableView去知道你想在每个分区里面有多少行。所以,计算每一个分区有多少行的任务就交给了UITableView的代理。这让UITableView能够和它要显示的数据进行独立
UITableView 对象的工做是显示一个table view,而后最终它仍是须要一些它根本没有拥有的数据。那么,它会向它的代理去发送一条消息去请求一些额外的信息。在Objective-C中的代理设计模式的实现中,一个类能够经过协议protocol声明必须的required或者可选的optional的方法。你将会实现这些协议在这个教程的后半部分。
彷佛看起来经过继承一个对象而后去重写它的须要的方法更容易,凡事考虑到你你只能继承一个类。若是你向一个对象成为两个或者更多对象的代理,那你不能经过继承去实现这个目标。
小贴士:这是一个很重要的模式,苹果公司使用这个方法在大部分的UIKit的类中:UITableView,UITextView,UITextField,UIWebView,UIAlert,UIAction,UICollectionView,UIPickerView,UIGestureRecognizer,UIScrollerView等等。
在ViewController.m中加入导入这些文件的头文件。
利用类扩展去添加这些私有变量。
而后在@interfaceViewController ()加上 <UITableViewDataSource,UITableViewDelegate>
这是你怎样使你的代理听从一个协议-想像它是一个被代理去完成方法的协议的一个约定,在这里你让ViewController去遵照UITableViewDataSource和UITableViewDelegate协议,这个方法使得UITableView能够绝对保证这些必须方法会被它的代理所实现。
接下来,在viewDidLoad中加入这些代码:
这里是这些代码的讲解:
1首先将背景颜色改为了一个相对友好的背景色。
2经过API而不是PersistencyManager获得了albums的列表。
3在这里建立了UITableView,你声明了这个控制器是UITableView的delegate/dataSource,所以UITableView的全部必须的方法都会由控制器提供。
而后添加这个方法在控制器实现代码中:
showDataForAlbumAtIndex:从albums这个数组中获取了所需答album的数据。而后你之须要去调用reloadData.这会让UITableView询问它的代理诸如在table view的每一个分区中显示多少行,多少个分区,每一行应该怎么样之类的事情。
在viewDidLoad的结尾处加上[self showDataForAlbumAtIndex:currentAlbumIndex];
这会在应用启动的时候load如今的album,而后由于currentAlbumIndex原先被初始化为0,因此只是在会显示第一个album。
编译运行你的工程,你会遇到一个崩溃伴随一个异常的显示在调试控制台。
这怎么了?由于你声明了控制器成为UITableView的delegate和dataSource,可是这样的话你必须遵照去实现它的必须的方法包括numberOfRowsInSection这个你还没实现的方法。向ViewController.m中加入这两个方法。
前一个方法返回在table view中要显示的行数,在这里和数据结构中的titles的数量相同。后者建立并返回了一个带有title和value的cell。
编译和运行工程,你的应用应该是向这样展现在你的面前。
到目前为止,事情好像看起老很棒,可是你若是你调用第一张图片去展现启动后的app,那将会由一个水平的滚动条在屏幕的albums转换之间的顶部。与其建立一个单一用途的水平滚动条,为何不使它成为一个通用的视图。
为了是这个视图可重用,全部的关于它的内容的决定都该留给另外一个对象-它的代理。这个horizontal scroller应该定义一些方法让它的代理去实现以致于去和scroller一块儿工做,和UITableViewde的代理方法想相似。咱们将会在下一个设计模式中去讨论这个设计模式。
适配器模式让不一样的类之间的不兼容的接口能够一块儿工做。它将本身包装成一个对象,而后暴露一个标准的接口去让外界和这个对象去交互。
若是你对适配器模式熟悉,那么你会注意到苹果用一个稍微不一样的方法去实现它-苹果使用协议去作这个工做,你也许会熟悉像UITableViewDelegate,UIScrollViewDelegate,NSCoding,NSCopying这样的协议,例如,经过NSCopying协议,任何的类均可以提供一个标准的copy方法。
这个之间提到的horizontal scroller应该是像下面的这个图这样子。
首先新建一个Objective-C的类,让它继承于UIView,打开它的.h文件,在@end的下面写上这行代码。
这定义了一个名为HorizontalScrollerDelegate的协议,继承于 NSObject协议。这是一个很好的实践去听从NSObject协议-或者去听从一个已经听从NSObject协议的协议,这会让你能够向HorizontalScroller的代理对象发送NSObject中定义的消息。你将会看到这为何是那么的重要。
你要定义它的代理必须和可选实现的方法在@protocol和@end之间
在这里你既有可选的也有必须的方法,必须的方法必须被代理实现,而一般这回包含一些这个类绝对须要的数据。在这个例子中,这些必须的分别是视图的数量,在特定的位置的视图和当一个视图被点击后的行为。这个可选的方法是初始化的使用,若是它不被代理所实现,那么默认就是第一个视图。
接下来,你须要将这个类的定义饮用到你的代理中去。可是这个协议的定义是在累的定义之下的,所以还剩不可见的,那你该怎么办呢。
解决的办法奇偶说前向的定义一个协议来让编译器去知道这样一个协议是可用的,因此,添加这行代码在@interface的上方。
而后在@interface和@end之间下入以下代码。
这个代理的属性是weak类型的,为了防止循环引用这是必须的,若是一个类持有一个强指针指向它的代理,而它的代理也持有一个强的指针指向它,那么你的应用会由于任何一个类都不能彼此释放内存而形成内存泄漏。而id类型表示你只能够成为遵照HorizontalScrollerDelefate的对象的assign方,给了你必定程度上的类型安全。
这个reload方法是一个在UITableView以后被从新刷新了,它reload了全部的用于构建horizontal scroller的数据。
用下面的代码体大地HorizontalScroller.m中全部的代码。
看一下这些注释:
1. 定义了一些常量去使更容易在设计时修改布局。视图在这个scroller中的面积是100*100有一个和它相近的矩形有一个10。
2. HorizontalScroller遵照<UIScrollViewDelegate>,这是由于HorizontalScroller适应一个UIScrollView去滚动album,它须要去知道用户的行为好比用户中止了滚动。
3. 建立了一个scroll view容器。
下一步你须要去实现这个初始化方法,添加下面这个方法。
这个scroll view彻底填充了这个HorizontalScroller,一个UITapGestureRecognizer检测有没有在这个scroll view上有触摸行为和检查一个album cover是否被点击。若是有的话,它会通知HorizontalScroller的代理。加入这个方法。
手势被当成locationInView的一个参数去传递让你精确的知道位置,接下来你唤醒了它的代理的numberOfviewsForHorizontalScroller方法,这个HorizontalScroller的实例除了知道它能够安全的向一个遵照了HorizontalScrollerDelegate的对象发送方法之外其余的一无所知。对于在uiscroll view中的每个视图,用CGRectContainsPoint方法去找出被点击的视图。当这个视图找到了之后,向它的代理发送clickViewAtIndex消息。在不跳出这个循环以前,将这个被点击的视图居中。添加下面这个方法去reload这个scroller。
经过逐行注释来看看这些代码。
1. 若是没用代理,那么没用什么能够作的事情,那你能够直接返回了。
2. 将原先添加到scroll view中的字视图所有移除掉。
3. 全部的视图都在一个给丁的距离开始,如今它是100,可是能够轻松的经过改变上面那个定义的常量去改变,
4. HorizontalScroller每次询问它的代理让它们彼此水平的挨着在一块。
5. 一旦全部的视图都安置好了,设置这个滚动视图的contenOffset让用户能够滚动全部的album covers。
6. 这个HorizontalScroller检查它的代理是否响应initialViewIndexForHorizontalScroller这个方法,若是响应的话这个代码就会将这个滚动视图放在它的代理定义的初始视图的中心,不然默认的就是0
当你的数据发生了改变之后你要执行reload操做,你也能够执行调用这个方法在你将HorizontalScroller添加到其余的视图上的时候。加入一下代码到HorizontalScroller.m文件中,
didMoveToSuperview这个消息当一个视图要添加到另外一个视图上做为一个子视图上时被调用。这个时候就是reload scroller的内容的正确的时机了。HorizontalScroller的最后一个难题时确保你正在看见的album老是在scroll view的正中间。所以大家要去实现一些计算当用户用手指拖动这个scroll view时。
将这个代码添加到HorizontalScroller.m中
上面这段代码计算当前的scroll view的骗一直和面积和视图的padding去计算当前的视图离中心的距离。最后一行是很重要的,一旦这个视图移动到中心了,通知它的代理这个显示的视图被改变了。
为了检测用户在scroll view的拖动,你必须添加以下的UIScrollViewDelegate方法:
scrollViewDidEndDragging: willDecelerate:会通知它的代理当用户结束拖动的时候。若是用户尚未结束拖动那么这个decelerate参数就为真。当用户结束拖动,那么系统就会调用前面那个方法。两个方法中咱们都调用了新的方法去让当前的视图居中由于这个当前的视图已经在用户拖动后发生了改变。
如今这个HorizontalScroller已经准备好使用了。回顾一下你刚才所写的代码,一点都没有说起到album和albumView这些类,这是很是好的,由于这意味着你的代码是独立的和可重用的。
如今编译使工程确保没事。
如今HorizontalScroller已经准备好了,是时候去使用它了。打开viewController.m加入以下的代码:
添加HorizontalScrollerDelegate,使
添加下面的这个实例变量到它的类扩展中。 HorizontalScroller *scroller;
如今你能够实现这些代理方法了,你将会惊讶只用几行代码就能够实现不少的功能。
添加下面的代码到ViewController.m中
这里设置了这些变量去存储暂时的album而后调用showDataForAlbumAtIndex方法去显示一个下那的album的数据。
小贴士:将一些方法放在一块儿经过@pragma mark是一种很好的习惯。编译器会忽略这一行可是你会在你的Xcode的jump bar中将这些方法列起来。这会帮助你在Xcode中更好的组织代码。而后添加下面的代码
这就是像你能辨认出来的那样,这是协议方法返回在scroll view中的视图的个数。由于折合scorll view为全部的album 的数据显示covers ,这个count就是album记录的数据。接下来添加这个方法:
在这里你建立了一个新的AlbumView而后将它传递给了horizontalScroller.
就是这么多,你只用了三个很简短的方法就显示了一个很好看的horizontalScroller。
是的,你仍要去真正的建立一个scroller而后将它添加到你的主要的view中可是在坐这个以前。添加这个方法:
这个方法loads album的数据经过LibraryAPI而后设置当前啊的现实的视图在机遇当前的视图的index的值上,若是当前的view index 小于0,那么意味着没有当前的视图选择,那就将第一张album显示,不然就是最后一张album被显示。
如今初始化这个scroller经过添加下面的代码到你的viewController.m中
上面的仅仅是建立了一个新的HorizontalScroller实例,添加到main view中,而后loads全部的子视图去显示album的数据。
小贴士:若是一个协议变得很大,而后有不少的方法,那么你应该考虑将它分解成几个小的协议,UITableViewDelegate和UITableViewDataSource就是一个很是好的例子。尝试着去设计你的戏而已让每个控制一个具体的功能。
编译和建立米的工程,看一下你的这个很棒的新的horizontal scroller:以下图:
等下,这个horizontal scroller是在里面了,可是这个album cover在哪里呢?
额,那九堆了-你尚未去实现下载cover的代码。因此你将要去添加一个下载图片的方法,计算你的因此访问的服务都是经过LibraryAPI,那么那里就是你放这些方法的地方,而后,首先还要考虑如下几个问题:
1 AlbumView不该该直接和LibraryAPI去搭配工做,你不要去将视图逻辑和交流逻辑混合起来.
2 相同的缘由,LibraryAPI也不应知道有关AlbumView的事情。
3 LibraryAPI应该去通知AlbumView一旦这些covers已经下载好了由于AlbumView要去显示它们。
听起来像个谜,不要回信,你将会学到怎么样去使用Observer模式.
在观察者模式中,一个对象将会通知其余对象的任何状态的改变。这些相关的对象并不须要去知道另外一个对象-这样就形成了一个非耦合的设计。这个模式大部分用在去通知一个感兴趣的对象它的一个属性已经发生了改变。
通常的实现须要一个对象注册成为它感兴趣的状态的观察者,当这个状态改变了,全部的观察者对象都会接收到通知。苹果的Push Notification服务就是对这个最好的例子。
若是你想要坚持MVC设计模式的概念,你须要去容许Model对象去和View对象交流,可是它们之间并无直接的引用,这就是观察站模式引入的缘由。
Cocoa实现观察者模式有两种经常使用的方法:Notification和Key-Value-Observing(KVO)
不要和和push或者本地的通知相混淆,Notifications是基于一个订阅-分发的模型去容许一个对象发送一些消息给其余对象。这个对象不须要去知道关于订阅者的任何信息。Notifications被苹果公司用的不少。例如,当键盘显示或者隐藏时,系统将会发送一个UIKeyboardWillShowNotification/UIKeyboardWillHideNotification,响应的当你进入到后台,系统将会发送一个UIApplicationDidEnterBackgroundNotification的通知.
提醒:打开UIApplication.h在文件的结尾你会看到一系列超过20条系统发送的通知。
打开AlbumView.m加入下面的代码到[self addSubView:indicator]后面:
这一行经过NSNotificationCenter单例发送一个通知,这个通知的info里面包括了UIImageView去计算和要下载的cover image的URL,这就是所有的你须要去实现下载任务的信息。
添加下面的代码到LibraryAPI的init方法中,直接在isOnline = No的后面。
这就是方程式的另外一端,观察者。每次一个AlbumView类发送一个DownloadImageNotification的通知的是很好,由于Library已经注册成为它的一个观察者,因此系统会通知它,那么它就会相应的去执行downloadImage。
然而,在你实现downloadImage以前,你必须记住当你的类deallocated的时候去取消观察者的状态。不然的话,一个通知可能会被发送给一个已经被deallocated的对象,那么就会致使app的崩溃。
添加下面的方法到Library中:
当这个类结束后,它会移除它全部的通知中的观察者状态。
还有一件事要去作。若是你将下载的covers保存起就是一个好主意,由于咱们不用一次又一次的去下载相同的covers。打开PersistencyManager.h而后添加下面的这两个方法的原型:
而后将它们的实现代码添加到.m文件中:
这几个代码是十分简洁的,它下载的图片将会被保存在Documents目录下,而后getImage:将会是nil若是在Documents目录下一个匹配的文件都没有。
而后添加下面的方法到Library.m中:
这是上面代码的讲解:
1. downloadImage会经过通知被执行,因此这个方法以通知为参数,UIImageView和image URL从通知中获得。
2. 若是先前已经下载的话那么就从PersistencyManager中去获取图片
3. 若是没下载的话就经过HTTPClient去下载。
4. 当下载结束,在uiimage view 上显示图片,再用manager去将它在本地保存。
你又一次使用外观设计模式去隐藏了下载图片的复杂性,这个nofitifation根本不会去关心这个图片是经过网页下载的仍是本地得到的。
编译和运行你的app,你将会看到下面这个美丽的covers在你的HorizentalScroller中。
中止你的app而后再次运行,只一道没有任何的延时在加载covers的时候,你能够断开网络而后你的应用仍是运行的完美无瑕。然而你会发现,这个spinning并无中止运行。这是怎么回事?
你在开始下载的时候开始启动这个spingning,可是你并无实如今图片开始下载无缺的时候去中止它的逻辑。你能够在每次图片已经下载完的时候发送一个notification,可是另外你可使用另外一个观察者模式-KVO。
在KVO中,一个对象能够请求去在一个具体的属性开始变化的时候获得它的一个通知,不论这个属性属于它本身仍是另外一个对象。在这个例子里面,你可使用KVO去观察加载image的UIImageView中的这个image属性的改变。
你们AlbumView.m,添加下面这个代码到initWIthFrame:albumCover:中,添加在[selfaddSubview:indicator]以后。
这添加了self,也当前的类成为coverImage的image属性的一个观察者。
你也须要吧去在你结束的时候取消成为观察者.仍在AlbumView.m中添加下面的代码:
最后,添加这个方法:
你必须在每一个你做为一个观察者的类里面吧去实现这个方法。每次当被观察的属性变化时系统就会执行这个方法,在上面的方法中当image属性改变的时候就会调用这个方法。就这样,当一个图片下载完成,这个spinning就会中止.
编译运行你的工程,这个spinnning 就会消失。
小贴士:必定要记得去remove你的observers在它们deallocated时,不然当这个对象视图像一个不存在的观察者发送一个消息时那么你的app就会崩溃掉。
若是你运行如下app而后滚动一下这个covers,以后中止运行它,你会注意到app的状态并无保存下来。你看到的最后一个视图并无在应用再次启动的时候成为默认的设置。
要改正这一点,你能够充分的利用下一个设计模式:Memento设计模式(备忘录模式)
备忘录设计模式将一个对象的内部状态进行捕捉并外部化,换句话说就是你将你的东西保存在某个地方。之后这个外部话的转台不须要借助封装就能够被回复,也就是私有的数据仍是私有的。
接下来将下面两个方法添加在ViewController.m中
saveCurrentState将当前的album的index保存到NSUserDefaults,NSUserDefaults是一个iOS为了保存应用的具体的设置和数据而提供的一个标准的数据存储的类。
loadPreviousState加载了先前保存的index这并非完整的备忘录设计模式,可是你如今达到了目的。
如今添加下面这行代码到ViewController的viewDidLoad中的在scroller被初始化的代码以前。
[self loadPreviousState];
这会在app启动的时候加载先前被保存的状态。可是你在那里保存从后台回来的的app的状态。你要使用通知去完成这个任务。iOS在app被放到后台的时候会发送一个UIApplicationDidEnterBackgroundNotification的通知,你可使用这个通知去调用saveCurrentState,是否是很方便啊?
加入下面的这行代码到ViewDidLoad的结尾。
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(saveCurrentState)name:UIApplicationDidEnterBackgroundNotificationobject:nil];
当app将要被挂起到后台的时候,ViewController就会自动地调用saveCurrentState去保存状态。
如今添加下面的代码:
这会保证当ViewController被deallocate时候将这个累中从观察者去移去。
编译运行你的应用,拖动到某一张album,使用Command+shift+H去将app设置到后台,而后关闭你的app,从新启动,看看原先你拖动到的那张图片是否是设置在中间。
看起来这个album的数据是对的嘛,可是这个scroller并无将正确的album设置在中心,这是怎么回事?
这是由于这个可选的方法: initialViewIndexForHorizontalScroller的意义所在,由于你没有实现这个方法,因此它老是被设置在默认的第一张。
去修正它,添加下面的代码到ViewController中:
如今这个HorizontalScroller的第一张数一被设置成表示album位置的currentAlbumIndex,这是一个很好的方法去确保这个app保持我的化和可重用的双重便利。
再次运行你的app,滚动一个 app
,中止app,再启动,确保问题已经被解决。
若是你看看你的PersistencyManager的init方法,你会注意到这个album的数据被写死了,而每次当PersistencyManager被建立的时候就被从新建立,可是是否是只建立一次albums的列表而后将它们保存在一个文件中更好呢?可是你怎么将你的Album数据保存再一个文件中呢?
一个选择就是使用Album的property属性,将它们保存再一个plist文件中而后当须要的时候从新去建立Album实例,这不是最好的选择,由于它须要你去写一个具体的依赖于再每个类中的数据或者properties的代码,例如说若是你后来建立了一个具备不一样的属性的Movie的类,那保存和从新读取数据将要一个新的代码去完成。
此外,你不能将一个私有变量保存在每个类的实例中由于它们是不能被外界所访问的。这就是苹果公司建立这个Achiving机制的缘由。
Achiving是苹果公司具体实现备忘录模式之一。它将一个对象转换成一个能够被保存后面能够回复可是不须要将私有变量暴露给外部类。
首先你须要去经过遵照NSCoding这个协议代表这个Album类能够被压缩打开Album.h而后将它变成遵照这个协议:
@interface Album :NSObject <NSCoding>
而后添加下面的代码到Album.m中:
你调用encodeWithCoder当你压缩一个类的实例变量的时候,相反你调用initWithCoder当你解压一个实例去建立一个Album的实例的时候,很简单却很强大。
如今这个Album类能够被压缩那就添加时机保存和重载albums的列表的类。
添加下面的方法声明到你的PersistencyManager.h中:
-(void)saveAlbums;
这将会在调用保存albums数据的时候被调用,下面添加这个实现到PersistencyManager.m中
NSKeyedArchiver黄这个album数组压缩到一个albums.bin的文件中。当你u压缩一个包含其余对象的对象时,那么这个Archiver会自动的尝试去递归地压缩子对象和子对象的子对象等等。在这个实例中,这个archival开始于albums-这时一个Album各类实例的数组,由于NSArray和Album都支持NSCoping接口,因此这个数组被自动的压缩了。
如今替换PersistencyManager.m文件中的init文件。
在这个新的代码中NSKeyedUnarchiver从文件中加载这个album的数据,若是存在的话,若是不存在,它就会建立一个album数据而后马上保存它让下次启动app的时候使用。
你也想要去在每次app进入到后台的时候保存album的数据,这彷佛不须要可是若是后面你想要添加一个去改变album数据的选择呢?而后你想要去确保全部的变化都被保存了的。添加下面的方法声明到LibraryAPI.h中;
-(void)saveAlbums;
既然主要的application访问全部的服务都是经过LibraryAPI,这就是应用怎么样让PersistencyManager去知道它是否须要保存数据,如今添加下面的方法实现到LibraryAPI.m中。
这个代码仅仅传递了一个去让PersistencyManager去保持albums的一个调用。
而后添加下面这行代码到ViewController.m的saveCurrentState 中
上面的代码使用LibraryAPI去当ViewCOntroller想要去保存数据的时候触发保存album数据的方法。
如今变异你的app去确保编译经过。
不幸的是,没有什么简单的方法去检查数据的固话是否彻底正确,你能够在模拟器的Mocunments文件夹下面去检查album数据文件是建立的,可是为了企业看到温和的改变你须要去添加一个能够去改变album数据的草种。
与其去改变数据,若是你添加一个去删除你不想让它存在在albums中的一个album的选择项的话是否是更好?此外添加一个撤销的操做以避免被误删除呢?
这就提供了一个很好的机会去介绍最后一个设计模式:命令模式
这个设计模式将对象封装成了一个请求获取操做,这个封装请求比一个原始的请求更加的灵活,且能够在对象之间传递,稍后存储,动态修改获取放到一个队列之中。苹果公司是用Target-action机制和Invocation实现的,你能够在苹果的官方文档中去知道更多的关于Target-Action,可是Invocation使用包含一个Target对象的NSInvocation类,一个方法选择器和一些参数。这个对象能够根据须要动态的改变和执行,在命令模式中这是一个完美的例子,它解除耦合了发送对象和接收对象,且能够持续的坚持一个或者一系列请求。
早莫深刻动做的invocation以前,你须要去设置撤销(undo action)动做的框架,因此你必须定义一个UIToolBar 和undo stack(撤销栈)上所须要的可变数组。
在ViewController.m的扩展中键入这些代码:
这建立了一个为了这些操做而添加显示的toolbar,还要一个数组去去做为命令队列。
添加下面的代码到ViewDidLoad的结尾:
上面的代码建立了一个toolbar,它有两个按钮和一个可变的空格。这个撤销按钮被禁止了由于这个undo stack一开始是空的。
一样,由于这个toolbar没有根据frame来初始化,因此这个在viewDidload中frame的大小尚未设置。因此经过下面的一些代码在一旦视图的frame被最终设置好了之后设置这个frame。
你将会添加下面的三个方法到ViewController中去处理album的三个管理操做:添加,删除和撤销。
第一个方法是增长一个新的album:
在这里你添加了一个album,设置它的当前的album的index,而后从新加载scroller.接下来删除方法:
在上面的这些代码里面有一些新的有趣的特性,因此解析一下:
1. 得到要删除的album,
2. 定义一个NSMethodSignature类型去建立一个NSInvocation,它将用于去在用户决定撤销一个删除时作和删除相反的操做。这个NSInovation须要知道三个事情:The selector(发送的消息),目标:((发送给谁)和发送的消息的一些参数。在这个例子里,消息时发送给删除的相反方,由于当你撤销一个删除操做,你须要将它们从新添加这个删除的album。、
3. 当这个undoAction被建立后你将它添加到undoStack中,这个动做将会被添加到一个数组的末尾,正像一个普通的堆栈同样。
4. 使用LibraryAPI去从数据结构中去删除album而后从新加载scroller。
5. 由于在undoStack中有了一个动做,因此你须要去将undo按钮使能。
注意:当你使用NSInvocation,你须要去记住下面的三个点:
1. 参数必须经过指针来传递。
2.参数起始于index 2,由于0和1是保留给target 和action用的。
3. 若是这些参数有可能被销毁(dealocate),你应该使用retainArguments.
最后添加下面的代码用于撤销:
这个撤销操做会pops堆栈中的最后一个对象。这个对象永远是NSInvocation类型的并且能够经过调用invoke被激活。这回激活你你先前在album被删除时建立的命令,而后添加背删除的album到album列表中。由于你也能够删除在你撤销后最后一个对象,因此你须要判断这个栈是否时空的,若是是的话那么将undo按钮禁止交互。编译和运行你的app去特使这个undo 机制。删除这个album而后点击撤销操做去看看效果。
还有两种设置模式没用将它们应用在这个app中,可是也是很重要的:抽象工厂模式和责任链模式。但官方文档去扩展你的设计模式水平
在这个教程你已经看到怎么样去将发挥ios的设计模式的威力用很简单和耦合性地的方式去处理很复杂的任务。你已经学习了不少关于ios设计模式的概念:MVC, 但里,代理,协议,外观,贯彻着,备忘录,命令。
你最后的代码是耦合性很低的,可重用的且可读性该,若是其余开饭看你的代码它会马上明白这是怎么回事和每一个类的做用。
关键不是使用设计模式去写你的每一行代码而是当你在解决一个困难的问题特别是在设计应用的早期的时候意识到使用什么设计模式。它会让你的开发工做更容易,代码更高效。
此博文来自:http://blog.csdn.net/sanjunsheng/article/details/38071787。写的很好做为收藏。