kvo kvc

kvo

1.添加监听接收通知移除监听

- (void)addObserver:(NSObject *)observer
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context
被观察者调用该函数 observer观察者对象, keyPath被观察者的键名(属性名字符串),
 options 可以传0,也可以传以下4个值,
 	NSKeyValueObservingOptionNew = 0x01, 值改变之后,把更改之后的值传给处理方法
    	NSKeyValueObservingOptionOld = 0x02, 值改变之后,把更改之前的值传给处理方法
    	NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04, 刚刚注册就会调用处理方法,以后就是值改变之后调用处理方法
    	NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08 调用两次处理方法,值改变之前和值改变之后

 context 上下文,可以传NULL

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

这是处理方法 在观察者类里。 keyPath 对应的被观察者的键名(属性字符串) object 被观察者

change 传过来的值 这样用 

        NSKeyValueChange change = change[NSKeyValueChangeKindKey];
        NSIndexSet set = change[NSKeyValueChangeIndexesKey];
        change[NSKeyValueChangeOldKey]
        change[NSKeyValueChangeNewKey]
        if ([change[NSKeyValueChangeNotificationIsPriorKey] boolValue]) {
            // 改变之前
        } else {
            // 改变之后
        }

NSKeyValueChangeKindKey 返回的是一个枚举值

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
一般情况下返回的是1,也就是NSKeyValueChangeSetting,但是如果你监听的属性是一个集合对象,那当这个集合有插入,删除,替换操作时,就会分别返回NSKeyValueChangeInsertion,NSKeyValueChangeRemoval,NSKeyValueChangeReplacement。
如果NSKeyValueChangeKindKey返回的枚举值不是1,是另外三个的话,那NSKeyValueChangeIndexesKey就会返回一个NSIndexSet对象,这对象里包含了增删改对象的index。


context 上下文,对应context


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

被观察者调用 移除观察者,observer 观察者对象 ,keyPath 同添加观察者的keyPath


如果有子类的话,context最好不要填NULL,在.m文件顶部写

static void  *PrivateKVOContext = &PrivateKVOContext;

然后这样用

[otherObject addObserver:self forKeyPath:@"someKey" options:someOptions context:PrivateKVOContext];
在observeValueForKeyPath里这样写

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == &PrivateKVOContext) {
        // 这里写相关的观察代码
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}
这样能保证父类和子类观察同样的键值(属性字符串)不会出错。


2.

如果被观察者的属性值A的变化依赖于被观察者的其他值B和C,那就需要在被观察者类里重写

+ (NSSet *)keyPathsForValuesAffecting<键名>
例子

+ (NSSet *)keyPathsForValuesAffectingA
{
    return [NSSet setWithObjects:@"B", @"C", nil];
}
这样的话,如果B或者C发生变化的话,那么观察者观察A的处理方法就会被调用。

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
也是依赖相关方法,被观察的键值在调用addObserver时,这个函数都会被调用,没有上面那个方法分的细。


3.
一般情况下键值改变通知都是自动发送的,如果需要手动控制是否发送通知,那就重写下面的方法,并返回NO

+ (BOOL)automaticallyNotifiesObserversOf<键名>{
    
    return NO;
}
比如我要控制属性A值的改变通知
+ (BOOL)automaticallyNotifiesObserversOfA{
    
    return NO;
}
这样当A值改变的时候就不会有通知发出了,下面就需要手动发送通知

- (void)setA:(id)A;
{
    if (_A == A) {
        return;
    }
    [self willChangeValueForKey:@"A"];
    _A = A;
    [self didChangeValueForKey:@"A"];
}

4. kvo 对集合的支持

如果用KVO要观察的属性是一个集合对象,比如NSMutableArray,当你给这个数组发addObject:消息时,是不会触发kvo通知的。

必需先拿到这个集合对象的集合代理对象,调用以下方法中的一个。

-mutableArrayValueForKey:
-mutableSetValueForKey:
-mutableOrderedSetValueForKey:
这些方法返回的就是集合代理对象。举例,如果监听了对象A里面的可变集合对象属性arr。为了改变arr的时候观察者能收到消息

NSMutableArray *transactions = [self.A mutableArrayValueForKey:@"arr"];
 [transactions addObject:object];


KVC

kvc Key-Value Coding是一种间接访问对象属性的机制,使用字符串标识属性,而不是通过调用实例变量的访问方法。其使用的方法基本都声明自NSKeyValueCoding协议,并被NSObject实现,NSKeyValueCoding定义的方法有

获得属性值的方法

- valueForKey:
- valueForKeyPath:
- dictionaryWithValuesForKeys:
- valueForUndefinedKey:
- mutableArrayValueForKey:
- mutableArrayValueForKeyPath:
- mutableSetValueForKey:
- mutableSetValueForKeyPath:
- mutableOrderedSetValueForKey:
- mutableOrderedSetValueForKeyPath:
设置属性值的方法

- setValue:forKeyPath:
- setValuesForKeysWithDictionary:
- setNilValueForKey:
- setValue:forKey:
- setValue:forUndefinedKey:

验证方法

- validateValue:forKey:error:
- validateValue:forKeyPath:error:

1

最简单的kvc,通过用属性的字符串名称来设置或访问属性

@property (nonatomic, copy) NSString *name;
@property (nonatomic,assign) int  age;

