zoukankan      html  css  js  c++  java
  • KVO 的使用和举例

        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;
    }

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

  • 相关阅读:
    LeetCode Best Time to Buy and Sell Stock II
    LeetCode Best Time to Buy and Sell Stock
    LeetCode Word Break
    LeetCode Climbing Stairs
    LeetCode Minimum Path Sum
    LeetCode N-Queens II
    LeetCode N-Queens
    LeetCode Minimum Cost For Tickets
    用mybatis生成插件自动生成配置文件
    log4j.properties文件的配置
  • 原文地址:https://www.cnblogs.com/haibosoft/p/3654641.html
Copyright © 2011-2022 走看看