1.前言在技术论坛上看到一个有趣的KVC案例:@interfacePerson:NSObject@property(nonatomic,copy)NSString*name;@property(nonatomic,assign)NSIntegerage;@endPerson*person=[Personnew];person.name=@"Tom";person.age=10;[personsetValue:@"100"forKey:@"age"];//这里赋一个字符串,类中的属性是Integer,第一反应是崩溃,因为OC是类型敏感的。但是自己实现打印出来的结果出乎意料,没有崩溃,赋值成功。于是就有了深入了解KVC内部实现的想法!2、什么是KVCkey-value-coding:键值编码,一种可以通过键名间接访问和分配对象属性的机制。KVC是通过NSObject、NSArray、NSDictionary等类来实现的。主要方法包括:-(nullableid)valueForKey:(NSString*)key;-(void)setValue:(nullableid)valueforKey:(NSString*)key;-(void)setNilValueForKey:(NSString*)key;-(void)setValue:(nullableid)valueforUndefinedKey:(NSString*)key;-(nullableid)valueForUndefinedKey:(NSString*)key;3、KVC执行分析那么在上面的案例中-(void)setValue:(nullableid)valueforKey:(NSString*)key;的执行过程是怎样的?使用反汇编工具获取Foundation.framework的部分源码(为了解决与系统API的冲突,添加前缀_d,NS替换为DS),分析KVC执行过程。(流程中的边界判断已经忽略,想了解的可以参考源码,本文只探讨主流程。)3.1设置属性3.1.1查找访问器方法或成员变量+(DSKeyValueSetter*)_d_createValueSetterWithContainerClassID:(id)containerClassIDkey:(NSString*)key{DSKeyValueSetter*setter=nil;charkey_cstr_upfirst[key_cstr_len+1];key_cstr[key_cstr_len+1];...方法method=NULL;//依次搜索set,_set,setIs。如果找到,则生成对应的seterDSKeyValueMethodForPattern(self,"setIs%s:",key_cstr_upfirst))){//生成方法:包括选择器,IMP。返回和参数类型stringsetter=[[DSKeyValueMethodSetteralloc]initWithContainerClassID:containerClassIDkey:keymethod:method];}elseif([selfaccessInstanceVariablesDirectly]){//如果没有找到对应的accessor方法且工厂方法accessInstanceVariablesDirectly==ture,则依次查找成员变量_,_is,,is(注意key首字母大写,如果找到则生成对应的setter)Ivarivar=NULL;如果((ivar=DSKeyValueIvarForPattern(self,"_%s",key_cstr))||(ivar=DSKeyValueIvarForPattern(self,"_is%s",key_cstr_upfirst))||(ivar=DSKeyValueIvarForPattern(self,"%s",key_cstr))||(ivar=DSKeyValueIvarForPattern(self,"is%s",key_cstr_upfirst))){setter=[[DSKeyValueIvarSetteralloc]initWithContainerClassID:containerClassIDkey:keycontainerIsa:selfivar:ivar];}}...returnsetter;}查找顺序如下:查找accessor方法:set,_set,setIs如果在第1步没有找到对应的方法且accessInstanceVariablesDirectly==YES,则查找顺序如下:_,_is,,未找到,则调用valueForUndefinedKey并抛出异常3.1.2生成setter+(DSKeyValueSetter*)_d_createOtherValueSetterWithContainerClassID:(id)containerClassIDkey:(NSString*)key{return[[DSKeyValueUndefinedSetteralloc]initWithContainerClassID:containerClassIDkey:keycontainerIsa:self];}//构造方法确定方法编号d_setValue:forUndefinedKey:和方法指针IMP_DSSetValueAndNotifyForUndefinedKey-(id)initWithContainerClassID:(id)containerClassIDkey:(NSString*)keycontainerIsa:(Class)containerIsa{...return[superinitWithContainerClassID:containerClassIDkey:keyimplementation:method_getImplementation(class_getInstanceMethod(containerIsa,@selector(d_setValue:forUndefinedKey:)))selector:@selector(d_setValue:forUndefinedKey:)extraArguments:argumentscount:1];}3.1.3赋值基本访问器方法,变量查找和异常处理已经明明知道,那么上面的例子是怎么出现的呢?明明传入的是字符串,最后赋值的时候却改成了accessor方法对应的类型?继续深究!DSKeyValueSetter对象已经生成,即发送消息的object对象,accessor方法名SEL,accessor函数指针IMP,使用KVC时传入的Key和Value都确定了。下面进入方法调用阶段:_DSSetUsingKeyValueSetter(self,setter,value);IMP指针是_DSSetIntValueForKeyWithMethod,定义如下:文章开头提到的效果之所以在这里起作用,IMP调用时完成[valuevalueGetSelectorName],将对应的NSNumber转换为简单数据类型.这是整数值。void_DSSetIntValueForKeyWithMethod(idobject,SELselector,idvalue,NSString*key,Methodmethod){//object:personselector:setAge:value:@(100)key:agemethod:selector+IMP+返回类型和参数类型为_extraArgument2,生成__DSSetPrimitiveValueForKeyWithMethod(object,selector,value,key,method,int,intValue);}#defineif(value){\void(*imp)(id,SEL,valueType)=(void(*)(id,SEL,valueType))method_getImplementation(method);\imp(object,method_getName(method),[valuevalueGetSelectorName]);\调用person的setAge:方法。参数为100}\else{\[objectsetNilValueForKey:key];\}\}while(0)//如果第一步没有找到accessor方法,只找到成员变量,则直接进行赋值操作void_DSSetIntValueForKeyInIvar(idobject,SELselector,idvalue,NSString*key,Ivarivar){if(value){*(int*)object_getIvarAddress(object,ivar)=[valueintValue];}else{[objectsetNilValueForKey:key];}}开头的问题完美解决!执行过程如下:3.2Value3.2.1查找访问器方法或成员变量+(DSKeyValueGetter*)_d_createValueGetterWithContainerClassID:(id)containerClassIDkey:(NSString*)key{DSKeyValueGetter*getter=nil;...方法getMethod=NULL;if((getMethod=DSKeyValueMethodForPattern(self,"get%s",keyCStrUpFirst))||(getMethod=DSKeyValueMethodForPattern(self,"%s",keyCStr))||(getMethod=DSKeyValueMethodForPattern(self,"is%s",keyCStrUpFirst))||(getMethod=DSKeyValueMethodForPattern(self,"_get%s",keyCStrUpFirst))||(getMethod=DSKeyValueMethodForPattern(self,"_%s",keyCStr))){getter=[[DSKeyValueMethodGetteralloc]initWithContainerClassID:containerClassIDkey:keymethod:getMethod];}//找到对应的访问器方法...elseif([selfaccessInstanceVariablesDirectly]){//找到属性Ivarivar=NULL;if((ivar=DSKeyValueIvarForPattern(self,"_%s",keyCStr))||(ivar=DSKeyValueIvarForPattern(self,"_is%s",keyCStrUpFirst))||(ivar=DSKeyValueIvarForPattern(self,"%s",keyCStr))||(ivar=DSKeyValueIvarForPattern(self,"is%s",keyCStrUpFirst))){getter=[[DSKeyValueIvarGetteralloc]initWithContainerClassID:containerClassIDkey:keycontainerIsa:selfivar:ivar];}}}if(!getter){getter=[self_d_createValuePrimitiveGetterWithContainerClassID:containerClassIDkey:key];}returngetter;}按照get,,is,_的顺序查找成员方法如果1.没有找到对应的方法并且accessInstanceVariablesDirectly==YES,继续查找成员变量,查找顺序为_,_is,,is如果1、2没有找到对应的对于方法和属性,调用valueForUndefinedKey:并抛出异常3.2.2如果accessor方法或成员变量在上述步骤中没有定位到,则按照以下流程生成对应的getteraccessor方法生成IMP-(id)initWithContainerClassID:(id)containerClassIDkey:(NSString*)keymethod:(Method)method{NSUIntegermethodArgumentsCount=method_getNumberOfArguments(方法);NSUIntegerextraAtgumentCount=1;如果(methodArgumentsCount==2){char*returnType=method_copyULReturnType(methodswp);NIMP(LreturnType[0]){...case'i':{imp=(IMP)_DSGetIntValueWithMethod;}休息;...免费(返回类型);如果(imp){void*arguments[3]={0};如果(extraAtgumentCount>0){参数[0]=方法;}返回[superinitWithContainerClassID:containerClassIDkey:keyimplementation:impselector:method_getName(method)extraArguments:argumentscount:extraAtgumentCount];imp类型定义如下:NSNumber*_DSGetIntValueWithMethod(idobject,SELselctor,Methodmethod){//return[[[NSNumberalloc]initWithInt:((int(*)(id,SEL))method_getImplementation(method))(object,method_getName(method))]autorelease];}3.2.3Value调用如下:4.简单数据类型KVC打包反汇编关系NSNunber:NSValue5,KVC高级修改数组中对象的属性[arrayvalueForKeyPath:@”uppercaseString”]可以修改在批量使用KVC属性的成员变量值求和,平均值,最大值,最小值NSNumbersum=[arrayvalueForKeyPath:@”@sum.self”];NSNumberavg=[arrayvalueForKeyPath:@”@avg.self”];NSNumbermax=[arrayvalueForKeyPath:@”@max.self”];NSNumbermin=[arrayvalueForKeyPath:@”@min.self”];6、数据筛选经过以上分析,我们可以了解KVC真正的执行过程。下面结合日常工程中的实际应用优雅地处理数据过滤问题。使用KVC处理可以减少大量for的使用,增加代码的可读性和健壮性。如图:项目中的明细如下:修改拒收数量时,更新正确发货总数和拒收总数,勾选明细更新正确发货总数和拒收总数,全选,清除和反向选择。如果通常的做法是循环遍历每个操作计算总数并记录选择状态。下面是使用KVC的实现过程。模型浸及:@property(nonatomic,copy)NSString*skuCode;@property(nonatomic,copy)NSString*goodsName;@property(nonatomic,assign)NSIntegertotalAmount;@property(nonatomic,assign)NSIntegerrejectAmount;@property(nonatomic,assign)NSIntegerdeliveryAmount;///单选@property(nonatomic,assign)BOOLselected;1)更新总数-(void)updateDeliveryInfo{//总数NSNumber*allDeliveryAmount=[self.orderDetailModel.deliveryGoodsDetailListvalueForKeyPath:@"@sum.总金额”];//妙投数NSNumber*allRealDeliveryAmount=[self.orderDetailModel.deliveryGoodsDetailListvalueForKeyPath:@"@sum.deliveryAmount"];//拒收数NSNumber*allRejectAmount=[self.orderDetailModel.deliveryGoodsDetailListvalueForKeyPath:@"@sum.rejectAmount"];}2)全选[self.orderDetailModel.deliveryGoodsDetailListsetValue:@(YES)forKeyPath:@”selected”];3)清空[self.orderDetailModel.deliveryGoodsDetailListsetValue:@(NO)forKeyPath:@”selected”];4)反选NSPredicate*selectedPredicate=[NSPredicatepredicateWithFormat:@"selected==%@",@(YES)];NSArray*selectedArray=[self.orderDetailModel.deliveryGoodsDetailListfilteredArrayUsingPredicate:selectedPredicate];NSPredicate*unSelectedPredicate=[NSPredicatepredicateWithFormat:@"selected==%@",@(NO)];NSArray*unSelectedArray=[self.orderDetailModel.deliveryGoodsDetailListfilteredArrayUsingPredicate:unSelectedPredicate];[selectedArraysetValue:@(NO)forKeyPath:@"selected"];[unSelectedArraysetValue:@(YES)forKeyPath:@"selected"];7.小结KVC在处理简单数据类型时,会经过数据封装、拆解、转换成相应的数据类型。通过KVC的特性,我们可以在日常使用中更加优雅的过滤和处理数据。优点如下:更高的可读性和更好的健壮性。