观察者模式 - Observer在观察者模式里,一个对象在状态变化的时候会
通知另一个对象。参与者并不需要知道其他对象的具体是干什么的 - 这是一种降低耦合度的设计。这个设计模式常用于在某个
属性改变的时候
通知关注该
属性的对象。常见的使用
方法是观察者
注册监听,然后再状态改变的时候,所有观察者们都会收到
通知。在 MVC 里,观察者模式意味着需要允许 Model 对象和 View 对象进行交流,而不能有直接的关联。Cocoa 使用两种方式实现了观察者模式: Notification 和 Key-Value Observing (KVO)。
通知 - Notification不要把这里的
通知和推送
通知或者本地
通知搞混了,这里的
通知是基于
订阅-发布模型的,即一个对象 (发布者) 向其他对象 (
订阅者) 发送消息。发布者永远不需要知道
订阅者的任何数据。Apple 对于
通知的使用很频繁,比如当
键盘弹出或者收起的时候,系统会分别发送 UIKeyboardWillShowNotification/UIKeyboardWillHideNotification 的
通知。当你的应用切到
后台的时候,又会发送 UIApplicationDidEnterBackgroundNotification 的
通知。注意:打开 UIApplication.swift
文件,在
文件结尾你会看到二十多种系统发送的
通知。如何使用
通知打开 AlbumView.swift 然后在 init 的最后插入如下
代码:NSNotificationCenter.defaultCenter().postNotificationName("BLDownloadImageNotification",object: self,userInfo: ["imageView":coverImage,"coverUrl" : albumCover])这行
代码通过 NSNotificationCenter 发送了一个
通知,
通知信息包含了 UIImageView 和
图片的下载地址。这是下载图像需要的所有数据。然后在 LibraryAPI.swift 的 init
方法的 super.init() 后面
加上如下
代码:NSNotificationCenter.defaultCenter().addObserver(self,selector:"downloadImage:",name: "BLDownloadImageNotification",object: nil)这是等号的另一边:观察者。每当 AlbumView 发出一个 BLDownloadImageNotification
通知的时候,由于 LibraryAPI 已经
注册了成为观察者,所以系统会
调用 downloadImage()
方法。但是,在实现 downloadImage() 之前,我们必须先在 dealloc 里取消监听。如果没有取消监听消息,消息会发送给一个已经销毁的对象,导致程序崩溃。在 LibaratyAPI.swift 里
加上取消
订阅的
代码:deinit { NSNotificationCenter.defaultCenter().removeObserver(self)}当
对象销毁的时候,把它从所有消息的
订阅列表里
去除。这里还要做一件事情:我们最好把
图片存储到本地,这样可以避免一次又一次下载相同的封面。打开 PersistencyManager.swift
添加如下
代码:func saveImage(image: UIImage,filename: String) { let path = NSHomeDirectory().stringByAppendingString("/Documents/\(filename)") let data = UIImagePNGRepresentation(image) data.writeToFile(path,atomically: true)}func getImage(filename: String) -> UIImage? { var error: NSError? let path = NSHomeDirectory().stringByAppendingString("/Documents/\(filename)") let data = NSData(contentsOfFile: path,options: .UncachedRead,error: &error) if let unwrappedError = error { return nil } else { return UIImage(data: data!) }}
代码很简单直接,下载的
图片会存储在 Documents 目录下,如果没有检查到缓存
文件, getImage()
方法则会返回 nil 。然后在 LibraryAPI.swift
添加如下
代码:func downloadImage(notification: NSNotification) { //1 let userInfo = notification.userInfo as [String: AnyObject] var imageView = userInfo["imageView"] as UIImageView? let coverUrl = userInfo["coverUrl"] as NSString //2 if let imageViewUnWrapped = imageView { imageViewUnWrapped.image = persistencyManager.getImage(coverUrl.lastPathComponent) if imageViewUnWrapped.image == nil { //3 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),{ () -> Void in let downloadedImage = self.httpClient.downloadImage(coverUrl) //4 dispatch_sync(dispatch_get_main_queue(),{ () -> Void in imageViewUnWrapped.image = downloadedImage self.persistencyManager.saveImage(downloadedImage,filename: coverUrl.lastPathComponent) }) }) } }}拆解一下上面的
代码:downloadImage 通过
通知调用,所以这个
方法的参数就是 NSNotification 本身。 UIImageView 和 URL 都可以从其中
获取到。如果以前下载过,从 PersistencyManager 里
获取缓存。如果
图片没有缓存,则通过 HTTPClient
获取。如果下载完成,展示
图片并用 PersistencyManager 存储到本地。再回顾一下,我们使用外观模式隐藏了下载
图片的复杂程度。
通知的发送者并不在乎
图片是如何从网上下载到本地的。运行一下项目,可以看到专辑封面已经
显示出来了:关了应用再重新运行,注意这次没有任何延时就
显示了所有的
图片,因为我们已经有了本地缓存。我们甚至可以在没有网络的情况下正常使用我们的应用。不过出了问题:这个用来
提示加载网络请求的小菊花怎么一直在
显示!我们在下载
图片的时候开启了这个白色小菊花,但是在
图片下载完毕的时候我们并没有停掉它。我们可以在每次下载成功的时候发送一个
通知,但是我们不这样做,这次我们来用用另一个观察者模式: KVO 。键值观察 - KVO在 KVO 里,对象可以
注册监听任何
属性的变化,不管它是否持有。如果感兴趣的话,可以读一读苹果 KVO 编程指南。如何使用 KVO正如前面所提及的, 对象可以关注任何
属性的变化。在我们的例子里,我们可以用 KVO 关注 UIImageView 的 image
属性变化。打开 AlbumView.swift
文件,找到 init(frame:albumCover:)
方法,在把 coverImage
添加到 subView 的
代码后面
添加如下
代码:coverImage.addObserver(self,forKeyPath: "image",options: nil,context: nil)这行
代码把 self (也就是当前类)
添加到了 coverImage 的 image
属性的观察者里。在销毁的时候,我们也需要取消观察。还是在 AlbumView.swift
文件里,
添加如下
代码:deinit { coverImage.removeObserver(self,forKeyPath: "image")}最终
添加如下
方法:override func observeValueForKeyPath(keyPath: String,ofObject object: AnyObject,change: [NSObject : AnyObject],context: UnsafeMutablePointer) { if keyPath == "image" { indicator.stopAnimating() }}必须在所有的观察者里实现上面的
代码。在检测到
属性变化的时候,系统会
自动调用这个
方法。在上面的
代码里,我们在
图片加载完成的时候把那个
提示加载的小菊花去掉了。再次运行项目,你会发现一切正常了:注意:一定要记得移除观察者,否则如果对象已经销毁了还给它发送消息会导致应用崩溃。此时你可以把玩一下当前的应用然后再关掉它,你会发现你的应用的状态并没有存储下来。最后看见的专辑并不会再下次打开应用的时候出现。为了
解决这个问题,我们可以使用下一种模式:备忘录模式。