目录
- KVC
- 观察者模式:KVO与通知
- 精度问题
- 三目运算符
- 点语法
1. KVC(Key-value coding)键值编码。用字符串动态去操作对象
其实现方法是使用字符串描述要更改的对象状态部分。通过Key
名直接访问对象的属性,或者给对象的属性赋值。这样就可以在运行时动态在访问和修改对象的属性。而不是在编译时确定。
所有的对象都可以使用KVC
键统一是字符串,而值是不支持基本数据类型的,必须将值转换为NSNumber或者NSValue类型
操作对象的属性和对象属性的属性,访问变量的属性,即使该属性没有get,set方法也可以调用
Human *human = [[Human alloc]init];
//将name属性设置为"holydancer"
[human setValue:@"holydancer" forKey:@"name"];
//将human中的name属性取出
NSString *nameOfHuman=[human valueForKey:@"name"];
KVC集合运算符
ProductModel *model = [[ProductModel alloc]init];
model.name = @"iMac";
model.price = @18888;
ProductModel *model1 = [[ProductModel alloc]init];
model1.name = @"iphone";
model1.price = @6999;
NSArray *array = @[model,model1];
简单类型的集合操作符:返回 strings, numbers, dates,简单集合操作符作用于 array 或者 set 中相对于集合操作符右侧的属性。包括 @avg, @count, @max, @min, @sum.
NSString *name = [array valueForKeyPath:@"@count"];//返回集合中对象总数的 NSNumber 对象。操作符右边没有键路径。
NSNumber *price_max = [array valueForKeyPath:@"@max.price"];//比较由操作符右边的键路径指定的属性值,并返回比较结果的最大值。最大值由指定的键路径所指对象的 compare: 方法决定
NSString *price_min = [array valueForKeyPath:@"@min.price"];//返回的是集合中的最小值
NSNumber *price_sum = [array valueForKeyPath:@"@sum.price"];//属性值的总和
NSNumber *price_avg = [array valueForKeyPath:@"@avg.price"];//转换为 double, 计算其平均值,返回该平均值的 NSNumber 对象。当均值为 nil 的时候,返回 0.
提示:你可以简单的通过把 self 作为操作符后面的 key path 来获取一个由 NSNumber 组成的数组或者集合的总值,例如对于数组 @[@(1), @(2), @(3)] 可使用 valueForKeyPath:@"@max.self" 来获取最大值。
NSLog(@"%@,%@,%@,%@,%@",name,price_max,price_min,price_sum,price_avg);
对象操作符,返回 NSArray 对象实例:对象操作符包括 @distinctUnionOfObjects 和 @unionOfObjects, 返回一个由操作符右边的 key path 所指定的对象属性组成的数组。其中 @distinctUnionOfObjects 会对数组去重,而 @unionOfObjects 不会。
NSArray *unionOfObjects = [array valueForKeyPath:@"@unionOfObjects.name"]; // 1.
NSArray *distinctUnionObjects = [array valueForKeyPath:@"@distinctUnionOfObjects.name"]; //2.
NSLog(@"%@,%@",unionOfObjects,distinctUnionObjects);
数组和集合操作符,返回的是一个 array 或者 set 对象
数组和集合操作符作用对象是嵌套的集合,也就是说,是一个集合且其内部每个元素是一个集合。数组和集合操作符包括 @distinctUnionOfArrays,@unionOfArrays,@distinctUnionOfSets:
@distinctUnionOfArrays / @unionOfArrays 返回一个数组,其中包含这个集合中每个数组对于这个操作符右面指定的 key path 进行操作之后的值。 distinct 版本会移除重复的值。
@distinctUnionOfSets 和 @distinctUnionOfArrays 差不多, 但是它期望的是一个包含着 NSSet 对象的 NSSet ,并且会返回一个 NSSet 对象。因为集合不能包含重复的值,所以它只有 distinct 操作。
NSArray *array1 = @[model,model1];
NSArray *totalArray = @[array,array1];
NSArray *distinctUnionOfArrays = [totalArray valueForKeyPath:@"@distinctUnionOfArrays.name"];
NSArray *unionOfArrays = [totalArray valueForKeyPath:@"@unionOfArrays.name"];
NSLog(@"%@,%@",distinctUnionOfArrays,unionOfArrays);
注意: 如果操作符右侧 key path 指定的对象为 nil,那么返回的数组中会包含 NSNull 对象.
- 四个主要方法
必须手动将值类型转换成NSNumber
或者NSValue
类型,才能设置为value
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
valueForKey:
总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
-
嵌套数据,
KEY
为数组或者字典- 对于有序的容器,可以用下面的方法:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- 对于无序的容器,可以用下面的方法:
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
键路径:嵌套数据中,使用
KeyPath
-
在
KVC
中处理异常情况- 如果某对象有一个常用数据类型,比如
bool
,在用setvalue:
设置value
的时候,需要实现setNilValueForKey:(NSString *)key
- 如果某对象有一个常用数据类型,比如
在用
set value
: 设置value的时候,如果该对象不存在该属性,比如bool
,需要实现- (void)setValue:(id)value forUndefinedKey:(NSString *)key
-
KVC使用场景
- 动态地取值和设值:利用KVC动态的取值和设值是最基本的用途了。
用KVC来访问和修改私有变量:利用KVC可以随意修改一个对象的属性和变量(即使是私有变量)
对于类里的私有属性,Objective-C
是无法直接访问的,但是KVC
是可以的。Model和字典转换
利用KVC集合运算符,KVC可以通过运算符层次查找对象的属性;KVC获取值不仅可以返回一个数据,还可以将某一个属性的所有值,数据归类出来(B不一定是类,也可以是数组)
利用KVC可以修改系统的只读变量,修改一些控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些空间的API
,这样我们就无法正常地访问和修改这些控件的样式。而KVC
在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField
中的placeHolderText
了。
因为数据造成crash
的原因大概几点:
- 使用字面量创建数组、字典,
value
为nil
- 使用KVC方法给数组字典赋值为
nil
-
null
发送方法。其他的诸如null
的判断方法及给控件赋值都不会引起crash。
2. 观察者模式:KVO与通知
iOS的一种设计模式, 观察者设计模式,依赖于 Objective-C 强大的 Runtime。观察者模式包含:
1.通知机制(notification)
2.KVO机制【可参考iOS--KVO的实现原理与具体应用】
- 通知机制:
委托机制是代理“一对一”的对象之间的通信,而通知机制是广播“一对多”的对象之间的通信。
//A类获取通知中心,并发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"statusBarHidden" object:nil userInfo:dic];
//B类注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarHidden:) name:@"statusBarHidden" object:nil];
//释放所有通知
- (void)removeObserver:(id)observer;
//释放名称为aName的通知
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;
- KVO
KVO提供一种机制,指定一个被观察对象,当对象某个属性发生更改时,对象会获得通知,并作出相应处理。KVO这种编码方式使用起来很简单,很适用与model修改后,引发的UIVIew的变化这种情况,当更改属性的值后,监听对象会立即得到通知;当指定的对象的属性被修改后,对象就会接受到通知,****前提是执行了setter方法、或者使用了KVC赋值****。
当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
原理:
- 当一个object有观察者时,动态创建这个object的类的子类
- 对于每个被观察的property,重写其set方法
- 在重写的set方法中调用- willChangeValueForKey:和- didChangeValueForKey:通知观察者
- 当一个property没有观察者时,删除重写的方法
- 当没有observer观察任何一个property时,删除动态创建的子类
- 注册,指定被观察者的属性
[objc addObserver:self forKeyPath:@"title" options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
- 实现回调方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{}
- 移除观察
[self removeObserver:self forKeyPath:@"title" context:nil];
- 两者区别:
notification比KVO多了发送通知的一步。两者都是一对多,但是对象之间直接的交互,notification明显多,需要notificationCenter来做为中间交互。
notification的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更显灵活方便.
3. 精度问题
- 原因:
NSNumber
的description
方法不够严谨,在调用NSNumber的description方法打印数值时,会发生精度损失。 - 建议 :
- 如果是double类型,处理精度有关的数据用
double
。建议把NSNumber转换成double再进行输出(NSString)或计算(CGFloat); - 有关浮点型数据,后台传字符串的格式,防止丢失精度.
- 如果是double类型,处理精度有关的数据用
NSNumber *value=dic[@"number"];
NSLog(@"value:%@", value);
输出:value:81.59999999999999
如果是double类型,建议把NSNumber转换成double再进行输出或计算 。
CGFloat numberValue = [self doubleValue];
NSString *value=[NSString stringWithFormat:@"%g",[dic[@"number"] doubleValue]];
4. 三目运算符
- 基本格式 : (关系表达式) ? 表达式1 : 表达式2;
- 执行流程 : 关系表达式为 真 返回表达式1 关系表达式为假 返回表达式2
int num1=8,num2=3,result=0;
result= num1>num2?num1:num2;
//因为num1>num2 成立 所以最后结果为num1的值
5. 点语法
- 点语法的本质还是方法调用,当使用点语法时,编译器会自动展开成相应的方法,而不是访问成员变量.
- 切记点语法的本质是转换成相应的对
setter
和getter
方法调用,如果没有set
和get
方法,则不能使用点语法。 - 不要在
getter
与setter
方法中使用本属性的点语法