OC学习篇之—KVC和KVO操作

OC学习篇 尼古拉斯.赵四 9012℃ 0评论

前一篇文章我们介绍了OC中最常用的文件操作:点击进入,那么今天来看一下OC中的一个比较有特色的知识点:KVC和KVO

一、KVC操作

OC中的KVC操作就和Java中使用反射机制去访问类的private权限的变量,很暴力的,这样做就会破坏类的封装性,本来类中的的private权限就是不希望外界去访问的,但是我们这样去操作,就会反其道而行,但是我们有时候真的需要去这样做,哎。所以说有些事不是都是顺其自然的,而是需要的时候自然就诞生了。

下面就来看一下这种技术的使用:

Dog.h

//
//  Dog.h
//  42_KVC
//
//  Created by jiangwei on 14-10-14.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Dog : NSObject

@end

Dog.m

//
//  Dog.m
//  42_KVC
//
//  Created by jiangwei on 14-10-14.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Dog.h"

@implementation Dog

@end

定义了Dog这个类,但是什么都没有,他只是一个中间类,没什么作用,在这个demo中。

Person.h

//
//  Person.h
//  42_KVC
//
//  Created by jiangwei on 14-10-14.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Dog.h"

@interface Person : NSObject{
@private
    NSString *_name;
    NSDog *_dog;
    
    NSInteger *age;
}

@end

Person.m

//
//  Person.m
//  42_KVC
//
//  Created by jiangwei on 14-10-14.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Person.h"

@implementation Person

- (NSString *)description{
    NSLog(@"%@",_name);
    return _name;
}

@end

Person类中我们定义了两个属性,但是这两个属性对外是不可访问的,而且也没有对应的get/set方法。我们也实现了description方法,用于打印结果

看一下测试代码

main.m

//
//  main.m
//  42_KVC
//
//  Created by jiangwei on 14-10-14.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"

//KVC:很暴力,及时一个类的属性是私有的,而且也没有get/set方法,同样可以读写
//相当于Java中的反射,破坏类的封装性
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *p = [[Person alloc] init];
        
        //设置值
        //这里setValue方法:第一个参数是value,第二个参数是key(就是类的属性名称)
        [p setValue:@"jiangwei" forKey:@"name"];
        
        Dog *dog = [[Dog alloc] init];
        [p setValue:dog forKey:@"dog"];
        
        //KVC设置值时,如果属性有set方法,则优先调用set方法,如果没有则直接设置上去,get方法类似
        
        //读取值
        NSString *name = [p valueForKey:@"name"];
        
        //设置基本数据类型
        //这里需要将基本类型转化成NSNumber
        //在设置值的时候,会有自动解包的过程,NSNumber会解包赋值给age
        [p setValue:@22 forKey:@"age"];
        
        NSLog(@"%@",p);
        
        return 0;
    }
    return 0;
}

这里我们生成一个Person对象,然后开始使用KVC技术了:

1、设置属性值

//设置值
//这里setValue方法:第一个参数是value,第二个参数是key(就是类的属性名称)
[p setValue:@"jiangwei" forKey:@"name"];
        
Dog *dog = [[Dog alloc] init];
[p setValue:dog forKey:@"dog"];

使用setValue方法,就可以进行对属性进行设置值操作了,同时需要传递这个属性的名称,这个和Java中使用反射机制真的很像。

注:KVC设置值时,如果属性有set方法,则优先调用set方法,如果没有则直接设置上去,get方法一样

//设置基本数据类型
//这里需要将基本类型转化成NSNumber
//在设置值的时候,会有自动解包的过程,NSNumber会解包赋值给age
[p setValue:@22 forKey:@"age"];

还有一个需要注意的地方:当我们在设置基本类型的时候,需要将其转化成NSNumber类型的。

2、取属性的值

//读取值
NSString *name = [p valueForKey:@"name"];

取值就简单了

