备忘录模式
备忘录设计模式将一个对象的内部状态进行捕捉并外部化,换句话说就是你将你的东西保存在某个地方。以后这个外部话的转台不需要借助封装就可以被回复,也就是私有的数据还是私有的。
如何使用备忘录设计模式
接下来将下面两个方法添加在ViewController.m中
- (void)saveCurrentState { [[NSUserDefaultsstandardUserDefaults] setInteger:currentAlbumIndexforKey:@"currentAlbumIndex"]; } - (void)loadPreviousState { currentAlbumIndex = [[NSUserDefaultsstandardUserDefaults]integerForKey:@"currentAlbumIndex"]; [selfshowDataForAlbumAtIndex:currentAlbumIndex]; }
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去保存状态。
现在添加下面的代码:
-(void)dealloc { [[NSNotificationCenterdefaultCenter]removeObserver:self]; }
这会保证当ViewController被deallocate时候将这个累中从观察者去移去。
编译运行你的应用,拖动到某一张album,使用Command+shift+H去将app设置到后台,然后关闭你的app,重新启动,看看原先你拖动到的那张图片是不是设置在中间。
看起来这个album的数据是对的嘛,但是这个scroller并没有将正确的album设置在中心,这是怎么回事?
这是因为这个可选的方法: initialViewIndexForHorizontalScroller的意义所在,因为你没有实现这个方法,所以它总是被设置在默认的第一张。
去修正它,添加下面的代码到ViewController中:
- (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller *)scroller { returncurrentAlbumIndex; }
现在这个HorizontalScroller的第一张数一被设置成表示album位置的currentAlbumIndex,这是一个很好的方法去确保这个app保持个人化和可重用的双重便利。
再次运行你的app,滚动一个 app
,停止app,再启动,确保问题已经被解决。
如果你看看你的PersistencyManager的init方法,你会注意到这个album的数据被写死了,而每次当PersistencyManager被创建的时候就被重新创建,但是是不是只创建一次albums的列表然后将它们保存在一个文件中更好呢?但是你怎么将你的Album数据保存再一个文件中呢?
一个选择就是使用Album的property属性,将它们保存再一个plist文件中然后当需要的时候重新去创建Album实例,这不是最好的选择,因为它需要你去写一个具体的依赖于再每一个类中的数据或者properties的代码,例如说如果你后来创建了一个具有不同的属性的Movie的类,那保存和重新读取数据将要一个新的代码去完成。
此外,你不能将一个私有变量保存在每一个类的实例中因为它们是不能被外界所访问的。这就是苹果公司创建这个Achiving机制的原因。
Achiving
Achiving是苹果公司具体实现备忘录模式之一。它将一个对象转换成一个可以被保存后面可以回复但是不需要将私有变量暴露给外部类。
如何使用Achiving
首先你需要去通过遵守NSCoding这个协议表明这个Album类可以被压缩打开Album.h然后将它变成遵守这个协议:
@interface Album :NSObject <NSCoding>
然后添加下面的代码到Album.m中:
- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_titleforKey:@"title"]; [aCoder encodeObject:_artistforKey:@"artist"]; [aCoder encodeObject:_coverUrlforKey:@"coverUrl"]; [aCoder encodeObject:_yearforKey:@"year"]; [aCoder encodeObject:_genreforKey:@"genre"]; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [superinit]) { _title = [aDecoder decodeObjectForKey:@"title"]; _genre = [aDecoder decodeObjectForKey:@"genre"]; _year = [aDecoder decodeObjectForKey:@"year"]; _artist = [aDecoder decodeObjectForKey:@"artist"]; _coverUrl = [aDecoder decodeObjectForKey:@"coverUrl"]; } returnself; }
你调用encodeWithCoder当你压缩一个类的实例变量的时候,相反你调用initWithCoder当你解压一个实例去创建一个Album的实例的时候,很简单却很强大。
现在这个Album类可以被压缩那就添加时机保存和重载albums的列表的类。
添加下面的方法声明到你的PersistencyManager.h中:
-(void)saveAlbums;
这将会在调用保存albums数据的时候被调用,下面添加这个实现到PersistencyManager.m中
-(void)saveAlbums { NSString *path = [NSHomeDirectory() stringByAppendingString:@"/Documents/,albums.bin"]; NSData *data = [NSKeyedArchiverarchivedDataWithRootObject:albums]; [data writeToFile:path atomically:YES]; }
NSKeyedArchiver黄这个album数组压缩到一个albums.bin的文件中。当你u压缩一个包含其他对象的对象时,那么这个Archiver会自动的尝试去递归地压缩子对象和子对象的子对象等等。在这个实例中,这个archival开始于albums-这时一个Album各种实例的数组,因为NSArray和Album都支持NSCoping接口,所以这个数组被自动的压缩了。
现在替换PersistencyManager.m文件中的init文件。
- (id)init { self = [superinit]; if (self) { NSString *path = [NSHomeDirectory() stringByAppendingString:@"/Documents/,albums.bin"]; NSData *data = [NSDatadataWithContentsOfFile:path]; albums = [NSKeyedUnarchiverunarchiveObjectWithData:data]; if (!albums) { albums = [NSMutableArrayarrayWithArray:@[[[Albumalloc] initWithTitle:@"Best of Bowie"artist:@"David Bowie"coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png"year:@"1992"],[[Albumalloc] initWithTitle:@"It‘s MyLife"artist:@"NoDoubt"coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png"year:@"2003"],[[Albumalloc] initWithTitle:@"NothingLike The Sun"artist:@"Sting"coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png"year:@"1999"],[[Albumalloc] initWithTitle:@"Staringat the Sun"artist:@"U2"coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png"year:@"2000"],[[Albumalloc] initWithTitle:@"AmericanPie"artist:@"Madonna"coverUrl:@"http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png"year:@"2000"]]]; [selfsaveAlbums]; } } returnself; }
在这个新的代码中NSKeyedUnarchiver从文件中加载这个album的数据,如果存在的话,如果不存在,它就会创建一个album数据然后立刻保存它让下次启动app的时候使用。
你也想要去在每次app进入到后台的时候保存album的数据,这似乎不需要但是如果后面你想要添加一个去改变album数据的选择呢?然后你想要去确保所有的变化都被保存了的。添加下面的方法声明到LibraryAPI.h中;
-(void)saveAlbums;
既然主要的application访问所有的服务都是通过LibraryAPI,这就是应用怎么样让PersistencyManager去知道它是否需要保存数据,现在添加下面的方法实现到LibraryAPI.m中。
- (void)saveAlbums { [managersaveAlbums]; }
这个代码仅仅传递了一个去让PersistencyManager去保持albums的一个调用。
然后添加下面这行代码到ViewController.m的saveCurrentState 中
[[LibraryAPIsharedInstance]saveAlbums];
上面的代码使用LibraryAPI去当ViewCOntroller想要去保存数据的时候触发保存album数据的方法。
现在变异你的app去确保编译通过。
不幸的是,没有什么简单的方法去检查数据的固话是否完全正确,你可以在模拟器的Mocunments文件夹下面去检查album数据文件是创建的,但是为了企业看到温和的改变你需要去添加一个可以去改变album数据的草种。
与其去改变数据,如果你添加一个去删除你不想让它存在在albums中的一个album的选择项的话是不是更好?此外添加一个撤销的操作以免被误删除呢?
这就提供了一个很好的机会去介绍最后一个设计模式:命令模式