iOS基础知识8

本文详细介绍了iOS中的KVO(Key-Value Observing)机制,包括如何注册和移除观察者、手动触发KVO、KVC的集合运算符使用以及如何自定义KVO实现。同时,探讨了KVO与通知中心、Delegate、Callback的区别和应用场景。

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

一、addObserver:forkeyPath:options:context:各个参数的作用分别是什么?observer中需要实现哪个方法才能获得KVO回调?

iOS中通知方面的内容:NSNotificationCenter通知中心、KVO(Key-Value Observing)键值观察 、Delegate代理、Callback回调。

KVO是Key-Value Observing的缩写,是对象间监控对方状态的改变,并做出反应的机制。对象可以为自己的属性注册观察者,当这个属性的值发生了改变,系统会对这些注册的观察者做出通知。

 

1、为对象的属性注册观察者。

 

- (void)addObserver:(NSObject *)observer  
         forKeyPath:(NSString *)keyPath  
            options:(NSKeyValueObservingOptions)options  
            context:(void *)context 

 

observer:观察者对象。其必须实现方法observeValueForKeyPath:ofObject:change:context:。

keyPath:被观察的属性,其不能为nil。

options:设定通知观察者时传递的属性值,是传改变前的还是改变后的,通常设置为NSKeyValueObservingOptionNew。

context:一些其他的需要传递给观察者的上下文信息,通常设置为nil。

比如:

 

Person *myPerson = [[Person alloc] init];
[myPerson addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];


2、观察者接收通知,观察者通过实现下面的方法,完成对属性改变的响应。

 

 

- (void)observeValueForKeyPath:(NSString *)keyPath  
                      ofObject:(id)object  
                        change:(NSDictionary *)change  
                       context:(void *)context 

keyPath:被观察的属性,其不能为nil。

 

object:被观察者的对象。

change:属性值,根据上面提到的Options设置,给出对应的属性值。

context:注册观察者传递的context对象。
比如:

 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context  
{  
    if ([keyPath isEqualToString:@"age"]) {  
        NSLog(@"age is changed age = %@", [change valueForKey:NSKeyValueChangeNewKey]);  
    } 
    else {  
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];  
    }  
}  

 

 

KVO的回调要被调用,属性必须是通过KVC的方法来修改的,如果是调用类的其他方法来修改属性,这个观察者是不会得到通知的。

 

 

3、清除观察者。

 

 

 

- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath


摘抄(http://ningandjiao.iteye.com/blog/2009729):

 

当一个观察者观察多个对象的相同属性(即不同Object,但是KeyPath相同),可通过设定静态的Context变量来区分不同的通知。

使用NSStringFromSelector(@selector(method))来获取KeyPath,而不是直接通过NSString写属性名,这样编译器可以帮助发现属性名中的Typo。

通过方法:+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key,通过一个Key观察多个属性值的改变。

 

 

 

二、如何手动触发一个value的KVO?

自动触发是指类似这种场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了。

 

想知道如何手动触发,必须知道自动触发 KVO 的原理:

键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就
会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。

转自<<猿圈>>

 

 

 

三、若一个类有实例变量NSString *_foo,调用setValue:forKey:时,可以以foo还是_foo作为key?

都可以。

 

 

 

四、KVC的keyPath中的集合运算符如何使用?

KVC中的集合运算符有以下三类:

1、简单集合运算符,只能用在集合对象中,对象属性必须为数字类型。

@avg、@sum、@max、@min、@count

2、对象操作符。

@unionOfObjects:返回指定属性的值的数组,不去重。

@distinctUnionOfObjects:返回指定属性去重后的值的数组。

3、数组/集体操作符。

@unionOfArrays:返回一个数组,值由各个子数组的元素组成,不去重。

@distinctUnionOfArrays:返回一个数组,值由各个子数组的元素组成,去重。

@distinctUnionOfSets:和distinctUnionOfArrays相似,NSSet不能有重复的值。

 

 

//
//  Person.h
//  KVC集合运算符
//
//  Created by hkshen on 2017/9/8.
//  Copyright © 2017年 hkshen. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject

// 姓名
@property (nonatomic, copy) NSString *name;

// 年龄
@property (nonatomic, assign) NSInteger age;

// 设置属性变量方法
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;

@end
//
//  Person.m
//  KVC集合运算符
//
//  Created by hkshen on 2017/9/8.
//  Copyright © 2017年 hkshen. All rights reserved.
//

#import "Person.h"

@implementation Person

//不写@synthesize name = _name的话,默认实例变量为_name
@synthesize name = _name;
@synthesize age = _age;

- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    
    if (self = [super init]) {
        _name = name;
        _age = age;
    }
    return self;
}

