KVC和KVO

目录:

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

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 概念 先来看看概念,Key-value coding (KVC) 和 key-value observing (K...
    wuwy阅读 1,256评论 0 1
  • 在iOS开发中,我们常常用到键值编码KVC和键值监听KVO两个东东,今天小编和大家分享的就是这两个东东在应用开发中...
    突然自我阅读 976评论 2 3
  • 在编程中,最常见的就是程序的流程取决于你所使用的各种变量和属性的值,根据变量和属性的值确定后面运行的代码,有时会检...
    pro648阅读 1,633评论 2 27
  • KVC和KVO都属于键值编程而且底层实现机制都是isa-swizzing 一.KVC概述 1.kvc 是一种通过(...
    俊俊吖阅读 141评论 0 0
  • 遍历遇到左括号一律进栈,右括号与栈顶的数比较,注意判断栈不能为空,另外返回时也要检查栈是否空
    Genejing阅读 187评论 0 0