目录:
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];
}