文章目录- Accessor Search Patterns 访问者搜索模式
- 一、Getter 搜索模式
- 1、简单访问器(simple accessor)
- 1、数组访问器 (Array accessor)
- 3、集合访问器 (Collection accessor)
- 4、直接访问成员变量 (Directly access)
- 二、Setter 搜索模式
- 1、简单访问器(simple accessor)
- 2、直接访问成员变量 (Directly access)
- 1、简单访问器(simple accessor)
- 1、数组访问器 (Array accessor)
- 3、集合访问器 (Collection accessor)
- 4、直接访问成员变量 (Directly access)
- 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时:
MOPerson *person = [[MOPerson alloc] init];
NSLog(@"%@", [person valueForKey:@"name"]);
首先会进入第一种类型,简单访器的查找:
1、简单访问器(simple accessor)
依次搜索实例中的方法:get<Key>
、<key>
、is<Key>
、_<key>
例如,name的如下方法:(当上一个方法没有实现时,才会查找的下一个方法)
- (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数组 :
@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:
,实现的话可以提高性能:
// 某些情况下会调用到,实现可以提高性能
- (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>:
,三个方法必须都实现才能完成第三类查找。
实现第三类访问方法:
- (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
)。
(BOOL)accessInstanceVariablesDirectly {
return YES;
}
- 在允许后依次查找成员变量:
_<key>
、_is<Key>
、<key>
、is<Key>
(当上一个成员变量没有时,才会查找到下一个)然后直接使用成员变量的值。 例如:实现name的这4个成员变量
@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时:
MOPerson *person = [[MOPerson alloc] init];
[person setValue:@"momo" forKey:@"name"];
1、简单访问器(simple accessor)
依次查找:set<Key>:
、_set<Key>:
、setIsName:
(我看官网没有写setIsName:
,不知道为什么,但是它是valid的)
实现name的简单访问器方法:
// 依次查找: 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);
}
还有MutableArray
、MutableOrderedSet
的访问搜索模式详情可见官网:
参考:官方文档
github上Demo地址