下面再来看一下KVC中强大的功能:键值路径

键值路径是对于一个类中有数组对象的属性进行便捷操作。

看个场景:

一个作者有多本书

Author.h

//
//  Author.h
//  43_KeyValuePath
//
//  Created by jiangwei on 14-10-15.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Author : NSObject{
    NSString *_name;
    
    //作者出版的书,一个作者对应多个书籍对象
    NSArray *_issueBook;
}

@end

作者类中定义了名字和一个书籍数组

Author.m

//
//  Author.m
//  43_KeyValuePath
//
//  Created by jiangwei on 14-10-15.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Author.h"

@implementation Author

@end

Book.h

//
//  Book.h
//  43_KeyValuePath
//
//  Created by jiangwei on 14-10-15.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Author.h"

@interface Book : NSObject{
    Author *_author;
}

@property NSString *name;
@property float *price;

@end

定义了一个作者属性,书的名字,价格

Book.m

//
//  Book.m
//  43_KeyValuePath
//
//  Created by jiangwei on 14-10-15.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Book.h"

@implementation Book

@end

看一下测试代码

main.m

//
//  main.m
//  43_KeyValuePath
//
//  Created by jiangwei on 14-10-15.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Book.h"
#import "Author.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //------------------KVC键值路径
        /*
        Book *book = [[Book alloc] init];
        Author *author = [[Author alloc] init];
        
        //设置作者
        [book setValue:author forKey:@"author"];
        
        //设置作者的名字
        //路径为:author.name,中间用点号进行连接
        [book setValue:@"jiangwei" forKeyPath:@"author.name"];
        NSString *name = [author valueForKey:@"name"];
        NSLog(@"name is %@",name);
         */
        
        
        //--------------------KVC的运算
        Author *author = [[Author alloc] init];
        [author setValue:@"莫言" forKeyPath:@"name"];
        
        Book *book1 = [[Book alloc] init];
        book1.name = @"红高粱";
        book1.price = 9;
        Book *book2 = [[Book alloc] init];
        book2.name = @"蛙";
        book2.price = 10;
        NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];
        [author setValue:array forKeyPath:@"issueBook"];
        
        //基本数据类型会自动被包装成NSNumber,装到数组中
        //得到所有书籍的价格
        NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"];
        NSLog(@"%@",priceArray);
        
        //获取数组的大小
        NSNumber *count = [author valueForKeyPath:@"issueBook.@count"];
        NSLog(@"count=%@",count);
        
        //获取书籍价格的总和
        NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"];
        NSLog(@"%@",sum);
        
        //获取书籍的平均值
        NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"];
        NSLog(@"%@",avg);
        
        //获取书籍的价格最大值和最小值
        NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"];
        NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"];
        
    }
    return 0;
}

1、首先通过前面说到的KVC设置作者的书籍数组

//--------------------KVC的运算
Author *author = [[Author alloc] init];
[author setValue:@"莫言" forKeyPath:@"name"];

Book *book1 = [[Book alloc] init];
book1.name = @"红高粱";
book1.price = 9;
Book *book2 = [[Book alloc] init];
book2.name = @"蛙";
book2.price = 10;
NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];
[author setValue:array forKeyPath:@"issueBook"];

添加了两本书籍

2、下面就开始用到KVC中键值路径了

1)获取作者类中书籍数组中所有书籍的价格

//基本数据类型会自动被包装成NSNumber,装到数组中
//得到所有书籍的价格
NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"];
NSLog(@"%@",priceArray);

看到了:@”issueBook.price” 这就是键值路径的使用,issueBook是作者类中的书籍数组属性名,price是书籍类的属性,中间用点号进行连接,这样我们就可以获取到了所有书籍的价格了,如果在Java中,我们需要用一个循环操作。但是OC中多么方便。

2)获取作者类中书籍数组的大小

//获取数组的大小
NSNumber *count = [author valueForKeyPath:@"issueBook.@count"];
NSLog(@"count=%@",count);

