洋洋洒洒又一年

[iOS] KVC KVO

2016-04-01  本文已影响211人  TYM

KCV

KVC的使用

KVC 动态设置

    People *bPel = [People new];
    // 单层设值
    [bPel setValue:@"谭谭谭" forKey:@"name"];
    [bPel setValue:@(20) forKey:@"age"];
    People *bPel = [People new];
    bPel.dog = [[Dog alloc] init];
    [bPel setValue:@(20) forKeyPath:@"age"];
    [bPel setValue:@"啊啊啊" forKeyPath:@"dog.name"];
    [bPel setValue:@(300) forKeyPath:@"dog.price"];
    @implementation People
    {
        double _hight;
        @private int _score;
    }
    // 创建people对象
    People *bbPel = [People new];
    // 单层设值
    [bbPel setValue:@"我我我" forKey:@"_name"];
    // 私有成员设置
    [bbPel setValue:@"170.0" forKey:@"_hight"];
    [bbPel setValue:@"99" forKey:@"_score"];
    // 类的.h文件中写的一个方法用来看是否设置了私有成员,打印操作
    [bbPel print];
#import <Foundation/Foundation.h>
@class Dog;
@interface PeopleModel : NSObject
/** 名字*/
@property (nonatomic, copy) NSString *name;
/** 年龄*/
@property (nonatomic, assign) float height;
/** 狗*/
@property (nonatomic, strong) Dog *dog;
@end
 
- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建字典
    NSDictionary *dict = @{
                           @"name":@"Sam",
                           @"height":@(1.75),
                           @"dog": @{
                                   @"name":@"WangCai"
                                   }
                           };
    PeopleModel *model = [PeopleModel new];
    // 字典转模型
    [model setValuesForKeysWithDictionary:dict];
    NSLog(@"%@", model);
    // 需要注意的是,model里面的dog应该是字典类型而不是dog类型
    // 因为setValuesForKeysWithDictionary:方法不能对对象属性进行转换
}

KVC 动态读取

NSString *aName = [aPel valueForKey:@"name"];
NSLog(@"%@",aName);
NSString *dogName = [aPel valueForKeyPath:@"dog.name"];
NSLog(@"%@",dogName);
int age = [aPel valueForKey:@"_age"];
NSDictionary *dicModel = [aPel dictionaryWithValuesForKeys:@[@"name",@"age"]];
NSLog(@"%@",dicModel);
    People *ppA = [People new];
    ppA.name = @"aaa";
    ppA.age = 1;
    ppA.dog = [Dog new];
    [ppA setValue:@"wangcaiA" forKeyPath:@"dog.name"];
    
    People *ppB = [People new];
    ppB.name = @"bbb";
    ppB.age = 2;
    ppB.dog = [Dog new];
    [ppB setValue:@"wangcaiB" forKeyPath:@"dog.name"];
    
    People *ppC = [People new];
    ppC.name = @"ccc";
    ppC.age = 3;
    ppC.dog = [Dog new];
    [ppC setValue:@"wangcaiC" forKeyPath:@"dog.name"];
    
    People *ppD = [People new];
    ppD.name = @"ddd";
    ppD.age = 4;
    ppD.dog = [Dog new];
    [ppD setValue:@"wangcaiD" forKeyPath:@"dog.name"];
    
    NSArray *arr = @[ppA,ppB,ppC,ppD];
    
    NSArray *nameArr = [arr valueForKeyPath:@"dog.name"];
    NSLog(@"%@", nameArr);
    People *ppA = [People new];
    ppA.name = @"aaa";
    ppA.age = 1;
    ppA.dog = [Dog new];
    [ppA setValue:@"wangcaiA" forKeyPath:@"dog.name"];
    
    People *ppB = [People new];
    ppB.name = @"bbb";
    ppB.age = 2;
    ppB.dog = [Dog new];
    [ppB setValue:@"wangcaiB" forKeyPath:@"dog.name"];
    
    People *ppC = [People new];
    ppC.name = @"ccc";
    ppC.age = 3;
    ppC.dog = [Dog new];
    [ppC setValue:@"wangcaiC" forKeyPath:@"dog.name"];
    
    People *ppD = [People new];
    ppD.name = @"ddd";
    ppD.age = 4;
    ppD.dog = [Dog new];
    [ppD setValue:@"wangcaiD" forKeyPath:@"dog.name"];
    
    NSArray *arr = @[ppA,ppB,ppC,ppD];
    
    NSString *maxName = [arr valueForKeyPath:@"@max.dog.name"];
    NSLog(@"%@", maxName);
    
    int maxAge = [[arr valueForKeyPath:@"@max.age"] intValue];
    NSLog(@"%d", maxAge);

    // 输出结果
    wangcaiD
    4

KVC的原理

KVC使用起来比较简单,但是它如何查找一个属性进行读取呢?具体查找规则(假设现在要利用KVC对a进行读取):

KVC的使用场景


KVO

在ObjC中使用KVO操作常用的方法

  - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
    - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context

KVO的使用步骤

  1. 通过addObserver: forKeyPath: options: context:为被监听对象(它通常是数据模型)注册监听器
  2. 重写监听器的observeValueForKeyPath: ofObject: change: context:方法
  3. 删除监听器

KVO的注意事项

// KVO只能监听到setter方法
[p setAge:998];
p.age = 998;
//这不是setter方法,KVO监听不到
p->_age = 998;

KVO实例

