KVO 的使用和举例

系统 1721 0

    KVO(key-value Observer),通过命名可以联想到,一个监视着监视着键值配对,让一个对象A来监视另一个对象B中的键值,一旦B中的受监视键所对应的值发生了变化,对象A会进入一个回调函数,有机会对于B中的受监视键值的改变立刻进行处理和应对。

    注:虽然对象A中的回调函数有点像代理方法,但是回调函数的调用和键值发生变化处在同一个线程中,并非像某些代理方法会在另一个线程中进行回调。也就是说,如果对键key进行了监视,一旦键key对应的值发生了变化,就会去调用监视着的回调函数,直到回调函数跑完后键key对应值发生变化的流程才能继续。

    好处就是减少胶水代码。

    比如比赛比分发生了变化,如果我们不用KVO机制,我们需要告诉大屏幕控制人员,告诉网络媒体,告诉广播电台播音员,甚至告诉其他赛场的工作人员。

  

一个简单的KVO机制的程序

导航栏有三个元素,左边的编辑按钮,用来删除表的记录,右边的“+”按钮,用来新增表的记录,而当中的标题,用来显示最近的一次动作。开发思路大致为这样:

   表视图有一个数据源dataSource,我们需要利用kVO机制去监视这个数据源,当按下“+”按钮时往数据源中添加一条数据,触发KVO,随后在KVO的回调函数中,我们将界面更新成和数据源同步。

   当删除一条数据时,数据源减少一条数据,同样触发KVO并在随后KVO的回调函数中,将界面更新同步。

总体来说,无论对数据源做任何操作,我们都会在KVO的回调函数中,进行程序界面和数据源的同步工作,代码如下:

      @interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>

{

    IBOutlet UITableView *_tbv;

}



//遵循KVC的编码规范

@property (nonatomic,retain) NSMutableArray *dataSrc;

@property (nonatomic,retain) NSString *titleMsg;



//提供KVC中对于容器键属性(dataSrc)的接口

-(NSUInteger)countOfDataSrc;

-(void)insertObject:(id)object inDataSrcAtIndex:(NSUInteger)index;

-(id)objectInDataSrcAtIndex:(NSUInteger)index;

-(void)removeObjectFromDataSrcAtIndex:(NSUInteger)index;

@end


    

 上述代码中一共声明了两个属性变量:dataSrc作为数据源,titleMsg作为标题

由于数据源dataSrc是属于容器类型的数据,根据KVC协议需要申明并实现数组形式的几个方法

协议的时间内容中直接使用可变数组提供的功能,对上述四个接口进行实现,代码如下:

      //集合属性的个数

-(NSUInteger)countOfDataSrc

{

    return [self.dataSrc count];

}



//集合属性的新增动作

-(void)insertObject:(id)object inDataSrcAtIndex:(NSUInteger)index

{

    [self.dataSrc insertObject:object atIndex:index];

}



//集合属性的取值动作

-(id)objectInDataSrcAtIndex:(NSUInteger)index

{

    return [self.dataSrc objectAtIndex:index];

}



//集合属性的删除动作

-(void)removeObjectFromDataSrcAtIndex:(NSUInteger)index

{

    [self.dataSrc removeObjectAtIndex:index];

}


    

 至此KVC的准备工作都做完了,继续实现KVO机制,对于界面的初始化进口位置,作如下初始化的设置

      - (void)viewDidLoad

