iOS_KVC:Key-Value Coding-2(访问者搜索模式)

2022-07-20 14:17:40 浏览数 (1)

文章目录
  • Accessor Search Patterns 访问者搜索模式
  • 一、Getter 搜索模式
    • 1、简单访问器(simple accessor)
    • 1、数组访问器 (Array accessor)
    • 3、集合访问器 (Collection accessor)
    • 4、直接访问成员变量 (Directly access)
  • 二、Setter 搜索模式
    • 1、简单访问器(simple accessor)
    • 2、直接访问成员变量 (Directly access)

Accessor Search Patterns 访问者搜索模式

一道面试题: 当我们调用valueForKey: or setValue:forKey: 时,系统是怎么查找的?

例如有这样一个person类:

代码语言:javascript复制
@interface MOPerson : NSObject
// @property (nonatomic, copy) NSString *name;
// 把name注释掉,假设.h文件没有暴露name or 根本没有实现这个属性
@end

一、Getter 搜索模式

例如:当我们通过valueForKey:获取person的name时:

代码语言:javascript复制
MOPerson *person = [[MOPerson alloc] init];
NSLog(@"%@", [person valueForKey:@"name"]);

首先会进入第一种类型,简单访器的查找:

1、简单访问器(simple accessor)

依次搜索实例中的方法:get<Key><key>is<Key>_<key> 例如,name的如下方法:(当上一个方法没有实现时,才会查找的下一个方法)

代码语言:javascript复制
- (NSString *)getName {
  return @"getName";
}
- (NSString *)name {
  return @"name";
}
- (NSString *)isName {
  return @"isName";
}
- (NSString *)_name {
  return @"_name";
}

如果以上的方法都没有实现,则会进入第二类型:数组访问器的查找:

1、数组访问器 (Array accessor)

搜索实例中的方法:countOf<Key>objectIn<Key>AtIndex: or <key>AtIndexes:,如果有实现第一个方法后面两个的其中一个,就可以完成第二类查找。 数组访问器方法会get到一个数组,所以适用于数组的查找。(因此下面用names数组来举例说明) 假设extension里有一个names数组 :

代码语言:javascript复制
@interface MOPerson()
@property (nonatomic, strong) NSArray *names;
@end
- (instancetype)init {
  self = [super init];
  if (self) {
    self.names = @[@"mo1", @"mo2", @"mo3"]; // 初始化数组
  }
  return self;
}

实现第二类访问方法:

代码语言:javascript复制
- (NSUInteger)countOfName { // 必须实现
  NSLog(@"%s", __func__);
  return self.names.count;
}
// 下面两个方法,实现其中一个
- (id)objectInNameAtIndex:(NSUInteger)index { // 优先调用
  NSLog(@"%s", __func__);
  return self.names[index];
}
// 上一个方法未实现,则会调用该方法
- (NSArray *)nameAtIndexes:(NSIndexSet *)indexes { 
  NSLog(@"%s", __func__);
  return [self.names objectsAtIndexes:indexes];
}

在上述条件达成的情况下,还有一个可选的方法get<Key>:range:,实现的话可以提高性能:

代码语言:javascript复制
// 某些情况下会调用到,实现可以提高性能
- (void)getName:(NSString * __unsafe_unretained *)name range:(NSRange)inRange {
  NSLog(@"%s", __func__);
  [self.names getObjects:name range:inRange];
}
// -[MOPerson countOfName]
// -[MOPerson getName:range:]
// ( // 类型是:NSKeyValueArray
//    mo1,
//    mo2,
//    mo3
// )

如果以上2种访问器的方法都没有实现,则会进入第三类型:集合访问器的查找:

3、集合访问器 (Collection accessor)

搜索实例中的方法:countOf<Key>enumeratorOf<Key>memberOf<key>:,三个方法必须都实现才能完成第三类查找。 实现第三类访问方法:

