首先,引用官方文档的一个例子,说明一下runtime和kvc之间的联系:
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // An attribute
@property (nonatomic) Person* owner; // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
In fact, you can set all the properties of the myAccount object with the same method, using different key parameters. Because the parameter is a string, it can be a variable that is manipulated at run-time.
可以通过设置不同的参数,用相同的方法为myAccount
所有的属性赋值。因为参数是字符串,但是在运行时可以成为操作的变量。
这里对KVC不作过多的介绍,引用文档中的一段话
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.
在我们的实际应用中,下面的这4个方法是我们经常用到的:
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
但是有没有想过KVC是这么通过key来完成赋值的呢?其实在文档中已经给出了我们答案:
- Look for the first accessor named
set<Key>:
or_set<Key>
, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.- If no simple accessor is found, and if the class method
accessInstanceVariablesDirectly
returnsYES
, look for an instance variable with a name like_<key>
,_is<Key>
,<key>
, oris<Key>
, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.- Upon finding no accessor or instance variable, invoke
setValue:forUndefinedKey:
. This raises an exception by default, but a subclass ofNSObject
may provide key-specific behavior.
看下面的图可能会有更加直观的认识:
1.png实际开发中的应用:
1.动态的设值和取值
2.修改系统控价的隐藏属性
ex:修改UITextField占位文字的颜色和字体大小
[self setValue:[UIColor grayColor] forKeyPath:@"_placeholderLabel.textColor"];
[self setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
3.字典转模型的使用
在转模型之前先来一个生成模型属性的方法:
- (void)propertyWithDict:(NSDictionary *)dict{
if (![dict isKindOfClass:[NSDictionary class]]) {
NSLog(@"param type is not NSDictionary!");
return;
}
NSMutableString * mString = [NSMutableString string];
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
NSMutableString * property = [NSMutableString stringWithFormat:@"%@",@""];
if ([value isKindOfClass:NSClassFromString(@"__NSCFString")] ||
[value isKindOfClass:NSClassFromString(@"NSTaggedPointerString")]) {
[property appendString:[NSString stringWithFormat:@"@property (nonatomic, copy) NSString *%@;",key]];
}
else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
NSScanner * scan = [NSScanner scannerWithString:[NSString stringWithFormat:@"%@",value]];
int val1;
float val2;
if ([scan scanInt:&val1] && [scan isAtEnd]) {
[property appendString:[NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key]];
}
if ([scan scanFloat:&val2] && [scan isAtEnd]) {
[property appendString:[NSString stringWithFormat:@"@property (nonatomic, assign) float %@;",key]];
}
}
else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
[property appendString:[NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key]];
}
else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
[property appendString:[NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key]];
}
else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
[property appendString:[NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key]];
}
else{
[property appendString:[NSString stringWithFormat:@"%@ --- %@",key,value]];
}
[mString appendFormat:@"\n%@\n",property];
}];
NSLog(@"------生成模型的字符串 mString ----- %@",mString);
}
这样我们就可以做转换工作了,可以通过setValuesForKeysWithDictionary:
方法来做转换,但是这样做的前提是保证dict
里面的key和model
的属性一一对应,不然的话,须重写setValue:forUndefinedKey:
来防止crash。这种方式是遍历dict
里面的key去给model赋值,我们可以换一种思路,反过来遍历model里面的属性,从dict
里面渠道对应的key的值。
字典转模型的方法,为NSObject添加一个分类:
#import "NSObject+Model.h"
#import <objc/runtime.h>
@implementation NSObject (Model)
+ (instancetype)modelWithDictionary:(NSDictionary *)dict{
id model = [[self alloc] init];
unsigned int ivarCount;
Ivar * ivarList = class_copyIvarList([self class], &ivarCount);
for (int i = 0; i < ivarCount; i++) {
NSString * propertyName = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
NSString * propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivarList[i])];
// 去掉_
NSString * key = [propertyName substringFromIndex:1];
id value = dict[key];
// videoTopic , @" @\"propertyType\" "
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType isEqualToString:@"NSDictionary"]) {
NSRange range = [propertyType rangeOfString:@"\""];
/** propertyType\ */
propertyType = [propertyType substringFromIndex:range.location + range.length];
range = [propertyType rangeOfString:@"\""];
// propertyType
propertyType = [propertyType substringToIndex:range.location];
Class cls = NSClassFromString(propertyType);
if (cls) {
value = [cls modelWithDictionary:value];
}
}
// 数组里面元素需要转换成模型情况
if([value isKindOfClass:[NSArray class]]){
if ([self respondsToSelector:@selector(wsy_objectInArray)]) {
// 取出字典中的模型类型
NSString * modelStr = [self wsy_objectInArray][key];
Class modelClass = NSClassFromString(modelStr);
if (modelClass) {
NSMutableArray * temp = [NSMutableArray array];
for (NSDictionary * dic in value) {
id mod = [modelClass modelWithDictionary:dic];
[temp addObject:mod];
}
value = temp;
}
}
}
if (value) {
[model setValue:value forKey:propertyName];
}
}
return model;
}
@end
// 数组中需要转换模型的方法,key:为数组名,value:模型类型。
+ (NSDictionary *)wsy_objectInArray{
return @{
@"videoTag":@"VideoTopic"
};
}
至此,我们字典转模型的简单操作就算告一段落了。。。
官方文档
网友评论