OC学习篇之---KVC和KVO操作

前一篇文章我们介绍了OC中最常用的文件操作:http://blog.csdn.net/jiangwei0910410003/article/details/41875015,那么今天来看一下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:@"[email protected]"];
        NSLog(@"count=%@",count);

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

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

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

    }
    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:@"[email protected]"];
NSLog(@"count=%@",count);

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

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

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

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

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

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

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

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

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

我们看到上面返回来的数据都是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:监听类中属性值变化的

时间: 2025-01-02 06:05:31

OC学习篇之---KVC和KVO操作的相关文章

(转载)OC学习篇之---KVC和KVO操作

前一篇文章我们介绍了OC中最常用的文件操作,那么今天来看一下OC中的一个比较有特色的知识点:KVC和KVO 一.KVC操作 OC中的KVC操作就和Java中使用反射机制去访问类的private权限的变量,很暴力的,这样做就会破坏类的封装性,本来类中的的private权限就是不希望外界去访问的,但是我们这样去操作,就会反其道而行,但是我们有时候真的需要去这样做,哎.所以说有些事不是都是顺其自然的,而是需要的时候自然就诞生了. 下面就来看一下这种技术的使用: Dog.h 1 // 2 // Dog.

OC学习篇之---总结和学习目录

今天终于把OC的基础知识学习完了,但是这些知识只是最基础的,还有很多高级知识,这个可能需要后面慢慢的去学习才能体会到.下面就是这次学习OC的目录教程,如果大家发现有什么不正确的地方,请指正,小弟是新生,多请OC老鸟来喷~~ 1.OC学习篇之---概述 2.OC学习篇之---第一个程序HelloWorld 3.OC学习篇之---类的定义 4.OC学习篇之---类的初始化方法和点语法的使用 5.OC学习篇之---类的三大特性(封装,继承,多态) 6.OC学习篇之[email protected]关键

OC学习篇之---单例模式

在之前的一片文章中介绍了对象的拷贝相关知识:http://blog.csdn.net/jiangwei0910410003/article/details/41926531,今天我们来看一下OC中的单例模式,单例模式在设计模式中用的可能是最多的一种了,而且也是最简单的一种 实现单例模式有三个条件 1.类的构造方法是私有的 2.类提供一个类方法用于产生对象 3.类中有一个私有的自己对象 针对于这三个条件,OC中都是可以做到的 1.类的构造方法是私有的 我们只需要重写allocWithZone方法,

(转载)OC学习篇之[email&#160;protected]关键字的作用以及#include和#import的区别

前一篇文章说到了OC中类的三大特性,今天我们来看一下在学习OC的过程中遇到的一些问题,该如何去解决,首先来看一下我们之前遗留的一个问题: 一.#import和#include的区别 当我们在代码中使用两次#include的时候会报错:因为#include相当于拷贝头文件中的声明内容,所以会报重复定义的错误 但是使用两次#import的话,不会报错,所以他可以解决重复导入的问题,他会做一次判断,如果已经导入一次就不导入了 二.关键字@class的作用 在来看一下OC中的关键字@class的作用,在

(转载)OC学习篇之---单例模式

在之前的一片文章中介绍了对象的拷贝相关知识,今天我们来看一下OC中的单例模式,单例模式在设计模式中用的可能是最多的一种了,而且也是最简单的一种 实现单例模式有三个条件 1.类的构造方法是私有的 2.类提供一个类方法用于产生对象 3.类中有一个私有的自己对象 针对于这三个条件,OC中都是可以做到的 1.类的构造方法是私有的 我们只需要重写allocWithZone方法,让初始化操作只执行一次 2.类提供一个类方法产生对象 这个可以直接定义一个类方法 3.类中有一个私有的自己对象 我们可以在.m文件

OC学习篇之[email&#160;protected]关键字的作用以及#include和#import的区别

前一篇文章说到了OC中类的三大特性:http://blog.csdn.net/jiangwei0910410003/article/details/41707161今天我们来看一下在学习OC的过程中遇到的一些问题,该如何去解决,首先来看一下我们之前遗留的一个问题: 一.#import和#include的区别 当我们在代码中使用两次#include的时候会报错:因为#include相当于拷贝头文件中的声明内容,所以会报重复定义的错误 但是使用两次#import的话,不会报错,所以他可以解决重复导入

(转载)OC学习篇之---通知(NSNotificationCenter)

在前一篇文章中我们介绍了OC中很常用的两个技术:KVC和KVO,今天我们来看一下OC中另外的一个常用技术:通知(Nofitication) 其实这里的通知和之前说到的KVO功能很想,也是用于监听操作的,但是和KVO不同的是,KVO只用来监听属性值的变化,这个发送监听的操作是系统控制的,我们控制不了,我们只能控制监听操作,类似于Android中系统发送的广播,我们只能接受.但是通知就不一样了,他的监听发送也是又我们自己控制,我们可以在任何地方任何时机发送一个通知,类似于Android中开发者自己发

OC学习篇之---概述

前言 终于开启了OC的学习篇了,之前由于工作上的事,学习就一直搁浅了,不过最近由于各种原因,感觉必须要开启iOS的开发旅程了,不然就老了.因为之前一直是做Android的,所以学习iOS来就没那么费劲了,当然我们知道,Android是Java语言支撑的,iOS是OC支撑的,关于OC的学习,会和Java相对比这来,这样效率也会高点,同时在大学里学了C/C++所以,学习OC就没什么难度了,就是一套新的api. 概述 目前来说,Objective-C(简称OC)是iOS开发的核心语言,在开发过程中也会

ios开发runtime学习五:KVC以及KVO,利用runtime实现字典转模型

一:KVC和KVO的学习 #import "StatusItem.h" /* 1:总结:KVC赋值:1:setValuesForKeysWithDictionary实现原理:遍历字典,得到所有的key,value值,再利用kvc, setVaue forkey来为value赋值 2: [item setValue:@"来自即刻笔记" forKey:@"source"],内部的底层实现, 1.首先去模型中查找有没有setSource,找到,直接调用