使用 @”issueBook.@count” 键值路径获取书籍数组的大小,issueBook是作者类中的书籍数组属性名,@count是特定一个写法,可以把它想象成一个方法,中间任然用点号进行连接

3)获取作者类中书籍数组的价格总和

//获取书籍价格的总和
NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"];
NSLog(@"%@",sum);

使用 @”issueBook.@sum.price” 键值路径获取书籍数组中的价格总和,issueBook是作者类中的书籍数组属性名,@sum是特性写法,可以把它想象成一个方法,price是书籍的价格属性名,可以把它看成是@sum的一个参数,中间用点号进行连接

如果在java中,这个需要用一个循环来计算总和,OC中很方便的

4)获取作者类中书籍数组的价格平均值、最小值、最大值

//获取书籍的平均值
NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"];
NSLog(@"%@",avg);

//获取书籍的价格最大值和最小值
NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"];
NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"];

操作和上面类似,这里就不解释了

我们看到上面返回来的数据都是NSNumber类型的

二、KVO操作

KVO操作在OC中也是经常会用到的,而且这种机制在java中不存在的。

它的作用就是用来监听类中属性值的变化,实现原理是观察者模式,当然我们也可以使用观察者模式在Java中实现这样的机制

看一下具体的例子:现在有一个小孩类,他有两个属性:开心值,饥饿值,然后还有一个护士类,用来监听孩子类的这两个属性值的

Chidren.h

//
//  Children.h
//  44_KVO
//
//  Created by jiangwei on 14-10-16.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Children : NSObject

@property NSInteger *hapyValue;
@property NSInteger *hurryValue;


@end

Children.m

//
//  Children.m
//  44_KVO
//
//  Created by jiangwei on 14-10-16.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Children.h"

@implementation Children

- (id) init{
    self = [super init];
    if(self != nil){
        //启动定时器
        [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        self.hapyValue= 100;
    }
    return self;
}

- (void) timerAction:(NSTimer *) timer{
    //使用set方法修改属性值,才能触发KVO
    
    int value = _hapyValue;
    [self setHapyValue:--value];
    
    int values = _hurryValue;
    [self setHurryValue:--values];
}


@end

在初始化方法中,我们启动一个定时器,然后隔1s就去修改孩子类的值

Nure.h

//
//  Nure.h
//  44_KVO
//
//  Created by jiangwei on 14-10-16.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@class Children;
@interface Nure : NSObject{
    Children *_children;
}

- (id) initWithChildren:(Children *)children;

@end

定义一个孩子属性

Nure.m

//
//  Nure.m
//  44_KVO
//
//  Created by jiangwei on 14-10-16.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Nure.h"
#import "Children.h"

@implementation Nure

- (id) initWithChildren:(Children *)children{
    self = [super init];
    if(self != nil){
        _children = children;
        
        //观察小孩的hapyValue
        //使用KVO为_children对象添加一个观察者,用于观察监听hapyValue属性值是否被修改
        [_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];
        
        //观察小孩的hurryValue
        [_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];
    }
    return self;
}

//触发方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"%@",change);
    //通过打印change,我们可以看到对应的key
    
    //通过keyPath来判断不同属性的观察者
    if([keyPath isEqualToString:@"hapyValue"]){
        //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
        //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
        //[change objectForKey:@"old"]是修改前的值
        NSNumber *hapyValue = [change objectForKey:@"new"];//修改之后的最新值
        
        NSInteger *value = [hapyValue integerValue];
        
        if(value < 90){
            //do something...
        }
    }else if([keyPath isEqualToString:@"hurryValue"]){
        //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
        //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
        //[change objectForKey:@"old"]是修改前的值
        NSNumber *hurryValue = [change objectForKey:@"new"];//修改之后的最新值
        
        NSInteger *value = [hurryValue integerValue];
        
        if(value < 90){
            //do something...
        }
    }
    
    NSLog(@"%@",context);//打印的就是addObserver方法的context参数
    
    
    
    //使用KVC去修改属性的值,也会触发事件
}

