iOS开发:KVC与KVO

简介: KVC 就是键值编码(key-value-coding),可以直接访问对象的属性,或者给对象的属性赋值。黑魔法之一,很多高级的iOS开发技巧都是基于KVC实现的。KVO 是键值观察者(key-value-observing)。实现方式:通过对某个对象的某个属性添加观察者,当该属性改变,就会调用”observeValueForKeyPath:”方法,为我们提供一个“对象值改变了!”的时机进行一些操作。

一、KVC


1、介绍:

KVC 就是键值编码(key-value-coding),可以直接访问对象的属性,或者给对象的属性赋值。黑魔法之一,很多高级的iOS开发技巧都是基于KVC实现的。


2、应用:


KVC设值

KVC取值

KVC使用keyPath

KVC处理异常

KVC处理数值和结构体类型属性

KVC键值验证(Key-Value Validation)

KVC处理集合

KVC数模转换


3、实现:


#import <Foundation/Foundation.h>
@interface Author : NSObject{
    NSString *_name;
    //作者出版的书籍,一个作者对应多个书籍对象
    NSArray *_issueBook;
}
@end
//---------------------
#import "Author.h"
@implementation Author
@end


#import <Foundation/Foundation.h>
#import "Author.h"
@interface Book : NSObject{
    Author *_author;
}
//名字
@property(nonatomic,copy) NSString *name;
///价格
@property(nonatomic,assign)float price;
@end
//---------------------
#import "Book.h"
@implementation Book
@end


1、KVC赋值


通过键值路径为对象的属性赋值。一般用于私有的属性赋值:


//----------    KVC键值编码    --------
    Author *author = [[Author alloc] init];
    //设置属性值
    [author setValue:@"莫言" forKeyPath:@"name"];


如果对象A中的属性含有是一个对象B,设置对象B的属性:


//-------   KVC设置作者的书籍数组   -------
    //键值路径:对于一个类中有数组对象的属性进行便捷操作
    Book *book1 = [[Book alloc] init];
    book1.name = @"红高粱";
    book1.price = 9;
    Book *book2 = [[Book alloc] init];
    book2.name = @"蛙";
    book2.price = 6;
    NSArray *array = [NSArray arrayWithObjects:book1,book2, nil];
    [author setValue:array forKeyPath:@"issueBook"];


2、KVC取值


通过键值路径获取属性的值。一般通过key值获得私有属性的值。


//获取属性值
    NSString *name = [author valueForKey:@"name"];
    NSLog(@"%@",name);

可以通过keypath获得值:


// -----    KVC中键值路径取值   -------
    //基本数据类型会自动被包装成NSNumber,装到数组中
    //得到所有书籍的价格
    NSArray *priceArray = [author valueForKeyPath:@"issueBook.price"];
    NSLog(@"%@",priceArray);
    //获取数组的大小
    NSNumber *count = [author valueForKeyPath:@"issueBook.@count"];
    NSLog(@"count=%@",count);
    //获取书籍价格的总和
    NSNumber *sum = [author valueForKeyPath:@"issueBook.@sum.price"];
    NSLog(@"%@",sum);
    //获取书籍的平均值
    NSNumber *avg = [author valueForKeyPath:@"issueBook.@avg.price"];
    NSLog(@"%@",avg);
    //获取书籍的价格最大值和最小值
    NSNumber *max = [author valueForKeyPath:@"issueBook.@max.price"];
    NSNumber *min = [author valueForKeyPath:@"issueBook.@min.price"];
    NSLog(@"%@____%@",max,min);

20180823111931286.png

模型转换


将字典转型成Model,方法:setValuesForKeysWithDictionary:


///数模转换
-(void)dicToModel{
    // 定义一个字典
    NSDictionary *dict = @{
                           @"name"  : @"rattan",
                           @"price" : @"88.66",
                           };
    // 创建模型
    Book *p = [[Book alloc] init];
    // 字典转模型
    [p setValuesForKeysWithDictionary:dict];
    NSLog(@"\n作者:%@\n价格:%.2lf",p.name,(float)p.price);
}

打印结果:

20180823111526296.png


注意:字典的key和Model的属性一定要一一对应。否则系统会报错如下:

‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’


二、KVO


KVO 是键值观察者(key-value-observing)。实现方式:通过对某个对象的某个属性添加观察者,当该属性改变,就会调用”observeValueForKeyPath:”方法,为我们提供一个“对象值改变了!”的时机进行一些操作。


1、原理:


当某个类的对象第一次被观察时,系统在运行时会创建该类的派生类,改派生类中重写了该对象的setter方法,并且在setter方法中实现了通知的机制。派生类重写了class方法,以“欺骗”外部调用者他就是原先那个类。系统将这个类的isa指针指向新的派生类,因此改对象也就是新的派生类的对象了。因而改对象调用setter就会调用重写的setter,从而激活键值通知机制。此外派生类还重写了delloc方法来释放资源。


