目录:
1.KVC用法;
2.KVC和对象的setter、getter方法的区别;
3.key和keyPath的区别;
4.KVC进行求和,求平均值等操作;
5.KVO的用法;
6.根据KVO底层原理自己实现KVO
一.KVC
1.KVC用法(很简单,不详细介绍)
KVC也就是key-value-coding(键值编码),简而言之就是通过key值去进行赋值和取值。主要是是操作对象的属性。以下是几个常用的方法:
setValue:forKey:(为对象的属性赋值)
setValue: forKeyPath:(为对象的属性赋值(包含了setValue:forKey:的功能,并且还可以对对象内的类的属性进行赋值))
valueForKey:(根据key取值)
valueForKeyPath:(根据keyPath取值)
setValuesForKeysWithDictionary:(对模型进行一次性赋值)
2.KVC和对象的setter、getter方法的区别
一般情况下,KVC和setter、getter应该说都能达到对对象属性的赋值,并且KVC操作也是去调用的setter方法和getter方法(针对一些已经在.h中声明的属性而言)。但是对于一些私有属性,那么这个时候setter、getter方法就没有用了,这个时候KVC却能发挥重要优势。
例如:在Person.m中
#import "Person.h"
@implementation Person
{
NSInteger _height;
}
@end
此时你会发现setter、getter已经无能为力了,但是KVC去可以实现赋值、取值
[p setValue:@170 forKey:@"height"];
3.key和keyPath的区别
keyPath方法是集成了key的所有功能,也就是说对一个对象的一般属性进行赋值、取值,两个方法是通用的,都可以实现。但是对对象中的对象进的属性行赋值,只有keyPath能够实现。
setValuesForKeysWithDictionary:的巧妙使用(字典转模型)
-(instancetype)initWithDict:(NSDictionary *)dict{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
4.KVC进行求和,求平均值等操作
Person.h
#import "Father.h"
#import "Book.h"
@interface Person : NSObject {
@public
NSString *_fullName;
@private
NSString *_name;
Father *_father;
NSArray *_books;
}
@end
Father.h
@interface Father : NSObject {
@protected
NSString *_name;
}
@end
Book.h
#import
@interface Book : NSObject {
@private
NSString *_name;
float _price;
}
@end
使用代码:
#import
#import "Person.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
Person *person = [[Person alloc] init];
//直接访问public变量
person->_fullName = @"ALI TOM";
NSLog(@"_fullName :%@",person->_fullName);
//KVC方式
[person setValue:@"TOM" forKey:@"_name"];
NSLog(@"_name :%@", [person valueForKey:@"_name"]);
Father *father = [[Father alloc] init];
[father setValue:@"JACK" forKey:@"_name"];
[person setValue:father forKey:@"_father"];
//KVC路径访问
NSLog(@"father.name :%@", [person valueForKeyPath:@"_father._name"]);
[person setValue:@"JERRY" forKeyPath:@"_father._name"];
NSLog(@"father.name :%@", [person valueForKeyPath:@"_father._name"]);
NSMutableArray *bookArray = [NSMutableArray arrayWithCapacity:3];
for (int i=0; i<3; i++) {
Book *book = [[Book alloc] init];
NSString *bookName = [NSString stringWithFormat:@"book%d", i];
[book setValue:bookName forKey:@"_name"];
[book setValue:@((i + 1) * 10.2) forKey:@"_price"];
[bookArray addObject:book];
[book release];
}
[person setValue:bookArray forKey:@"_books"];
//KVC计算
//通过@count获取集合book个数
NSNumber *bookCount = [person valueForKeyPath:@"_books.@count"];
NSLog(@"book count :%@", bookCount);
//价格总和
NSNumber *sum = [person valueForKeyPath:@"_books.@sum._price"];
NSLog(@"sum :%@", sum);
//价格平均值
NSNumber *avg = [person valueForKeyPath:@"_books.@avg._price"];
NSLog(@"vag :%@", avg);
//最低价格
NSNumber *min = [person valueForKeyPath:@"_books.@min._price"];
NSLog(@"min :%@", min);
//最高价格
NSNumber *max = [person valueForKeyPath:@"_books.@max._price"];
NSLog(@"max :%@", max);
二.KVO
1.KVO的用法
KVO也就是key-value-observing(即键值观察),利用一个key来找到某个属性并监听其值得改变。用法如下:
添加观察者
在观察者中实现监听方法,observeValueForKeyPath: ofObject: change: context:(通过查阅文档可以知道,绝大多数对象都有这个方法,因为这个方法属于NSObject)
移除观察者
//让对象b监听对象a的name属性
//options属性可以选择是哪个
/* NSKeyValueObservingOptionNew =0x01, 新值
* NSKeyValueObservingOptionOld =0x02, 旧值
*/
[a addObserver:b forKeyPath:@"name"options:kNilOptionscontext:nil];
a.name = @"zzz";
#pragma mark - 实现KVO回调方法
/* * 当对象的属性发生改变会调用该方法
* @param keyPath 监听的属性
* @param object 监听的对象
* @param change 新值和旧值
* @param context 额外的数据
*/
- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary*)change context:(void *)context{
NSLog(@"%@的值改变了,",keyPath);
NSLog(@"change:%@", change);
}
//最后不要忘记了,去移除observer
- (void)dealloc{
[a removeObserver:b forKeyPath:@"name"];
}
KVO底层(这部分涉及到了runtime,关于isa指针,会在随后的简述中介绍)
当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。
具体实现图如下
1.当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
2.派生类在被重写的 setter 方法中实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
3.同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
2.根据KVO底层原理自己实现KVO
#import "NSObject+HKKVO.h"
#import@implementation NSObject (QLKVO) //给NSObject增加分类
//自定义的KVO
-(void)QL_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
//1.动态生成一个类!!
//1.1获取类名
NSString * oldClassName = NSStringFromClass([self class]);
NSString * newClassName = [@"QLKVO_" stringByAppendingString:oldClassName];
const char * name = [newClassName UTF8String];
//动态创建一个子类
Class MyClass = objc_allocateClassPair([self class], name, 0);
//添加方法
class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");
//注册类
objc_registerClassPair(MyClass);
//NSLog(@"%@", [self class]); 会输出:Person
//修改isa,修改完后self变成了子类
object_setClass(self, MyClass);
//NSLog(@"%@", [self class]); 会输出:hkKVO_Person
//保存观察者对象,这里的self指的是子类
objc_setAssociatedObject(self, @"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
void setName(id self,SEL _cmd,NSString * newName){
NSLog(@"我监听到了!!");
id class = [self class];
//拿到观察者
id objc = objc_getAssociatedObject(self, @"objc");
//改变self的isa指针,指向父类
object_setClass(self, class_getSuperclass(class));
//调用父类的set方法!!
objc_msgSend(self, @selector(setName:),newName);
// NSLog(@"修改完毕!!");
//通知观察者
objc_msgSend(objc, @selector(observeValueForKeyPath:ofObject:change:context:),self,@"name",nil,nil);
//改回子类类型(如果不改,self就指向了父类,下次父类的name属性更改的,就不会调用到这个函数里面去)
object_setClass(self, class);
}
#import "NSObject+HKKVO.h"
@interface ViewController ()
/** */@property(nonatomic,strong)Person * p;
@end@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person * p = [[Person alloc]init];
_p = p;
//使用自定义的KVO来监听!Person 的 name 属性
[p hk_addObserver:self forKeyPath:@"name" options:0 context:nil];
NSLog(@"%@",[p class]);
}
//监听到了就来了!!
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
NSLog(@"哥么来了!!!%@",_p.name);
}
//点击就改变!!
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
static int i = 0;
i++;
_p.name = [NSString stringWithFormat:@"%d",i];
}