代码语言:javascript复制
- (NSUInteger)countOfName { // 这个方法在第二类时,也是必须的实现的
  NSLog(@"%s", __func__);
  return self.names.count;
}
- (id)enumeratorOfName { // 返回的是一个 NSEnumerator 类型的对象
  NSLog(@"%s", __func__);
  return [self.names reverseObjectEnumerator];
}
// NSSet里的member方法是:
// 在set里用 isEqual: 方法查找,euqal的对象返回set里的对象,否则返回nil
- (id)memberOfName:(id)object {
  NSLog(@"%s", __func__);
  NSUInteger index = [self.names indexOfObject:object];
  return index ? [self.names objectAtIndex:index] : nil;
  // 因为这个方法的log没有输出,所以我感觉 只要有这个方法就行,并不需要实现什么
  // 因此我试了一下,这里直接 return nil 也可以
  return nil;
}
//-[MOPerson countOfName]
//-[MOPerson countOfName]
//-[MOPerson enumeratorOfName]
//{( // 类型是:NSKeyValueSet
//    mo3,
//    mo2,
//    mo1
//)}

如果以上3种访问器的方法都没有实现,则会进入第四类型:直接访问成员变量

4、直接访问成员变量 (Directly access)

  • 接下来的查找会先调用accessInstanceVariablesDirectly方法进行判断:是否允许直接方法成员变量,默认return YES (我们可以根自己需要重写return NO)。
代码语言:javascript复制
  (BOOL)accessInstanceVariablesDirectly {
  return YES;
}
  • 在允许后依次查找成员变量:_<key>_is<Key><key>is<Key>(当上一个成员变量没有时,才会查找到下一个)然后直接使用成员变量的值。 例如:实现name的这4个成员变量
代码语言:javascript复制
@implementation MOPerson {
  NSString* _name;
  NSString* _isName;
  NSString* name;
  NSString* isName;
}
 - (instancetype)init {
  self = [super init];
  if (self) {
    _name = @"_name";
    _isName = @"_isName";
    name = @"name";
    isName = @"isName";
  }
  return self;
}

当上一个成员变量没有实现时,才会查找到下一个成员变量: 得到:_name or _isName or name or isName

  • 到此所有可以Get到value的方法都search过了,如果都没有找到,则会触发valueForUndefinedKey:方法抛出NSUnknownKeyException异常,导致crash。

我们可以重写该方法:打印出log,防止crash,方便debug…

代码语言:javascript复制
- (id)valueForUndefinedKey:(NSString *)key {
  NSLog(@"Error: valueForUndefinedKey: %@", key);
  return nil;
}

二、Setter 搜索模式

例如:当我们通过setValue:forKey:设置person的name时:

代码语言:javascript复制
MOPerson *person = [[MOPerson alloc] init];
[person setValue:@"momo" forKey:@"name"];

1、简单访问器(simple accessor)

依次查找:set<Key>:_set<Key>:setIsName: (我看官网没有写setIsName:,不知道为什么,但是它是valid的) 实现name的简单访问器方法:

代码语言:javascript复制
// 依次查找: setName、_setName、setIsName
- (void)setName:(NSString *)name {
  NSLog(@"%s", __func__);
}
- (void)_setName:(NSString *)name {
  NSLog(@"%s", __func__);
}
- (void)setIsName:(NSString *)name {
  NSLog(@"%s", __func__);
}

同理:当上一个方法未实现时,才会查找到下一个方法 当简单访问器方法未实现时,会尝试直接设置成员变量:

2、直接访问成员变量 (Directly access)

同上getter的第4类:

  • 先调用accessInstanceVariablesDirectly方法进行判断:是否允许直接方法成员变量。
  • 在允许后依次查找成员变量:_<key>_is<Key><key>is<Key>(当上一个成员变量没有时,才会查找到下一个)然后直接给成员变量的赋值。

例如:实现name的4个成员变量

代码语言:javascript复制
@implementation MOPerson {
  NSString* _name;
  NSString* _isName;
  NSString* name;
  NSString* isName;
}

当上一个未实现时,才会将值复制给下一个。 如果以上2类方式都访问不到,则会触发setValue: forUndefinedKey:方法抛出NSUnknownKeyException异常,导致crash。

我们可以重写该方法:打印出log,防止crash,方便debug…

代码语言:javascript复制
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
  NSLog(@"Error: setValue:%@ forUndefinedKey: %@", value, key);
}

还有MutableArrayMutableOrderedSet的访问搜索模式详情可见官网: 参考:官方文档 github上Demo地址

0 人点赞