- (void)dealloc{
    
    //移除观察者
    [_children removeObserver:self forKeyPath:@"hapyValue"];
    [_children removeObserver:self forKeyPath:@"hurryValue"];
    
}

@end

看到了在这里就开始进行监听操作了

下面来具体看一下如何做到监听的

1、添加监听对象

我们使用addObserver方法给孩子添加监听对象

第一个参数:监听者,这里是Nure,所以可以直接传递self

第二个参数:监听对象的属性名

第三个参数:监听这个属性的状态:这里可以使用|进行多种组合操作,属性的新值和旧值

第四个参数:传递内容给监听方法

//观察小孩的hapyValue
//使用KVO为_children对象添加一个观察者,用于观察监听hapyValue属性值是否被修改
[_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];

//观察小孩的hurryValue
[_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"context"];

2、监听方法

//触发方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"%@",change);
    //通过打印change,我们可以看到对应的key
    
    //通过keyPath来判断不同属性的观察者
    if([keyPath isEqualToString:@"hapyValue"]){
        //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
        //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
        //[change objectForKey:@"old"]是修改前的值
        NSNumber *hapyValue = [change objectForKey:@"new"];//修改之后的最新值
        
        NSInteger *value = [hapyValue integerValue];
        
        if(value < 90){
            //do something...
        }
    }else if([keyPath isEqualToString:@"hurryValue"]){
        //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
        //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
        //[change objectForKey:@"old"]是修改前的值
        NSNumber *hurryValue = [change objectForKey:@"new"];//修改之后的最新值
        
        NSInteger *value = [hurryValue integerValue];
        
        if(value < 90){
            //do something...
        }
    }
    
    NSLog(@"%@",context);//打印的就是addObserver方法的context参数
    
    
    
    //使用KVC去修改属性的值,也会触发事件
}

我们上面传递的第一个参数是监听者,这个方法也是在监听者中实现的,当属性值发生变化的时候,这个方法会被回调

这个方法的参数:

第一个参数:键值路径

第二个参数:监听对象

第三个参数:变化的值

第四个参数:传递的内容

我们看到代码中有一个特殊的参数:第三个参数:NSDirctionary类型的

其实我们如果不知道是干什么的,我们可以打印一下他的结果看一下,很简单,这里就不截图说明了

我们会发现他有两个键值对

key是:new和old

他们就是分别代表这个属性值变化的前后值,同时他们的得到也和之前我们添加监听对象时设置的第三个参数有关:

NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld

那个地方设置了几种状态,这里的NSDirctionary中就会有几个键值对

3、销毁方法

这个并不属于KVO的内容了,只是在这里用到了就顺便说一下

- (void)dealloc{
    
    //移除观察者
    [_children removeObserver:self forKeyPath:@"hapyValue"];
    [_children removeObserver:self forKeyPath:@"hurryValue"];
    
}

我们在创建一个对象的时候会调用alloc方法,当对象被销毁的时候会调用dealloc这个方法,这个和C++中的析构函数一样,Java中有垃圾回收器,所以没有此类的方法,但是有一个finalize方法,其实这个方法就是在垃圾回收器回收对象的时候会调用,和这个功能差不多,但是在Java中,我们并不提倡使用这个方法。因为会造成GC的回收发生错误。

我们在销毁方法中需要移除监听者

总结

这一篇就介绍了OC中比较有特色的两个机制:KVC和KVO

KVC:就是可以暴力的去get/set类的私有属性,同时还有强大的键值路径对数组类型的属性进行操作

KVO:监听类中属性值变化的

 

关注微信公众号,最新技术实时推送

转载请注明:尼古拉斯.赵四 » OC学习篇之—KVC和KVO操作

喜欢 (0)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址