观察者

观察者模式

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

一般的实现需要一个对象注册成为它感兴趣的状态的观察者,当这个状态改变了,所有的观察者对象都会接收到通知。苹果的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
image的URL,这就是全部的你需要去实现下载任务的信息。

添加下面的代码到LibraryAPI的init方法中,直接在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设计模式(备忘录模式)

        时间: 2024-08-06 07:59:28

        观察者的相关文章

        Ember.js 入门指南——观察者(observer)

        本系列文章全部从(http://ibeginner.sinaapp.com/)迁移过来,欢迎访问原网站. 1,观察者使用 Ember可以察觉所有属性的变化,包括计算属性.观察者是非常有用的,特别是计算属性绑定之后需要同步的时候. 观察者经常被Ember开发过度使用.Ember框架本身已经大量使用观察者,但是对于大多数的开发者面对开发问题时使用计算属性是更适合的解决方案. 使用方式:可以用"Ember.observer"设置一个对象为观察者. // Observer对于Emberjs来说

        属性观察者(监听属性的变化)

        // 属性观察者, 用于监听属性变化, 在属性变化的时候调用 class Person { var name: String = "" { // 也可以写成willSet{}, 系统默认会传入一个名为newValue的参数 willSet(newValue) { print("name这个属性将被修改了, \(newValue)") } // 也可以写成didSet{} didSet(newValue) { print("name这个属性已经被修改了, \

        理解设计模式(观察者)

        观察者模式,保证主题和若干观察者之间的一致性. 概述 有时又被称为发布-订阅Subscribe>模式.模型-视图View>模式.源-收听者Listener>模式或从属者模式 此种模式通常被用来实现事件处理系统 特定环境 一个对象改变,同时引发其他若干对象发生变化时 主动改变的对象和被动变化的对象之间相对独立,以便复用和维护 一类问题 一个系统,由一个对象和若干协作对象组成,同时要避免这些对象之间相互耦合 解决方案 抽象主题角色 具体主题角色 抽象观察者角色 具体观察者角色 抽象主题角色

        Observer(观察者)模式

        1.概述 一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力.当对象们连接在一起时,它们就可以相互提供服务和信息. 通常来说,当某个对象的状态发生改变时,你仍然需要对象之间能互相通信.但是出于各种原因,你也许并不愿意因为代码环境的改变而对代码做大的修改.也许,你只想根据你的具体应用环境而改进通信代码.或者,你只想简单的重新构造通信代码来避免类和类之间的相互依赖与相互从属. 2.问题 当一个对象的状态发生改变时,你如何通知其他对象?是否需要一个动态方案――一个就像允许脚本的执行一样,允

        设计模式——观察者

        观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,他的所有的依赖者都会收到通知并自动更新: 定义主题,主题的功能是持有观察者的引用,提供注册.取消注册和通知,并且有状态:观察者实现接口中的update方法,必须注册具体主题: JDK自带了对观察者的支持功能 被观察者Observable对象,提供add/delete/notifyObserver()和setChanged()设置状态方法 观察者Observer接口,提供update()方法进行相关的更新操作. 观察者类图: 以下程序模拟

        Cocos中的观察者设计模式与通知机制

        观察者(Observer)模式也叫发布/订阅(Publish/Subscribe)模式,是 MVC( 模型-视图-控制器)模式的重要组成部分.天气一直是英国人喜欢讨论的话题,而最近几年天气的变化也成为中国人非常关注的话题.我会根据天气预报决定是坐地铁还是开车上班,我的女儿也会根据天气预报决定明天穿哪件衣服.于是我在移动公司为我的手机定制了天气预报短信通知服务,它的工作模型如图所示. 每天气象局将天气预报信息投送给移动运营商,移动运营商的短信中心负责把天气预报发送给定制过这项服务的手机.在软件系统

        观察者模式分析、结构图及基本代码

         定义:观测者模式定义了一种一对多的依赖关系,让多个观测者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观测者对象,使它们能够自动更新自己. 结构图: Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现.它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者.抽象主题提供一个接口,可以增加和删除观测者. Observe类,抽象观测者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己.这个接口叫更新接口.抽象观察者

        观察者设计模式

        /** * 事件接口即被观察者 * * @author Host * */ publicinterface Event { void attach(Listener listener); void detach(Listener listener); void notifyListener(); } /** * 事件监听者观察者 * * @author Host * */ publicinterface Listener { void execute(); } 事件源的一个实现类(磁盘事件) p

        内容观察者

        在Android项目中我们常常需要检测另一个应用私有数据的变化与否,如何检测另一个数据库应用中的数据变化,我们通过下面的案例来讲述具体的操作. 应用场景:有两个应用,一个是"01_数据库应用",另一个是"07_内容观察者":第二个应用监测第一个应用数据库的变化,当第一个应用数据库发生变化便即时通知第二个应用.实现方法如下: 轮询 若要监测数据库中数据的变化与否,我们可以在当前应用中编写一个定时器,该定时器的工作是每隔一段时间查询数据库中的数据,将查询的结果与之前查询

        内容观察者使用(程序锁应用中的使用)

        1.在Dao层的增删改查方法中,添加一句代码,通知内容观察者,数据库发生改变. context.getContentResolver().notifyChange(Uri.parse("content://applock/change"), null); 参数1:自定义Uri,在注册内容观察者时匹配 2.在需要使用内容观察者的类中(即DogService),注册内容观察者.一旦数据库的数据发生改变,就要调用内容观察者中的onchange方法,在这里就是再一次findAll(). //使