KVC和KVO

作者: 伦伦子_f7b3 | 来源:发表于2017-09-06 17:25 被阅读60次

目录:

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

}

相关文章

网友评论

      本文标题:KVC和KVO

      本文链接:https://www.haomeiwen.com/subject/yxfvjxtx.html