{

    [super viewDidLoad];

	// Do any additional setup after loading the view, typically from a nib.

    //初始化表视图(UITableView)的数据

    self.dataSrc = [[NSMutableArray alloc]initWithCapacity:0];

    self.titleMsg = @"没有动作";

    _tbv=[[UITableView alloc]init];

    //

    //对表视图的数据进行监视

    //

    //谁来监视,KVO的监视回调函数就调用谁

    [self addObserver:self

     //监视的键的路径,我们这里的属性由于只有一层,所以直接写dataSrc

           forKeyPath:@"dataSrc"

     //需要知道表数据改动时的新旧数据,方便我们研究,如果不需要,可以置为0

              options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld

     //KVO 触发时,我们收到的额外信息,如果不需要可以置为nil

              context:@"testContent"];

    

    [self addObserver:self forKeyPath:@"titleMsg" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"testContent"];

    

    //右边的按钮,我们放增加

    UIBarButtonItem *addButton=[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(add)];

    self.navigationItem.rightBarButtonItem=addButton;

    

    //左边的按钮,我们放编辑,主要提供删除功能

    //初始化没有数据,所以我们disable掉“编辑”按钮

    self.navigationItem.leftBarButtonItem=self.editButtonItem;

    self.navigationItem.leftBarButtonItem.enabled=NO;

    self.editButtonItem.title=@"编辑";

    

    //标题

    self.navigationItem.title=self.titleMsg;

    

    _tbv.delegate=self;

    [self.view addSubview:_tbv];

}


    

 然后写上必须释放的方法

      -(void)dealloc

{

    [self removeObserver:self forKeyPath:@"dataSrc"];

    [self removeObserver:self forKeyPath:@"titleMsg"];

}


    

 随后当用户点击“+”按钮时,新增的处理函数如下:

      
        //
      
      
        导航栏上增加按钮的调用方法
      
      

-(
      
        void
      
      
        )add

{

    
      
      
        //
      
      
        我们打算设置一个静态的整形记录当前的排序值
      
      
        static
      
      
        int
      
       myIndex=
      
        0
      
      
        ;

    

    
      
      
        //
      
      
        每次进来,我们就把当前的排序值作为新增的对象

    
      
      
        //
      
      
        所以调用KVO提供的新增接口,插入新元素的位置始终位于最后
      
      

    [self insertObject:[NSString stringWithFormat:
      
        @"
      
      
        %d
      
      
        "
      
      
        ,myIndex] inDataSrcAtIndex:[self countOfDataSrc]];

    

    myIndex
      
      ++
      
        ;

    

    self.titleMsg
      
      =[NSString stringWithFormat:
      
        @"
      
      
        新增:%d
      
      
        "
      
      
        ,myIndex];

}
      
    

当用户点击“编辑”按钮时,被调用的系统默认的方法进行重写

      
        //
      
      
        当用户单击“编辑”按钮时,对被调用的系统默认方法进行重写
      
      

-(
      
        void
      
      
        )setEditing:(BOOL)editing animated:(BOOL)animated

{

    
      
      
        //
      
      
        UIViewController 提供的editButtonItem 默认会调用此方法

    
      
      
        //
      
      
        所以我们重写此方法,第一步就是让表视图变成编辑状态,供我们删除内容用
      
      
            

    [_tbv setEditing:editing animated:animated];

    

    
      
      
        //
      
      
        第二步让super继续操作

    
      
      
        //
      
      
        目的是不改变UIViewController对于editButtonItem原有的动作

    
      
      
        //
      
      
        如果不加,那就是等于我们将这个方法截获了

    
      
      
        //
      
      
        效果不同体现在:editButtonItem不会在Edit状态和Done状态之间切换
      
      
            [super setEditing:editing animated:animated];

    

    
      
      
        if
      
      
        (editing)

    {

        self.editButtonItem.title
      
      =
      
        @"
      
      
        完成
      
      
        "
      
      
        ;

    }

    
      
      
        else
      
      
        

    {

        self.editButtonItem.title
      
      =
      
        @"
      
      
        编辑
      
      
        "
      
      
        ;

    }

}
      
    

当用户按下“Delete”后,作为表视图的代理,“tableView:commitEditingStyle:forRowAtIndexPath:”,这个代理方法将会被调用,所以需要实现如下代码:

      -(
      
        void
      
      )tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *
      
        )indexPath

{

    
      
      
        if
      
      (editingStyle==
      
        UITableViewCellEditingStyleDelete)

    {

        self.titleMsg
      
      =[NSString stringWithFormat:
      
        @"
      
      
        删除:[%d]
      
      
        "
      
      
        ,indexPath.row];

        [self removeObjectFromDataSrcAtIndex:indexPath.row];

    }

}
      
    

