mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
docs: refine
This commit is contained in:
@@ -1,8 +1,4 @@
|
||||
# KVC && KVO
|
||||
|
||||
|
||||
|
||||
|
||||
# KVC && KVO
|
||||
|
||||
## 一、基本用法-字典快速赋值
|
||||
|
||||
@@ -24,13 +20,12 @@ KVC 可以将字典里面和 model 同名的 property 进行快速赋值 `setVal
|
||||
运行上面的代码,代码不崩溃,只不过在输出值的时候输出了 null
|
||||
|
||||
- 情况二:在 NSDictionary 中存在某个值,但是在 model 里面没有值
|
||||
|
||||
|
||||
运行后编译成功,但是代码奔溃掉。原因是 KVC 。所以我们只需要实现这么一个方法。甚至不需要写函数体部分
|
||||
|
||||
```
|
||||
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -38,7 +33,6 @@ KVC 可以将字典里面和 model 同名的 property 进行快速赋值 `setVal
|
||||
|
||||
我们照样可以利用 **setValue:forUndefinedKey:** 去处理
|
||||
|
||||
|
||||
```objective-c
|
||||
//model
|
||||
@property (nonatomic,copy)NSString *name;
|
||||
@@ -65,7 +59,6 @@ NSMutableArray *hobbies = [_person mutableArrayValueForKeyPath:@"hobbies"];
|
||||
```
|
||||
|
||||
- 情况五: 注册依赖键.
|
||||
|
||||
|
||||
KVO 可以观察属性的二级属性对象的所有属性变化。说人话就是“假如 Person 类有个 Dog 类,Dog 类有 name、fur、weight 等属性,我们给 Person 的 Dog 属性观察,假如 Dog 的任何属性变化是,Person 的观察者对象都可以拿到当前的变化值。我们只需要在 Person 中写下面的方法即可”
|
||||
|
||||
@@ -82,7 +75,7 @@ self.person.dog.weight = 50;
|
||||
// Person.m
|
||||
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
|
||||
|
||||
|
||||
if ([key isEqualToString:@"dog"]) {
|
||||
NSArray *affectingKeys = @[@"name", @"fur", @"weight"];
|
||||
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
|
||||
@@ -91,9 +84,6 @@ self.person.dog.weight = 50;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 二、 KVO 的本质
|
||||
|
||||
kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础。
|
||||
@@ -109,18 +99,39 @@ kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础
|
||||
return NO;
|
||||
}
|
||||
```
|
||||
|
||||
3. 若类有实例变量 NSString *_foo, 调用 setValue:forKey: 是以 foo 还是 _foo 作为 key ?
|
||||
|
||||
都可以
|
||||
|
||||
4. KVC 的 keyPath 中的集合运算符如何使用
|
||||
|
||||
- 必须用在 **集合对象** 或者 **普通对象的集合属性** 上
|
||||
|
||||
-简单的集合运算符有 @avg、@count、@max、@min、@sum
|
||||
|
||||
5. KVO 和 KVC 的 keyPath 一定是属性吗?
|
||||
可以是成员变量
|
||||
可以是成员变量
|
||||
|
||||
6. KVO 中 派生类的 setter 方法内部实现调用了 Foundation 框架中的 `_NSSetIntValueAndNotify`.
|
||||
|
||||
7. 直接修改对象的成员变量会触发 KVO 吗?
|
||||
|
||||
不会。因为成员变量没有 setter.
|
||||
|
||||
```
|
||||
@interface Person: NSObject
|
||||
{
|
||||
@public:
|
||||
int age;
|
||||
}
|
||||
@end
|
||||
```
|
||||
|
||||
8. 手动触发 KVO?调用 willChangeValueForKey、didChangeValueForKey
|
||||
|
||||
9.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -142,17 +153,13 @@ Apple 文档告诉我们:被观察对象的 `isa指针` 会指向一个中间
|
||||
|
||||
- 键值观察通知依赖于 NSObject 的两个方法:`willChangeValueForKey:、didChangeValueForKey:` 。在一个被观察属性改变之前,调用 `willChangeValueForKey:` 记录旧的值。在属性值改变之后调用 `didChangeValueForKey:`,从而 `observeValueForKey:ofObject:change:context:` 也会被调用。
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
为什么要选择是继承的子类而不是分类呢?
|
||||
子类在继承父类对象,子类对象调用调方法的时候先看看当前子类中是否有方法实现,如果不存在方法则通过 isa 指针顺着继承链向上找到父类中是否有方法实现,如果父类种也不存在方法实现,则继续向上找...直到找到 NSObject 类为止,系统会抛出几次机会给程序员补救,如果未做处理则奔溃
|
||||
|
||||
关于分类与子类的关系可以看看我之前的 [文章](1.50.md).
|
||||
|
||||
|
||||
|
||||
## 四、 模拟实现系统的 KVO
|
||||
|
||||
1. 创建被观察对象的子类
|
||||
@@ -160,6 +167,7 @@ Apple 文档告诉我们:被观察对象的 `isa指针` 会指向一个中间
|
||||
3. 外界改变 isa 指针(class方法重写)
|
||||
|
||||
我们用自己的类模拟系统的 KVO。
|
||||
|
||||
```
|
||||
//NSObject+LBPKVO.h
|
||||
#import <Foundation/Foundation.h>
|
||||
@@ -169,7 +177,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface NSObject (LBPKVO)
|
||||
|
||||
- (void)lbpKVO_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -189,15 +197,15 @@ NS_ASSUME_NONNULL_END
|
||||
Class myclass = objc_allocateClassPair(self.class, [currentClassName UTF8String], 0);
|
||||
// 生成后不能马上使用,必须先注册
|
||||
objc_registerClassPair(myclass);
|
||||
|
||||
|
||||
//2. 重写 setter 方法
|
||||
class_addMethod(myclass,@selector(setName:) , (IMP)setName, "v@:@");
|
||||
//3. 修改 isa
|
||||
object_setClass(self, myclass);
|
||||
|
||||
|
||||
//4. 将观察者保存到当前对象里面
|
||||
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
|
||||
//5. 将传递的上下文绑定到当前对象里面
|
||||
objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN);
|
||||
}
|
||||
@@ -212,7 +220,7 @@ void setName (id self, SEL _cmd, NSString *name) {
|
||||
object_setClass(self, class_getSuperclass(class));
|
||||
//2. 调用父类的 setName 方法
|
||||
objc_msgSend(self, @selector(setName:), name);
|
||||
|
||||
|
||||
//3. 调用观察
|
||||
id observer = objc_getAssociatedObject(self, "observer");
|
||||
id context = objc_getAssociatedObject(self, "context");
|
||||
@@ -235,7 +243,7 @@ void setName (id self, SEL _cmd, NSString *name) {
|
||||
_person.hobbies = [@[@"iOS"] mutableCopy];
|
||||
NSDictionary *context = @{@"name": @"成吉思汗", @"hobby" : @"弯弓射大雕"};
|
||||
[_person lbpKVO_addObserver:self forKeyPath:@"hobbies" options:(NSKeyValueObservingOptionNew) context:(__bridge void * _Nullable)(context)];
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
|
||||
@@ -247,10 +255,8 @@ void setName (id self, SEL _cmd, NSString *name) {
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
|
||||
NSLog(@"%@",change);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
KVO 的缺陷:
|
||||
|
||||
KVO 虽然很强大,你只能重写 `-observeValueForKeyPath:ofObject:change:context: ` 来获得通知,想要提供自定义的 selector ,不行;想要传入一个 block 没门儿。感觉如果加入 block 就更棒了。
|
||||
@@ -261,4 +267,54 @@ KVO 的改装:
|
||||
|
||||
|
||||
|
||||
## 五、 KVC
|
||||
|
||||
`setValueForKey` 用来设置对象的一层属性值修改。
|
||||
|
||||
`setValueForKeyPath` 可以设置对象的某个属性(属性本身是对象)的属性值修改
|
||||
|
||||
|
||||
|
||||
KVC 之后会触发 KVO。为什么?探究下 `setValueForKey`
|
||||
|
||||
`[self.person setValue:@10 forKey:@"age"]` 会先调用 `setKey:` 同名的方法,找不到则调用 `_setKey:` 的方法,如果还是找不到则调用 `+(BOOL)accessInstanceVariableDirectlt`,如果该方法返回 YES,则可以直接修改成员变量的值,会按照 _key、_isKey、key、isKey 的顺序寻找成员变量,如果找到则直接赋值,没找到则抛出异常 NSUnknownKeyException
|
||||
|
||||
整个流程如下
|
||||
|
||||

|
||||
|
||||
```
|
||||
@implementation Person
|
||||
- (void)setAge:(int)age
|
||||
{
|
||||
_age = age;
|
||||
}
|
||||
|
||||
- (void)_setAge:(int)age
|
||||
{
|
||||
_age = age;
|
||||
}
|
||||
|
||||
+ (BOOL)accessInstanceVariableDirectlt
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
valueForKey 原理
|
||||
|
||||
- 按照 getKey、key、isKey、_key 的顺序寻找方法实现
|
||||
|
||||
- 找到则直接调用方法,返回值
|
||||
|
||||
- 如果没找到则调用 accessInstanceVariableDirectlt 方法,询问是否可以访问成员变量。为 NO 则抛出异常
|
||||
|
||||
- 为 YES 则按照 __key、isKey、key、isKey 的顺序访问成员变量。找到哪个则返回值
|
||||
|
||||
- 都没找到则抛出异常
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user