NSObject+YYModel之-modelCopy对象拷贝
前言:YYModel是由ibireme开发的一套小而精美的模型转换框架,采用分类的形式,无需继承框架的某个基类就可以方便地完成模型的转换,且内部做了自动类型转换和安全处理,可以有效地防止因模型类型和后台给的数据类型不一样而产生的崩溃问题。
近些天抽空拜读了一下其源码,果然是思维严谨,考虑的一些细节也很到位,让人自叹弗如。虽然作者说这个框架是花了两个周末的时间完成的,但是其代码质量还是非常让人惊艳的,值得仔细阅读。本文章就其中的一个对象的拷贝方法-modelCopy进行学习。
一、modelCopy拷贝原理
modelCopy的拷贝原理是:将已有对象获取其类名 通过 [obj.class. new]重新开辟一块新地址。存放新对象。但是新对象与旧对象的具体属性,比如说NSString *name; name 的地址却是同一个。再对新对象进行改变的时候(即重新赋值),只要把新对象的属性name指向新值的内存区域即可。这样对象与拷贝对象的属性具体值在被修改时两者互不影响。Ps:为什么要这样写呢,因为如果你把对象p1 = p2这样赋值的话,改变p2里边的某个属性的值,p1的这个属性值也被改变了。
接下来我们就用图来说明一下:
首先我先设定一个类 Person (这个类已经被教师用烂了,我这儿也用一下)
原有的一个对象 personTom {汤姆,NO,18} 地址为0x6d1,现在要将personTom克隆出一个来。那么被克隆出来的personJerry{汤姆,NO,18} 地址为0x6E3。从其具体的某个属性Name的值来看,两个对象name所存放的地址 都是0Xeff1。所以利用modelCopy拷贝出来的某个属性具体值是一样的。 那么问题就来了,怎么改变新对象personJerry的name呢。 只需要将@“jerry"在堆中的地址0xedd2 给personJerry的name即可(图中绿线的指向)。
对象拷贝图二、源码解析
- (id)modelCopy{
// 这是NSObject对象直接调用本方法,所以 self 就是将要被复制的对象.(-_-)虽然比较基础,但还是提一下。
if (self == (id)kCFNull) return self;//self是空,则返回self
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class]; //下划线"_"开头是作者用于仅仅在sdk内部使用,外部可以使用的类作者没有添加”_“ 只是一种编程习惯. 这里 _YYModelMeta 简单理解就是一个类 里边有具体的成员变量和属性 if(modelMeta->_nsType)return[selfcopy];
/*作用:如果 对象类型unknown, 则将浅拷贝self
modelMeta->_nsType 就是访问 modelMeta 里边的成员变量 _nsType
*/
NSObject *one = [self.class new];//获取self的类名 新new了一个对象 for(_YYModelPropertyMeta*propertyMetainmodelMeta->_allPropertyMetas) {
//遍历self 对象的所有属性 "modelMeta->_allPropertyMetas"存放了self对象的所有属性名称 if(!propertyMeta->_getter|| !propertyMeta->_setter)continue;
//如果 propertyMeta 没有getter 或者 没有setter方法 就跳过本次循环,执行下次循环 if(propertyMeta->_isCNumber) {
//如果self的被遍历的这个属性 是一个 数值类型
switch(propertyMeta->_type&YYEncodingTypeMask) { //switch的条件 是 枚举值的二进制按位与运算 即1010 & 1111 = 1010
caseYYEncodingTypeBool: { //YYEncodingTypeBool c语言的bool类型 boolnum = ((bool(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
/* 整句话的意思就是一个Getter方法调用 即获取self 的这个bool类型的属性的值。类似于这:
- (bool)getMethod{
return ture;
}
分解理解:
bool num 定义一个bool 变量num来接受getter返回的值
bool (*) 修饰被调用方法的返回值类型 返回一个bool类型的值
id, objc_msgSend方法的第一个参数的类型,即任意对象类型。 SEL obj_msgSend 第二个参数 即obj_msgSend使用的方法名称 (void *) 一般来说这个修饰关键字可以省略, 笔者暂不理解作者为何这样写。待深入学习补充
objc_msgSend runtime中动态获取方法 的函数,获取后并且会执行 (id)self objc_msgSend 第一个参数 即任意oc对象类型 self 就是objc_msgSend将要操作的对象
propertyMeta->_getter 获取 propertyMeta 的成员变量_getter 成员变量的值就是一个SEL 类型的方法名称
*/
((void(*)(id,SEL,bool))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); /* 整句话的意思就是一个Setter方法调用 即 利用setter方法 给新对象one 的bool类型属性 赋值num。类似于这:
- (void) setMethod:(bool)num{
_type = type;
}
分解理解:
void (*) 修饰被调用方法的返回值类型 返回void
id, objc_msgSend方法的第一个参数的类型,即任意对象类型。 SEL objc_msgSend 第二个参数的方法名称
bool objc_msgSend 第三个参数 这个动态方法传入参数的值。即type (void *) 一般来说这个修饰关键字可以省略, 笔者暂不理解作者为何这样写。待深入学习补充
objc_msgSend runtime中动态获取方法 的函数,获取后并且会执行 (id)one objc_msgSend 第一个参数 即任意oc对象类型 one 就是objc_msgSend将要操作的对象
propertyMeta->_getter 获取 propertyMeta 的成员变量_setter 成员变量的值就是一个SEL 类型的方法名称
num num 传入setter的参数值
*/
}break;
case YYEncodingTypeInt8:
caseYYEncodingTypeUInt8: {
//uint8_t 就是无符号字符类型 就是对 unsigned char 重新命名 typedef uint8_tnum = ((bool(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,uint8_t))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); }break;
case YYEncodingTypeInt16:
case YYEncodingTypeUInt16: {
// typedef unsigned short uint16_t;
uint16_tnum = ((uint16_t(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,uint16_t))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); }break;
case YYEncodingTypeInt32:
case YYEncodingTypeUInt32: { //typedef unsigned int uint32_t; uint32_tnum = ((uint32_t(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,uint32_t))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); }break;
case YYEncodingTypeInt64:
case YYEncodingTypeUInt64: { //typedef unsigned long long uint64_t; uint64_tnum = ((uint64_t(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,uint64_t))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); }break;
caseYYEncodingTypeFloat: {
floatnum = ((float(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,float))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); }break;
case YYEncodingTypeDouble: {
doublenum = ((double(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,double))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num); }break;
case YYEncodingTypeLongDouble: {
longdoublenum = ((longdouble(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);
((void(*)(id,SEL,longdouble))(void*)objc_msgSend)((id)one, propertyMeta->_setter, num);
}// break; commented for code coverage in next line
default:break;
}
}else{ //当该属性不是一个数值类型时候
switch(propertyMeta->_type&YYEncodingTypeMask) {
case YYEncodingTypeObject:
case YYEncodingTypeClass:
caseYYEncodingTypeBlock: { //当是对象类型、类、block时候用 id来获取其值
idvalue = ((id(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter); ((void(*)(id,SEL,id))(void*)objc_msgSend)((id)one, propertyMeta->_setter, value); }break;
caseYYEncodingTypeSEL:
case YYEncodingTypePointer:
case YYEncodingTypeCString: { //size_t 经过代码测试 就是将 SEL 、void* 、 char* 值的地址转换成10进制 传给value
size_tvalue = ((size_t(*)(id,SEL))(void*)objc_msgSend)((id)self, propertyMeta->_getter);//这里getter获取到就是一个地址的10进制表示值。 size_t 可以理解为long、int
((void(*)(id,SEL,size_t))(void*)objc_msgSend)((id)one, propertyMeta->_setter, value);
}break;
case YYEncodingTypeStruct:
caseYYEncodingTypeUnion: {
/* 结构体、联合体类型
作者用了try catch 放置在转换结构体时候转crash。完备性操作
*/
@try{
NSValue*value = [selfvalueForKey:NSStringFromSelector(propertyMeta->_getter)]; if(value) {
[onesetValue:valueforKey:propertyMeta->_name];
}
}@catch(NSException *exception) {}
}// break; commented for code coverage in next line
default:break;
}
}
}
returnone;}
三、小结
很粗略地记录下部分阅读过程。YYModel有不少值得学习的地方,不管是代码风格还是考虑问题的全面性,这些都需要通过阅读源码来了解。
如发现不当之处,欢迎留言指出。代码之路漫长,希望与你同行