2、应用:


#import <Foundation/Foundation.h>
@interface Chidren : NSObject
@property(nonatomic,assign)NSInteger hapyValue;
@property(nonatomic,assign)NSInteger hurryValue;
///添加定时器
-(void)addTimer;
///注销定时器
-(void)deallocTimer;
@end
// -----------------------------
#import "Chidren.h"
@interface Chidren()
///定时器
@property(nonatomic,strong)NSTimer *timer;
@end
@implementation Chidren
- (id) init{
    self = [super init];
    if(self != nil){
        self.hapyValue= 100;
        [self addTimer];
    }
    return self;
}
///添加定时器
-(void)addTimer{
    //启动定时器
    [self timer];
}
- (void)timerAction:(NSTimer *)timer{
    //使用set方法修改属性值,才能触发KVO
    NSInteger value = _hapyValue;
//    [self setHapyValue:--value];
    self.hapyValue = --value;
    NSInteger values = _hurryValue;
    [self setHurryValue:--values];
}
///注销定时器
-(void)deallocTimer{
    [_timer invalidate];
    _timer = nil;
}
-(void)dealloc{
    [self deallocTimer];
}
#pragma mark - lazyload
-(NSTimer *)timer{
    if (!_timer) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
    }
    return _timer;
}
@end


#import <Foundation/Foundation.h>
#import "Chidren.h"
@interface Nure : NSObject
- (id) initWithChildren:(Chidren *)children;
@end
// ----------------------------
#import "Nure.h"
@implementation Nure{
    Chidren *_children;
}
static NSInteger isGo = 0;
#pragma mark - initial
- (id) initWithChildren:(Chidren *)children{
    self = [super init];
    if(self != nil){
        _children = children;
        isGo = 0;
        [self addKVO];
    }
    return self;
}
///添加KVO观察者
-(void)addKVO{
    //观察小孩的hapyValue
    //使用KVO为_children对象添加一个观察者,用于观察监听hapyValue属性值是否被修改
    [_children addObserver:self forKeyPath:@"hapyValue" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"hapyValue"];
    //观察小孩的hurryValue
    [_children addObserver:self forKeyPath:@"hurryValue" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:@"hurryValue"];
}
#pragma mark - NSKeyValueObserving
/**
 触发方法:当属性值发生变化的时候,这个方法会被回调
 @param keyPath 键值路径
 @param object 监听对象
 @param change 变化的值
 @param context 传递的内容
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    //通过打印change,我们可以看到对应的key
    NSLog(@"%@:%@",context,change);
    //通过keyPath来判断不同属性的观察者
    if([keyPath isEqualToString:@"hapyValue"]){
        //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
        //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
        //[change objectForKey:@"old"]是修改前的值
        NSNumber *hapyValue = [change objectForKey:@"new"];//修改之后的最新值
        NSInteger value = [hapyValue integerValue];
        if(value < 90){
            [_children deallocTimer];
            if (isGo == 0) {
                isGo = 1;
                NSLog(@"休息一下");
                sleep(5);
            }
            [_children addTimer];
        }
    }else if([keyPath isEqualToString:@"hurryValue"]){
        //这里change中有old和new的值是因为我们在调用addObserver方法时,用到了
        //NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;想要哪一个就用哪一个
        //[change objectForKey:@"old"]是修改前的值
        NSNumber *hurryValue = [change objectForKey:@"new"];//修改之后的最新值
        NSInteger value = [hurryValue integerValue];
        if(value < -30){
            //do something...
            NSLog(@"退出循环");
            [_children deallocTimer];
        }
    }
    //使用KVC去修改属性的值,也会触发事件
}
- (void)dealloc{
    //移除观察者
    [_children removeObserver:self forKeyPath:@"hapyValue"];
    [_children removeObserver:self forKeyPath:@"hurryValue"];
}
@end


#import "Nure.h"
@interface KVOVC ()
///小孩
@property(nonatomic,strong)Chidren *child;
///护士
@property(nonatomic,strong)Nure *nure;
@end
@implementation KVOVC
#pragma mark - initial
- (void)viewDidLoad {
    [super viewDidLoad];
    [self initView];
}
#pragma mark - method
-(void)buttonClick:(UIButton *)sender{
    NSLog(@"开启键值观察者模式");
    [self removeObj];
    [self nure];
}
-(void)removeObj{
    _child = nil;
    _nure = nil;
}
#pragma mark - lazyload
-(Nure *)nure{
    if (!_nure) {
        _nure = [[Nure alloc]initWithChildren:self.child];
    }
    return _nure;
}
-(Chidren *)child{
    if (!_child) {
        _child = [[Chidren alloc]init];
    }
    return _child;
}


4、使用场景:用于监听对象属性的改变


(1)下拉刷新、下拉加载监听UIScrollView的contentoffsize;

(2)webview混排监听contentsize;

(3)监听模型属性实时更新UI;

(4)监听控制器frame改变,实现抽屉效果。


Demo下载

参考链接

1、iOS KVC和KVO详解



相关文章
|
5天前
|
安全 Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的深度对比
在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。本文旨在通过数据驱动的分析方法,深入探讨这两大操作系统在开发环境、用户界面设计及市场表现等方面的差异。引用最新的行业报告和科研数据,结合技术专家的观点,本文将提供对开发者和市场分析师均有价值的洞见。
|
7天前
|
Java 开发工具 Android开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
在移动应用开发的广阔天地中,Android和iOS两大平台各自占据着半壁江山。本文将深入探讨这两个平台在开发过程中的关键差异点,包括编程语言、开发工具、用户界面设计、性能优化以及市场覆盖等方面。通过对这些关键因素的比较分析,旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和目标受众做出明智的平台选择。
|
8天前
|
编解码 Android开发 iOS开发
深入探索Android与iOS开发的差异与挑战
【6月更文挑战第24天】在移动应用开发的广阔舞台上,Android和iOS两大操作系统扮演着主角。它们各自拥有独特的开发环境、工具集、用户基础及市场策略。本文将深度剖析这两个平台的开发差异,并探讨开发者面临的挑战,旨在为即将踏入或已在移动开发领域奋斗的开发者提供一份实用指南。
32 13
|
4天前
|
iOS开发 开发者 UED
探索iOS开发中的SwiftUI框架
【6月更文挑战第28天】在移动应用开发的海洋中,SwiftUI作为iOS平台的新星,以其声明式语法和灵活性,正引领着界面设计的未来。本文将带你深入理解SwiftUI的核心概念、布局能力以及如何通过它提升开发效率,为开发者们提供一份实操指南,解锁SwiftUI的强大潜力。
13 1
|
11天前
|
iOS开发 开发者 容器
探索iOS开发中的SwiftUI框架
【6月更文挑战第21天】本文深入探讨了苹果在iOS开发中推出的SwiftUI框架,旨在为开发者提供一种声明式、更简洁的界面设计方法。文章首先概述了SwiftUI的核心概念和优势,接着通过一个天气预报应用实例,详细讲解了如何使用SwiftUI进行布局和用户界面的设计。此外,还讨论了SwiftUI与UIKit的差异,以及如何将SwiftUI集成到现有的项目中。最后,文章展望了SwiftUI的未来发展方向,包括潜在的改进和新特性。
|
7天前
|
监控 Android开发 iOS开发
探索Android与iOS开发的差异:平台、工具和用户体验的比较
【6月更文挑战第25天】在移动应用开发的广阔天地中,Android和iOS两大平台各领风骚,它们在开发环境、工具选择及用户体验设计上展现出独特的风貌。本文将深入探讨这两个操作系统在技术实现、市场定位和用户交互方面的关键差异,旨在为开发者提供一个全景式的视图,帮助他们在面对项目决策时能够更加明智地选择适合自己项目需求的平台。
|
11天前
|
Java 开发工具 Android开发
安卓与iOS开发差异解析
【6月更文挑战第21天】本文旨在深入探讨安卓和iOS两大移动操作系统在应用开发过程中的主要差异。通过对比分析,揭示各自的设计哲学、编程语言选择、用户界面构建、性能优化策略以及发布流程的异同。文章将提供开发者视角下的实用信息,帮助他们更好地理解各自平台的特点和挑战,从而做出更明智的开发决策。
|
12天前
|
Java 开发工具 Android开发
探索安卓与iOS开发的核心差异
【6月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文将深入探讨这两大操作系统在开发过程中的主要区别,包括编程语言、开发工具、用户界面设计哲学、系统架构以及市场分布等方面。通过对这些关键差异的分析,旨在为开发者提供一份实用的指南,帮助他们在面对项目决策时,能够更加明智地选择合适的平台,并针对特定平台优化他们的应用。
|
12天前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:从工具到用户体验
【6月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计以及性能优化等方面的关键差异。我们将通过比较分析,揭示各自平台的独特优势和面临的挑战,为开发者提供决策参考,并为最终用户提供更深层次的用户体验洞察。
|
14天前
|
Java Android开发 Swift
探索Android与iOS开发的差异:平台选择对项目成功的影响
【6月更文挑战第18天】在移动应用开发的广阔天地中,Android和iOS两大平台各据一方,它们在市场份额、用户群体及开发环境上各有千秋。本文将深入分析这两个操作系统的开发差异,探讨如何根据项目需求选择合适的平台,并讨论跨平台解决方案的可行性与挑战。我们将通过实际案例,揭示平台选择对项目成功的关键性影响,为开发者提供决策支持。