iOS底层探索-KVO
1、KVC
键值编码,经过Key名直接拜访目标特点,由NSKeyValueCoding
非正式协议启用的机制
@interface LZPerson : NSObject {
@public
NSString *name;
}
#import "ViewController.h"
#import "LZPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LZPerson *p = [LZPerson alloc];
p->name = @"lz";
[p setValue:@"kvcValue" forKey:@"name"];
NSLog(@"%@",[p valueForKey:@"name"]);
}
@end
// 打印成果
kvcValue
-
KVC 本质上是对 NSOb工龄越长退休金越多吗ject、NSArray、NSDictionary、NSMut变量之间的关系ableDictionary、NSOrderedSet、NSSet 等目标,完成
NSKeyValueCoding
分类,赋予它们K字符是什么ey-Value Coding
的能力;概况参阅 KVC文档
1.1、赋值流程
- 首要会去找类的 set办法,假如找不到会去找 带下划线的set办法
@implementation LZPerson - (void)setName:(NSString *)name { self->name = @"setValue"; } - (void)_setName:(NSString *)name { self->name = @"_setValue"; } @end
- 假如都找不到,则会看
+(BOOL)a字符间距ccessInstanceVariablesDirectly
办法中的google回来(默认为YES)// 依照 _key、_isKey、key、isKey 的次序找特点赋值 @interface LZPerson : NSObject { @public NSString *_name; NSString *_isName; NSString *name; NSString *isName; } @end
- 回来YES时,会依照 _key、_isKey、key、isKey 的次序找特点赋值,假如 类中没有上面的这些特点 则会调用
-(void)setValue:(id)valu变量值e forUndefinedKey:(NSString *)ke字符型变量y
办法(自己完成一下,不然报错) - 回来NO时,会直接调用 -(字符串逆序输出v字符间距怎么加宽oid)setValue:(id)value forUndefinedKey 办法
@implementation LZPerson + (BOOL)accessInstanceVariablesDirectly { return YES; } // 简略完成一下避免溃散 - (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"%s",__func__); } @end
- 回来YES时,会依照 _key、_isKey、key、isKey 的次序找特点赋值,假如 类中没有上面的这些特点 则会调用
1.2、取值流程
- 首要取值会按 getK字符ey、key、isKey、_key 的次序取
- (id)getName { return @"getGetNameValue"; } - (id)name { return @"getNameValue"; } - (id)isName { return @"getIsNameValue"; } - (id)_name { return @"get_NameValue"; } + (BOOL)accessInstanceVariablesDirectly { return YES; } - (id)valueForUndefinedKey:(NSString *)key { return @"valueForUndefineKey"; }
- 找不到也会根据 +(BOOL)accessInstanceVariablesDire变量名ctly 回来值
1.3、API
// 经过 Key 读取和存储
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 经过 keyPath 读取和存储
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
// 默认回来YES,若没有找到Set<Key>办法,依照_key、_iskey、key、iskey次序搜索成员
+ (BOOL)accessInstanceVariablesDirectly;
// KVC供给特点值正确性验证的API,它能够用来检查set的值是否正确,为不正确的值做一个替换值或者回绝设置新值并回来过错原因
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// 这是调集操作的API,里边还有一系列这样的API,假如特点是一个NSMutableArray,那么能够用这个办法来回来
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
// 假如Key不存在,且KVC无法搜索到任何和Key有关的字段或者特点,则会调用这个办法,默认是抛出反常
- (nullable id)valueForUndefinedKey:(NSString *)key;
// 和上一个办法一样,但这个办法是设值
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//假如你在SetValue办法时给Value传nil,则会调用这个办法
- (void)setNilValueForKey:(NSString *)key;
//输入一组Key,回来该组Key对应的Value,再转成字典回来,用于将Model转到字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
1.4、自定义KVC
相关办法
#import "NSObject+LZKVC.h"
#import <objc/runtime.h>
@implementation NSObject (LZKVC)
- (BOOL)lz_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *ivarNameChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
NSLog(@"ivarName == %@",ivarName);
[mArray addObject:ivarName];
}
free(ivars);
return mArray;
}
KVC存储
// 自定义KVC-存储
- (void)lz_setValue:(nullable id)value forKey:(NSString *)key{
// 1: 判别key
if (key == nil || key.length == 0) {
return;
}
// 2: setter:set<Key>:→_set<Key>→setIs<Key>
// key要大写
NSString *Key = key.capitalizedString;
// 拼接办法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self lz_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self lz_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self lz_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3: 判别能否直接赋值实例变量,假如accessInstanceVariablesDirectly回来NO,奔溃
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LZUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 直接变量
// 获取 ivar -> 遍历 containsObjct
// 4.1: 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key>→_is<Key>→<key>→is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2: 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3: 对相应的 ivar 设置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5: 找不到,奔溃
@throw [NSException exceptionWithName:@"LZUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
KVC读取
- (nullable id)lz_valueForKey:(NSString *)key{
// 1: 判别key
if (key == nil || key.length == 0) {
return nil;
}
// 2: 找到相关办法get<Key>→<key> countOf<Key> objectIn<Key>AtIndex
// key要大写
NSString *Key = key.capitalizedString;
// 拼接办法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}
else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}
else if ([self respondsToSelector:NSSelectorFromString(isKey)]){
return [self performSelector:NSSelectorFromString(isKey)];
}
else if ([self respondsToSelector:NSSelectorFromString(_key)]){
return [self performSelector:NSSelectorFromString(_key)];
}
else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3: 判别是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 找相关实例变量进行赋值
// 4.1: 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key>→_is<Key>→<key>→is<Key>
// _name→_isName→name→isName
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
2、KVO
-
KVO 全称
Key-Value Observing
(键值调查),是一种机制,答Go应目标在 其字符间距加宽2磅怎么设置他目标的指定字符串是什么意思特点产生更改时 收到告诉
2.1、KVO 和 NSNotificatio成员变量Center 的差异
- KVO 只能用于监听 目标字符常量特点的改变,NSNotific字符串逆序输出atioCenter进程上下文 能够变量泵监听任何你感兴趣的东西
- KVO 宣布音讯由 系统控制,NSNotificatioCenter 由 开发者控制
- KVO 自进程上下文动记载新旧值改变,N变量的定义SNotificatioCenter 只能记载开发者传递的参数
2.2、监听过程
-
注册调查者
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
- 音讯中的 上下文指针context 包括任意数据,这些数据将在相应的更改告诉中传回给调查者;能够指定
NULL
并彻底依赖keyPath
字符串来确认更改告诉的来源,但这样可能会导致 父类由于不同原字符间距怎么加宽因也在调查相上下文同键途径的目标时 出现问题
- 音讯中的 上下文指针context 包括任意数据,这些数据将在相应的更改告诉中传回给调查者;能够指定
-
特点改变告诉
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"name"]) { NSLog(@"%@",change); } }
-
移除调查者
[self.person removeObserver:self forKeyPath:@"name" context:NULL];
- 假上下文语境如被调查者是单例,那么假如被调查者地点界面毁掉时不移除调查者会溃散(被调查者未开释,值变量名改变办工资超过5000怎么扣税法还要调用,但界面被开释,这个办法找不到了所以溃散)
-
设置 context上下文,区分告诉来源
static void *PersonNickContext = &PersonNickContext; static void *PersonNameContext = &PersonNameContext; [self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:PersonNickContext]; [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext]; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if (context == PersonNickContext) { NSLog(@"nick:%@",change); return; } if (context == PersonNameContext){ NSLog(@"name:%@",change); return; } }
2.3、手变量类型有哪些动封闭KVO、手动触发KV工资超过5000怎么扣税O
-
+(BOOL)automaticallyNotifiesObse上下文切换rversForKey
手动封闭KVO+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { return NO; }
-
willChangeValueForKey
、didChang公积金eValueForKey
手动触发KVO[LZPerson willChangeValueForKey:@"name"]; _name = name; [LZPerson didChangeValueForKey:@"name"];
2.4、监听可变数组
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 这种写法不能收到KVO告诉,由于KVO基于KVC,拜访 调集目标 有三种不同的代理办法
// if(self.person.dateArray.count == 0){
// [self.person.dateArray addObject:@"1"];
// }
// else{
// [self.person.dateArray removeObjectAtIndex:0];
// }
if(self.person.dateArray.count == 0){
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
}
else{
[[self.person mutableArrayValueForKey:@"dateArray"] removeObjectAtIndex:0];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"dateArray"];
}
- 对
调集目标
拜访定义的三种不同的代理办法-
mutableArrayVa变量类型有哪些lueForKey:
和mutableArrayValueForKeyPath: -
mutableSetValueForKey:
和 mutableSetValueF上下文orKeyPat变量名的命名规则h:变量的定义 -
mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
-
- 会打印 NSKeyValue公积金Change字符常量 类型的 kind,表示键值改变的类型,履行
addObject
时,kind 打印值为 2;履上下文无关文法行removeObjectAtIndex
时,kind 打印值为字符型变量 3/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information. */ typedef NS_ENUM(NSUInteger, NSKeyValueChange) { NSKeyValueChangeSetting = 1, //赋值 NSKeyValueChangeInsertion = 2, //插入 NSKeyValueChangeRemoval = 3, //移除 NSKeyValueChangeReplacement = 4, //替换 };
2.5、技术细节
- 自动键值调查是运用称为
isa-s成员变量有没有默认值wizzling
的技术完成 - 该 isa 指针指向目标的类,它坚持一个调度表,该调度表首要包括指向类完成的办法的指针,以及其他数据
- 当调查者 注册变量与函数调查目标的某特点 时,被调查目标的 isa 指针被修正,
指向中心类而变量名的命名规则不是真实的类
;因而,isa 指针的值不一定反映实例的实践类 - 永远
不要依Go赖 isa 指针来确认类成员身份
,应该运用该 class 办法来确认目标实例的类
2.6、底层原理
i上下文什么意思sa改变
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[LZPerson alloc] init];
NSLog(@"添加KVO调查者之前:%s", object_getClassName(self.person));
[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];
NSLog(@"添加KVO调查者之后:%s", object_getClassName(self.person));
}
// 打印成果
添加KVO调查者之前:LZPerson
添加KVO调查者之后:NSKVONotifying_LZPerson
- 当调用
addObserve
办法时,系统动态
生成当时类的子类NSKVONotifying_类名
(当时类的子类) - 目标会将 isa指针指向这成员变量和成员方法个子类,这个子类会生成对应的 set办法(setName)、结构办法、dealloc、_isKVOA(符号是否为KVO生成的中心类)
- set办法中会调用
willChangeValueForKey
和didChangeValueForKey
两个办法 - 当注册被移除时,目标将isa指执行上下文针指回正常
-
NSKVONotifying_类名
中的办法- (void)viewDidLoad { [super viewDidLoad]; self.person = [[LZPerson alloc] init]; [self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL]; unsigned int intCount; Method *methodList = class_copyMethodList(objc_getClass("NSKVONotifying_LZPerson"), &intCount); for (unsigned int intIndex=0; intIndex<intCount; intIndex++) { Method method = methodList[intIndex]; NSLog(@"SEL:%@,IMP:%p",NSStringFromSelector(method_getName(method)), method_getImplementation(method)); } } // 打印成果 SEL:setNickName:,IMP:0x18a5d8520 SEL:class,IMP:0x18a5d6fd4 SEL:dealloc,IMP:0x18a5d6d58 SEL:_isKVOA,IMP:0x18a5d6d50
-
由于类上下文无关文法的特点有set办法,而 成员变量枸杞没有set办法,因而
KV字符间距O不能监听成员变量
;假如一定要监听成员变字符间距在哪里设置量,需要 运用KV进程上下文C触发[self.person addObserver:self forKeyPath:@"_sex" options:(NSKeyValueObservingOptionNew) context:NULL]; //直接赋值无法触发KVO,要用KVC //self.person->_sex = @"male"; [self.person setValue:@"male" forKey:@"_sex"];
-
能够参阅
FBK上下文语境VOController
参阅文章:字符串是什么意思w变量ww.yuque.com/u12101430/a…