假设当商品(模型)的price价格发送改变的时候,我们商品的ShopView展示页面(视图)可以及时作出响应。那么此时ShopItemModel就作为我们的被监听对象,需要ShopVIew为它注册监听,而商品展示页面ShopVIew作为监听器需要重写它的observeValueForKeyPath: ofObject: change: context:方法,当监听的余额发生改变后会回调监听器ShopVIew监听方法(observeValueForKeyPath: ofObject: change: context:)。下面通过代码模拟上面的过程:

ShopItemModel.h

#import <Foundation/Foundation.h>

@interface ShopItemModel : NSObject

/** 商品名称*/
@property (nonatomic, copy) NSString *name;
/** 价格*/
@property (nonatomic, assign) float price;

@end

ShopItemModel.h

#import "ShopItemModel.h"

@implementation ShopItemModel

@end

ShopView.h

#import <UIKit/UIKit.h>
@class ShopItemModel;
@interface ShopView : UIView

/** 数据模型*/
@property (nonatomic, strong) ShopItemModel *item;

@end

ShopView.m

#import "ShopView.h"
#import "ShopItemModel.h"

@interface ShopView ()

/** 标签*/
@property (nonatomic, weak) UILabel *tLabel;

@end

@implementation ShopView

-(instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor lightGrayColor];
        // 添加一个标签
        UILabel *label = [[UILabel alloc] init];
        label.textAlignment = NSTextAlignmentCenter;
        label.text = @"价格";
        [self addSubview:label];
        self.tLabel = label;
    }
    return self;
}

-(void)layoutSubviews {
    [super layoutSubviews];
    
    CGSize size = self.frame.size;
    CGFloat labHeight = size.height * 0.5;
    CGFloat labY = (size.height - labHeight) * 0.5;
    self.tLabel.frame = CGRectMake(0, labY, size.width, labHeight);
}

#pragma mark 设置商品模型
-(void)setItem:(ShopItemModel *)item {
    _item = item;
    
    self.tLabel.text = [NSString stringWithFormat:@"%@价格为:%f", self.item.name, self.item.price];
    
    // 注册监听
    [self.item addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"商品界面"];
}

#pragma mark 重写observeValueForKeyPath方法,当商品价格变化后此处获得通知
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"price"]) {
        NSLog(@"keyPath = %@, object = %@, change = %@, context = %@", keyPath, object, change, context);
        self.tLabel.backgroundColor = [UIColor redColor];
        self.tLabel.text = [NSString stringWithFormat:@"%@价格为:%f", self.item.name, self.item.price];
        NSLog(@"%@知道价格发生变化了,价格为%f", self, self.item.price);
    }
}
#pragma mark 重写销毁方法
-(void)dealloc {
    // 移除监听
    [self.item removeObserver:self forKeyPath:@"price"];
}

ViewController.m

#import "ViewController.h"

#import "ShopView.h"
#import "ShopItemModel.h"

@interface ViewController ()

/** 商品view*/
@property (nonatomic, weak) ShopView *shopView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建商品模型
    ShopItemModel *item = [[ShopItemModel alloc] init];
    item.name = @"包包";
    item.price = 20000;

    // 创建商品视图
    ShopView *sV = [[ShopView alloc] init];
    sV.frame = CGRectMake(0, 200, self.view.frame.size.width, 200);
    // 设置商品视图的模型
    sV.item = item;
    // 添加到界面
    [self.view addSubview:sV];
    self.shopView = sV;
}

#pragma mark 重写触摸事件方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.shopView.item.price = 500;  //注意!执行到这一步会触发监听器的回调函数observeValueForKeyPath: ofObject: change: context:
}

// 结果:
keyPath = price, object = <ShopItemModel: 0x7f9119d2fca0>, change = {
    kind = 1;
    new = 500;
    old = 20000;
}, context = 商品界面
<ShopView: 0x7f84b1e141f0; frame = (0 200; 414 200); layer = <CALayer: 0x7f84b1e16850>>知道价格发生变化了,价格为500.000000
触摸前 触摸后

KVO原理

只要给一个对象注册一个监听,那么在运行时:

  1. 系统给这个对象生成一个子类对象 NSKVONotifying_XXX(XXX类名)
  2. 对子类对象中的被监听属性重写setter方法,
  3. 在setter方法中通知监听者

修改上面例子的setItem方法

-(void)setItem:(ShopItemModel *)item {
    _item = item;
    
    self.tLabel.text = [NSString stringWithFormat:@"%@价格为:%f", self.item.name, self.item.price];
    
    // 打印被监听对象的isa
    NSLog(@"%@", [self.item valueForKey:@"isa"]);

    // 注册监听
    NSKeyValueObservingOptions option =  NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.item addObserver:self forKeyPath:@"price" options:option context:@"商品界面"];

    // 在这里打断点

    // 打印被监听对象的isa
    NSLog(@"%@", [self.item valueForKey:@"isa"]);
}

// 输出结果
ShopItemModel
NSKVONotifying_ShopItemModel

运行时创建子类,相当于以下代码操作

#import <Foundation/Foundation.h>

@interface NSKVONotifying_ShopItemModel : ShopItemModel

@end

#import "NSKVONotifying_ShopItemModel.h"

@implementation NSKVONotifying_ShopItemModel

// 重写了price的setter方法
-(void)setPrice:(float)price {
    // Invoked to inform the receiver that the value of a given property is about to change.
    [self willChangeValueForKey:@"price"];
    _price = price;
    // Invoked to inform the receiver that the value of a given property has changed.
    [self didChangeValueForKey:@"price"];
}

@end

注意:如果你自己定义了这样一个NSKVONotifing_xxx子类的话,会报错,因为你自己搞了一个子类,系统又需要搞一个同名子类,就搞不了,所以出错

上一篇 下一篇

猜你喜欢

热点阅读