接触iOS也有三四年了,感受颇多,经历过高潮也经过低潮,大浪淘沙 靠的都是硬功夫,经历过迷茫期,怀疑过自己,但是还是得坚持,生活就是这样,什么干好都是不容易的,最近有空来对这几年经历做些总结,每个人的经历都不一样,仁者见仁智者见智哈。
在一家公司待久了 慢慢都会变成业务性程序员,毕竟小企业还是以公司发展为目的,所以慢慢的对一些技术要求不是那么重要了,以能实现功能目的。所以我们大部分都是搬砖程序员,现在对iOS学习路线有些见解
1.原理,底层。知其然知其所以然,研究底层的过程中会恍然大悟,这是进阶必须要经历的阶段
2. 编程思想,设计模式,架构设计。项目中实战
3. 专业领域 --市场(ARKit\CoreML Open GL 等框架)---> 跨平台
扯得有点远了,言归正传。先上一张图看下API
270478-8035b5dfae171ec4.png.jpeg
KVC 全称 Key Value Coding
KVC主要对三种类型进行操作,基础数据类型及常量,对象类型,集合类型。
@interface BankAccount : NSObject
@property (nonatomic, strong) NSNumber *currentBalance;
@property (nonatomic, strong) Person *owner;
@property (nonatomic, strong) NSArray<transaction *> *transactions;
@end</transaction *>
在使用KVC时,直接将属性名当做key,并设置value,即可对属性进行赋值。
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
除了对当前对象的属性进行赋值外,还可以对其更“深层”的对象进行赋值。例如对当前对象的address属性的street属性进行赋值。KVC进行多级访问时,直接类似于属性调用一样用点语法进行访问即可
[myAccount setValue:@"中关村大街" forKeyPath:@"address.street"];
通过keyPath对数组进行取值时,并且数组中存储的对象类型都相同,可以通过valueForKeyPath:方法指定取出数组中所有对象的某个字段。例如下面例子中,通过valueForKeyPath:将数组中所有对象的name属性值取出,并放入一个数组中返回。
NSArray *names = [array valueForKeyPath:@"name"];
通过keyPath对数组进行取值时,并且数组中存储的对象类型都相同,可以通过valueForKeyPath:方法指定取出数组中所有对象的某个字段。例如下面例子中,通过valueForKeyPath:将数组中所有对象的name属性值取出,并放入一个数组中返回
NSArray *names = [array valueForKeyPath:@"name"];
多值操作
KVC还有更强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value。
- (NSDictionary<nsstring *, id> *)dictionaryWithValuesForKeys:(NSArray<nsstring *> *)keys;</nsstring *></nsstring *, id>
NSArray *keys = @[@"name",@"age"];
NSDictionary *dict = [person dictionaryWithValuesForKeys:keys];
{
age = 222;
name = afei;
}
同样,也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给User对象的属性赋值。
赋值时会遇到一些问题,例如服务器会返回一个id字段,但是对于客户端来说id是系统保留字段,可以重写setValue:forUndefinedKey:方法并在内部处理id参数的赋值。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:@"id"]) {
self.userId = [value integerValue];
}
}
关于KVC valueForKey:key 的调用顺序
先调用相关方法,先后顺序:
getter 方法:getKey(注意Key首字母大写) --> key -->isKey
如果没有相关方法:看 +(BOOl)accessInstanceVariablesDirectly 返回值
默认 YES 找成员变量,先后顺序:_key ->_isKey ->key -> isKey
NO 异常 valueForUnderFineKey 可以重写补救
异常信息
当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException的异常,并且应用程序Crash。
我们可以重写下面两个方法,根据业务需求合理的处理KVC导致的异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
异常处理
当通过KVC给某个非对象的属性赋值为nil时,此时KVC会调用属性所属对象的setNilValueForKey:方法,并抛出NSInvalidArgumentException的异常,并使应用程序Crash。
我们可以通过重写下面方法,在发生这种异常时进行处理。例如给name赋值为nil的时候,就可以重写setNilValueForKey:方法并表示name是空的。
- (void)setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
[self setValue:@"" forKey:@”name”];
} else {
[super setNilValueForKey:key];
}
}
集合属性操作
根据KVO的实现原理,是在运行时生成新的子类并重写其setter方法,在其内容发生改变时发送消息。但这只是对属性直接进行赋值会触发,如果属性是容器对象,对容器对象进行add或remove操作,则不会调用KVO的方法。可以通过KVC对应的API来配合使用,使容器对象内部发生改变时也能触发KVO。
在进行容器对象操作时,先调用下面方法通过key或者keyPath获取集合对象,然后再对容器对象进行add或remove等操作时,就会触发KVO的消息通知了。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
集合运算符
@count @max @min @sum @avg
私有访问
根据上面的实现原理我们知道,KVC本质上是操作方法列表以及在内存中查找实例变量。我们可以利用这个特性访问类的私有变量,例如下面在.m中定义的私有成员变量和属性,都可以通过KVC的方式访问。
这个操作对readonly的属性,@protected的成员变量,都可以正常访问。如果不想让外界访问类的成员变量,则可以将accessInstanceVariablesDirectly属性赋值为NO。
KVC在实践中也有很多用处,例如UITabbar或UIPageControl这样的控件,系统已经为我们封装好了,但是对于一些样式的改变并没有提供足够的API,这种情况就需要我们用KVC进行操作了。
可以自定义一个UITabbar对象,然后在内部创建自己想要的视图,并通过layoutSubviews方法在内部进行重新布局。然后通过KVC的方式,将UITabbarController的tabbar属性替换为自定义的类即可。
安全性检查
KVC存在一个问题在于,因为传入的key或keyPath是一个字符串,这样很容易写错或者属性自身修改后字符串忘记修改,这样会导致Crash。
可以利用iOS的反射机制来规避这个问题,通过@selector()获取到方法的SEL,然后通过NSStringFromSelector()将SEL反射为字符串。这样在@selector()中传入方法名的过程中,编译器会有合法性检查,如果方法不存在或未实现会报黄色警告。
[self valueForKey:NSStringFromSelector(@selector(object))];
网友评论