[object setValue:@(20) forKey:@"age"]
[object setValue:@"Tom" forKey:@"name"]

NSString *n = [object valueForKey:@"name"];
int age = [[object valueForKey:@"age"] intValue];

NSDictionary *dic =  [object dictionaryWithValuesForKeys:@[@"name",@"age"]];


还可以访问属性的属性
比如对象objcet有属性A,属性A是一个对象,A里面有属性b,访问或设置属性b就要这样

NSString *n = [object valueForKeyPath:@"A.b"];
[self.a setValue:@"b" forKeyPath:@"A.b"];
这个功能存取对象的私有成员是比较实用的。


valueForKey:这个函数还有一个特殊的用法,如果是集合类调用 valueForKey:那就是给这个集合里的每个对象发送操作消息,并且结果会被保存在一个新的容器中返回,valueForKeyPath:还能实现多个消息的传递

例子

    NSArray *arra= @[@"10.11",@"20.22"];
    NSArray *resultArray = [arra valueForKeyPath:@"doubleValue.intValue"];
    NSLog(@"%@", resultArray);
    (
    10,
    20
	)

如果要获取或设置值的key,object 没有,那就会分别调用
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key
默认是要报NSUndefinedKeyException错误的。如果你子类化后并重载上面的方法,就不会报错了。


2.集合的操作
简单集合操作符 返回的是strings, number, 或者 dates
@count: 返回一个值为集合中对象总数的NSNumber对象。
@sum: 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象。
@avg: 把集合中的每个对象都转换为double类型,返回一个值为平均值的NSNumber对象。
@max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。
@min: 和@max一样,但是返回的是集合中的最小值。

举例 
取出数组中的最大值

NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);
假设 有一个数组里面是都是 Person 对象,要算出平均年龄

NSArray *a = @[person1, person2, person3];
NSLog(@"avg age = %@", [a valueForKeyPath:@"@avg.age"]);


也可以自定义一些操作法 比如要实现@abc 那就 在NSArray的category里实现-(id)abcForKeyPath:(NSString*)keyPath 函数

对象操作符
返回一个由操作符右边的key path所指定的对象属性组成的数组

@unionOfObjects 不会对数组去重
@distinctUnionOfObjects 会对数组去重
如果其中任何涉及的对象为nil,则抛出异常

举例
我们有一个inventory数组,代表了当地苹果商店的当前库存,里面存储的都是Product对象

@interface Product : NSObject
@property NSString *name;
@property double price;
@property NSDate *launchedOn;
@end
NSArray *inventory = @[iPhone5, iPhone5, iPhone5, iPadMini, macBookPro, macBookPro];

返回的是字符串数组
[inventory valueForKeyPath:@"@unionOfObjects.name"]; // "iPhone 5", "iPhone 5", "iPhone 5", "iPad Mini", "MacBook Pro", "MacBook Pro"
[inventory valueForKeyPath:@"@distinctUnionOfObjects.name"]; // "iPhone 5", "iPad Mini", "MacBook Pro"

数组和集合操作符

@distinctUnionOfArrays 
@unionOfArrays:
数则和集合操作符跟对象操作符很相似,只不过它是在NSArray和NSSet所组成的集合中工作的,如果其中任何涉及的对象为nil,则抛出异常。
举例
NSArray *appleStoreInventory = @[iPhone5,iPad Mini,MacBook Pro,iMac];
NSArray* arrayOfArrays = @[inventory, appleStoreInventory];
NSArray* arr = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.name"];
arr中有两个数组,第一个数组里的内容是inventory里的,第二个数组里的内容是appleStoreInventory里的。

3.验证方法KVV

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

当给某个属性通过kvc的方式设置值的时候,咱自己调用validateValue函数来验证设置值的合法性,kvc不会自动调用的。
步骤:
1、调用validateValue:forKey:error: 
2、在对象的方法列表中匹配validate<Key>:error: 
3、如果找到则执行并返回结果 
4、如果未找到则返回YES,并赋值 
注意:set方法中禁止调用


第3布返回结果会有三种可能,当然,这三种可能也得咱自己实现
1 对象值是有效的,所以返回YES,不更改值和error
2 对象值无效,并且无法创建一个有效值。这时返回NO,并设置error参数为一个NSError对象表名错误原因。
3 对象值无效,但一个有效值被创建并返回。这时返回YES。并且不设置NSError,但需要设置一个新的ioValue。

- (BOOL)validateName:(NSString **)nameP error:(NSError * __autoreleasing *)error
{
    if (*nameP == nil) {
        *nameP = @"";
        return YES;
    } else {
        *nameP = [*nameP stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        return YES;
    }
}

NSError *err;
NSString* name = @"  John  ";
[object validateValue:&name forKeyPath:@"name" error:&err];

还有一验证方法是为 NSNumber(基本数据类型会自动转为NSNumber)或NSValue(结构体需手动转成NSValue)时调用准备的。
- (void)setNilValueForKey:(NSString *)key{
    NSLog(@"不能为 nil");
}
如果通过kvc给一个数值设置为nil
[object setValue:nil forKey:@"age"];
只要在object类里实现了 setNilValueForKey 方法,程序就不会崩溃。






猜你喜欢

转载自blog.csdn.net/langzxz/article/details/53333991