iOS开发之KVC和KVO原理解析和实例分析

本文详细介绍了iOS开发中的KVC(键值编码)和KVO(键值观察)机制,包括它们的工作原理和实际应用。通过示例代码展示了KVO如何基于KVC工作,以及如何使用KVO来监听对象属性变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在iOS开发中,苹果提供了许多机制给我们进行回调,代理,通知,block等。其中KVO(key-value-observing)是一种很实用的监听回调机制,KVO又基于KVC(key-value-coding)。

1. KVC
KVC就是键值编码,可以对私有变量进行赋值。主要通过isa-swizzling(类型混合指针机制),来实现其内部查找定位的。isa指针维护分发表的对象的类,该分发表实际上包含了指向实现类中的方法的指针,和其它数据。

[person setValue:@"myName" forKey:@"name"];

这行代码的底层实现原理如下:

  • 首先系统会通过isa指针找到setName方法,通过setter方法进行设置;
  • 如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作;
  • 如果上面的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。

2. KVO

KVO就是键值观察,当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。
而给监听的属性赋值正好就是KVC要做的事情,所以说KVO是基于KVC实现的。

为了进一步探索KVO的原理,代码亲测如下:

NSLog(@"address: %p", self.tableView);
NSLog(@"class method: %@", self.tableView.class);
NSLog(@"description method: %@", self.tableView);
NSLog(@"get class: %@", object_getClass(self.tableView));
[self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil];
NSLog(@"===================================================");
NSLog(@"address: %p", self.tableView);
NSLog(@"class method: %@", self.tableView.class);
NSLog(@"description method: %@", self.tableView);
NSLog(@"get class %@", object_getClass(self.tableView));

输出结果:

2017-12-7 15:02:33.216 AndyTest[1287:54832] address: 0x7f927a81d200
2017-12-7 15:02:33.216 AndyTest[1287:54832] class method: UITableView
2017-12-7 15:02:33.216 AndyTest[1287:54832] description method: <UITableView: 0x7f927a81d200; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7f927971f9a0>; layer = <CALayer: 0x7f9279706f50>; contentOffset: {0, 0}; contentSize: {600, 0}>
2017-12-7 15:02:33.216 AndyTest[1287:54832] get class: UITableView
2017-12-7 15:02:33.216 AndyTest[1287:54832]  ===================================================
2017-12-7 15:02:33.216 AndyTest[1287:54832] address: 0x7f927a81d200
2017-12-7 15:02:33.216 AndyTest[1287:54832] class method: UITableView
2017-12-7 15:02:33.216 AndyTest[1287:54832] description method: <UITableView: 0x7f927a81d200; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7f927971f9a0>; layer = <CALayer: 0x7f9279706f50>; contentOffset: {0, 0}; contentSize: {600, 0}>
2017-12-7 15:02:33.216 AndyTest[1287:54832]  get class NSKVONotifying_UITableView

除了通过 object_getClass 获取的类型之外,其他都是一样的。这也就说明了 NSKVONotifying_UITableView就是isa指针指向的派生类。

3. 实例应用

我们假设在一个界面上用文字属性展示人物的年龄,我们给这个年龄添加一个监听者,当点击屏幕时年龄发生变化,我们就让展示的文字属性值跟随改变。

person类的.h

@interface Person : NSObject
/** age */
@property(nonatomic) NSInteger age;

@end

视图控制器的.m

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()
/** person */
@property(nonatomic, strong) Person * person;
/** 展示的文字 */
@property(nonatomic, strong) UILabel *personAge;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建person对象,并对年龄年龄的变化添加监听
    Person *person = [Person new];
    person.age = 10;
    self.person = person;

    UILabel *personAge = [UILabel new];
    personAge.frame = CGRectMake(100, 100, 100, 100);
    personAge.text = [NSString stringWithFormat:@"%zd",person.age];
    [self.view addSubview:personAge];
    self.personAge = personAge;
    //添加观察者
    [self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
    
}
/** 点击屏幕出发年龄改变 */
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person.age = 20;
    //KVC
    //[self.person setValue:@20 forKey:@"age"];
}
/** 在观察者中实现监听方法 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    /**
     NSLog(@"keyPath=%@,object=%@,change=%@,context=%@",keyPath,object,change,context);
     
     keyPath=age,object=<Person: 0x7fe64af086e0>,change={
     kind = 1;
     new = 20;
     },context=(null)
     */
    NSLog(@"keyPath=%@,object=%@,change=%@,context=%@",keyPath,object,change,context);
    
    //这里需要将NSNumber类型转换为字符串类型
    NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
     NSString *ageStr = [numberFormatter stringFromNumber:[change objectForKey:@"new"]];
    self.personAge.text = ageStr;
}

@end

最后要像通知一样,要在dealloc方法里面移除监听

- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"age"];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值