@end
//
//  ViewController.m
//  KVC集合运算符
//
//  Created by hkshen on 2017/9/8.
//  Copyright © 2017年 hkshen. All rights reserved.
//

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

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    Person *person1 = [[Person alloc] initWithName:@"maozedong" age:99];
    Person *person2 = [[Person alloc] initWithName:@"maozedong" age:98];
    Person *person3 = [[Person alloc] initWithName:@"baoqingtian" age:97];
    Person *person4 = [[Person alloc] initWithName:@"qinshihuang" age:96];
    Person *person5 = [[Person alloc] initWithName:@"lishiming" age:95];
    Person *person6 = [[Person alloc] initWithName:@"kangxi" age:94];
    
    // 简单集合运算符不能用于单个对象中,一下写法crash
    //int oneAgeSum = [[person1 valueForKeyPath:@"@sum.age"] intValue];
    //NSLog(@"%d", oneAgeSum);
    
    NSArray *personArrayHK = @[person1, person2, person3, person4, person5, person6];
    // 简单集合运算符
    int personAgeAvg = [[personArrayHK valueForKeyPath:@"@avg.age"] intValue];
    int personAgeSum = [[personArrayHK valueForKeyPath:@"@sum.age"] intValue];
    int personAgeMax = [[personArrayHK valueForKeyPath:@"@max.age"] intValue];
    int personAgeMin = [[personArrayHK valueForKeyPath:@"@min.age"] intValue];
    int personAgeCount = [[personArrayHK valueForKeyPath:@"@count.age"] intValue];
    
    // 对象操作符:对数组对象进行操作
    NSArray<NSString *> *personNameArray1 = [personArrayHK valueForKeyPath:@"@unionOfObjects.name"]; // 不去重
    NSArray<NSString *> *personNameArray2 = [personArrayHK valueForKeyPath:@"@distinctUnionOfObjects.name"]; // 去重
    
    
    Person *person7 = [[Person alloc] initWithName:@"jiangzeming" age:96];
    Person *person8 = [[Person alloc] initWithName:@"jiangzeming" age:95];
    Person *person9 = [[Person alloc] initWithName:@"zhurongji" age:94];
    NSArray *personArrayWL = @[person7, person8, person9];
    // 数组/集体操作符:对由NSArray和NSSet所组成的集合操作
    // 需要两个数组组成一个数组
    NSArray *personNameArray3 = [@[personArrayHK, personArrayWL] valueForKeyPath:@"@unionOfArrays.name"]; // 不去重
    NSArray *personNameArray4 = [@[personArrayHK, personArrayWL] valueForKeyPath:@"@distinctUnionOfArrays.name"]; // 去重
    
    NSLog(@"personAgeAvg:%d", personAgeAvg);
    NSLog(@"personAgeSum:%d", personAgeSum);
    NSLog(@"personAgeMax:%d", personAgeMax);
    NSLog(@"personAgeMin:%d", personAgeMin);
    NSLog(@"personAgeCount:%d", personAgeCount);
    
    NSLog(@"personNameArray1:%@", personNameArray1);
    NSLog(@"personNameArray2:%@", personNameArray2);
    NSLog(@"personNameArray3:%@", personNameArray3);
    NSLog(@"personNameArray4:%@", personNameArray4);
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

打印:

 

 

2017-09-08 18:12:34.384 KVC集合运算符[36056:3211801] personAgeAvg:96
2017-09-08 18:12:34.384 KVC集合运算符[36056:3211801] personAgeSum:579
2017-09-08 18:12:34.385 KVC集合运算符[36056:3211801] personAgeMax:99
2017-09-08 18:12:34.385 KVC集合运算符[36056:3211801] personAgeMin:94
2017-09-08 18:12:34.385 KVC集合运算符[36056:3211801] personAgeCount:6
2017-09-08 18:12:34.385 KVC集合运算符[36056:3211801] personNameArray1:(
    maozedong,
    maozedong,
    baoqingtian,
    qinshihuang,
    lishiming,
    kangxi
)
2017-09-08 18:12:34.385 KVC集合运算符[36056:3211801] personNameArray2:(
    baoqingtian,
    maozedong,
    lishiming,
    qinshihuang,
    kangxi
)
2017-09-08 18:12:34.385 KVC集合运算符[36056:3211801] personNameArray3:(
    maozedong,
    maozedong,
    baoqingtian,
    qinshihuang,
    lishiming,
    kangxi,
    jiangzeming,
    jiangzeming,
    zhurongji
)
2017-09-08 18:12:34.386 KVC集合运算符[36056:3211801] personNameArray4:(
    lishiming,
    kangxi,
    jiangzeming,
    maozedong,
    zhurongji,
    baoqingtian,
    qinshihuang
)

github:点击打开链接



五、如何关闭默认的KVO的默认实现,并进入自定义的KVO实现?

 

http://tech.glowing.com/cn/implement-kvo/

 

 

 

六、apple用什么方式实现对一个对象的KVO

http://blog.youkuaiyun.com/bravegogo/article/details/50699594

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值