KVO所触发的回调函数的实现方式

      
        //
      
      
        KVO监视某个属性时,当属性发生变化会受到此回调
      
      

-(
      
        void
      
      )observeValueForKeyPath:(NSString *)keyPath ofObject:(
      
        id
      
      )
      
        object
      
       change:(NSDictionary *)change context:(
      
        void
      
       *
      
        )context

{

    
      
      
        if
      
      ([keyPath isEqualToString:
      
        @"
      
      
        titleMsg
      
      
        "
      
      
        ])

    {

        [self handleTitleChangeofObject:
      
      
        object
      
      
        

                                 change:change

                                context:context];

        
      
      
        return
      
      
        ;

    }

    

    NSInteger changeRow
      
      =
      
        0
      
      
        ;

    
      
      
        //
      
      
        NSKeyValueChangeIndexesKey键中记录了集合属性改变位置等重要信息
      
      

    NSIndexSet *indices=
      
        [change objectForKey:NSKeyValueChangeIndexesKey];

    

    
      
      
        if
      
      
        (indices)

    {

        
      
      
        //
      
      
        我们每次只改集合中的一处地方,所以我们可以用firstIndex来简单的取出改变的地方

        
      
      
        //
      
      
        如果时多处地方遭到修改,需要使用NSindexSet类提供的getIndexes方法
      
      

        changeRow=
      
        indices.firstIndex;

    }

    

    
      
      
        //
      
      
        制作NSIndexPath,为了提供给表视图进行UI更新
      
      

    NSIndexPath *changeIndexPath=[NSIndexPath indexPathForRow:changeRow inSection:
      
        0
      
      
        ];

    

    
      
      
        //
      
      
        NSKeyValueChangeKindKey信息中记录了监视属性的值变化类型
      
      

    NSNumber *kind=
      
        [change objectForKey:NSKeyValueChangeKindKey];

    
      
      
        switch
      
      
         ([kind intValue]) {

        
      
      
        case
      
      
         NSKeyValueChangeInsertion:

            
      
      
        //
      
      
        此新增方法后,表视图重绘
      
      
                    [_tbv insertRowsAtIndexPaths:[NSArray arrayWithObjects:changeIndexPath] withRowAnimation:UITableViewRowAnimationFade];

            
      
      
        break
      
      
        ;

         
      
      
        case
      
      
         NSKeyValueChangeRemoval:

            
      
      
        //
      
      
        次删除方法后,表视图会重绘
      
      
                    [_tbv deleteRowsAtIndexPaths:[NSArray arrayWithObjects:changeIndexPath] withRowAnimation:UITableViewRowAnimationFade];

            
      
      
        break
      
      
        ;

        
      
      
        default
      
      
        :

            
      
      
        break
      
      
        ;

    }

    

    
      
      
        //
      
      
        控制编辑按钮

    
      
      
        //
      
      
        如果表数据有记录
      
      
        if
      
      ([self countOfDataSrc]>
      
        0
      
      
        )

    {

        
      
      
        //
      
      
        让编辑按钮可用
      
      

        self.navigationItem.leftBarButtonItem.enabled=
      
        YES;

    }

    
      
      
        else
      
      
        

    {

        
      
      
        //
      
      
        让编辑按钮不可用,并且遵循UIVievController对于不可用时的UI处理(比如变成edit)
      
      
                [self setEditing:NO animated:YES];

        self.navigationItem.leftBarButtonItem.enabled
      
      =
      
        NO;

    }

    

}
      
    

下列代码则是对于界面标题的更新代码

      -(
      
        void
      
      )handleTitleChangeofObject:(
      
        id
      
      )
      
        object
      
      
        

                          change:(NSDictionary 
      
      *
      
        )change

                         context:(
      
      
        void
      
       *
      
        )context

{

    self.navigationItem.title
      
      =
      
        self.titleMsg;

}
      
    

剩下的表视图的实现方法就不贴了

KVO 的使用和举例


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论