首页 > 代码库 > 观察者

观察者

观察者模式

在观察者模式中,一个对象将会通知其他对象的任何状态的改变。这些相关的对象并不需要去知道另一个对象-这样就造成了一个非耦合的设计。这个模式大部分用在去通知一个感兴趣的对象它的一个属性已经发生了改变。

一般的实现需要一个对象注册成为它感兴趣的状态的观察者,当这个状态改变了,所有的观察者对象都会接收到通知。苹果的Push Notification服务就是对这个最好的例子。

如果你想要坚持MVC设计模式的概念,你需要去允许Model对象去和View对象交流,但是它们之间并没有直接的引用,这就是观察站模式引入的原因。

Cocoa实现观察者模式有两种常用的方法:Notification和Key-Value-Observing(KVO)

Notificaions

不要和和push或者本地的通知相混淆,Notifications是基于一个订阅-分发的模型去允许一个对象发送一些消息给其他对象。这个对象不 需要去知道关于订阅者的任何信息。Notifications被苹果公司用的很多。例如,当键盘显示或者隐藏时,系统将会发送一个 UIKeyboardWillShowNotification/UIKeyboardWillHideNotification,响应的当你进入到后 台,系统将会发送一个UIApplicationDidEnterBackgroundNotification的通知.

           提醒:打开UIApplication.h在文件的结尾你会看到一系列超过20条系统发送的通知。

如何使用Notifications

打开AlbumView.m加入下面的代码到[self addSubView:indicator]后面:

[[NSNotificationCenterdefaultCenter] postNotificationName:@"DownloadImageNotification"  
   
                                                           object:self  
   
                                                          userInfo:@{@"imageView":coverImage, @"coverUrl":albumCover}];

这一行通过NSNotificationCenter单例发送一个通知,这个通知的info里面包括了UIImageView去计算和要下载的cover imageURL,这就是全部的你需要去实现下载任务的信息。

添加下面的代码到LibraryAPIinit方法中,直接在isOnline = No的后面。

[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(downloadImageAction:) name:@"DownloadImageNotification"object:nil];
  •   



这就是方程式的另一端,观察者。每次一个AlbumView类发送一个DownloadImageNotification的通知的是很好,因为Library已经注册成为它的一个观察者,所以系统会通知它,那么它就会相应的去执行downloadImage

然而,在你实现downloadImage之前,你必须记住当你的类deallocated的时候去取消观察者的状态。否则的话,一个通知可能会被发送给一个已经被deallocated的对象,那么就会导致app的崩溃。

添加下面的方法到Library:

- (void)dealloc  
   
{  
   
    [[NSNotificationCenterdefaultCenter]removeObserver:self];  
   
}

当这个类结束后,它会移除它所有的通知中的观察者状态。

还有一件事要去做。如果你将下载的covers保存起就是一个好主意,因为我们不用一次又一次的去下载相同的covers。打开PersistencyManager.h然后添加下面的这两个方法的原型:

 

- (UIImage *)getImageFromFileName:(NSString *)fileName;  
   
-(void)saveAlbums;




然后将它们的实现代码添加到.m文件中:

-(void)saveImage:(UIImage *)image fileName:(NSString *)filename  
   
{  
   
    filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@",filename];  
   
    NSData *data = UIImagePNGRepresentation(image);  
   
    [data writeToFile:filename atomically:YES];  
   
}  
   
    
   
-(UIImage *)getImageFromFileName:(NSString *)fileName  
   
{  
   
    fileName = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@",fileName];  
   
    NSData *data = [NSDatadataWithContentsOfFile:fileName];  
   
    return [UIImageimageWithData:data];  
   
}



 

这几个代码是十分简洁的,它下载的图片将会被保存在Documents目录下,然后getImage:将会是nil如果在Documents目录下一个匹配的文件都没有。

然后添加下面的方法到Library.m:

- (void)downloadImageAction:(NSNotification *)notification  
   
{  
   
      
   
    UIImageView *imageView =notification.userInfo[@"imageView"];  
   
    NSString *coverUrl =notification.userInfo[@"coverUrl"];  
   
    
   
    imageView.image = [managergetImageFromFileName:[coverUrl lastPathComponent]];  
   
    if ( imageView.image == nil) {  
   
        NSLog(@"notificatiom%@",notification.userInfo);  
   
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
   
            UIImage *image = [clientdownloadImage:coverUrl];  
   
            dispatch_async(dispatch_get_main_queue(), ^{  
   
               imageView.image = image;  
   
                [managersaveImage:image fileName:[coverUrl lastPathComponent]];  
   
            });  
   
        });  
   
    }  
   
}

这是上面代码的讲解:

1.    downloadImage会通过通知被执行,所以这个方法以通知为参数,UIImageView和image URL从通知中得到。

2.    如果先前已经下载的话那么就从PersistencyManager中去获取图片

3.    如果没下载的话就通过HTTPClient去下载。

4.    当下载结束,在uiimage view 上显示图片,再用manager去将它在本地保存。

你又一次使用外观设计模式去隐藏了下载图片的复杂性,这个nofitifation根本不会去关心这个图片是通过网页下载的还是本地获得的。

编译和运行你的app,你将会看到下面这个美丽的covers在你的HorizentalScroller中。


停止你的app然后再次运行,只一道没有任何的延时在加载covers的时候,你可以断开网络然后你的应用还是运行的完美无瑕。然而你会发现,这个spinning并没有停止运行。这是怎么回事?

你在开始下载的时候开始启动这个spingning,但是你并没有实现在图片开始下载完好的时候去停止它的逻辑。你可以在每次图片已经下载完的时候发送一个notification,但是另外你可以使用另一个观察者模式-KVO。

Key-Value_Observing(KVO)

在KVO中,一个对象可以请求去在一个具体的属性开始变化的时候得到它的一个通知,不论这个属性属于它自己还是另一个对象。在这个例子里面,你可以使用KVO去观察加载image的UIImageView中的这个image属性的改变。

大家AlbumView.m,添加下面这个代码到initWIthFrame:albumCover:中,添加在[selfaddSubview:indicator]之后。

[coverImageaddObserver:selfforKeyPath:@"image"options:0 context:nil];

这添加了self,也当前的类成为coverImage的image属性的一个观察者。

你也需要吧去在你结束的时候取消成为观察者.仍在AlbumView.m中添加下面的代码:

-(void)dealloc  
   
{  
   
    [coverImageremoveObserver:selfforKeyPath:@"image"];  
   
}
  •  




最后,添加这个方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(voidvoid *)context  
   
{  
   
    if ([keyPath isEqualToString:@"image"]) {  
   
        [indicatorstopAnimating];  
   
    }  
   
}


你必须在每个你作为一个观察者的类里面吧去实现这个方法。每次当被观察的属性变化时系统就会执行这个方法,在上面的方法中当image属性改变的时候就会调用这个方法。就这样,当一个图片下载完成,这个spinning就会停止.

编译运行你的工程,这个spinnning 就会消失。


           小贴士:一定要记得去remove你的observers在它们deallocated时,否则当这个对象视图像一个不存在的观察者发送一个消息时那么你的app就会崩溃掉。

如果你运行以下app然后滚动一下这个covers,之后停止运行它,你会注意到app的状态并没有保存下来。你看到的最后一个视图并没有在应用再次启动的时候成为默认的设置。

要改正这一点,你可以充分的利用下一个设计模式:Memento设计模式(备忘录模式)


观察者