ObjC 学习笔记(三):property
在我们将JSON
数据转换为Model
过程中,我们常常会使用MJExtension或者JSONModel等框架,那他们的实现和在runtime中都是怎么去实现的呢?
首先,我们做一个简化版的JSON
转Model
。
// 定义Model
@interface Product : NSObject
@property (nonatomic, copy) NSString *productId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double price;
@end
@implementation Product
- (NSString *)description {
return [NSString stringWithFormat:@"productId: %@, name: %@, price: %f", _productId, _name, _price];
}
@end
// 实现最基本的JSON转Model,不考虑类型匹配等问题
- (void)json2Model:(NSDictionary *)dict {
Product *product = [[Product alloc] init];
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList([Product class], &propertyCount);
for (int idx = 0; idx < propertyCount; idx ++) {
objc_property_t property = properties[idx];
const char *name = property_getName(property);
id value = dict[@(name)];
[product setValue:value forKey:@(name)];
}
NSLog(@"product : %@", product);
}
我们从上面的代码中,可以找到实现这个功能的两和个方法class_copyPropertyList
和property_getName
,下面我们从class_copyPropertyList
开始学习属性相关的内容。
class_copyPropertyList
接下来,我们看一下class_copyPropertyList
的实现
objc_property_t *
class_copyPropertyList(Class cls, unsigned int *outCount)
{
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
assert(cls->isRealized());
// 从类中的定义中找到相关数据,`class->data()`会返回函数、变量、协议等信息
auto rw = cls->data();
property_t **result = nil;
// 获取变量数量
unsigned int count = rw->properties.count();
if (count > 0) {
// 分配内存空间
result = (property_t **)malloc((count + 1) * sizeof(property_t *));
count = 0;
// 遍历变量,存储到结果数据数组中
for (auto& prop : rw->properties) {
result[count++] = ∝
}
result[count] = nil;
}
if (outCount) *outCount = count;
return (objc_property_t *)result;
}
上面注释中我们给几个关键节点添加了注释,我们可以清晰的看到函数
、变量
、协议
等信息都是由cls->data()
这个函数返回的,我们进一步的去了解变量在类结构中是如何存储的。
在objc_class
中,我们可以看到变量等都是使用bits.data()
获取相关内容。
struct objc_class : objc_object {
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
....
}
从上述描述中我们可以了解到变量的存储结构objc_class.bits.data()->properties。
在了解class_copyPropertyList
之后,我们再来看看其他与变量相关的方法。
property_getName 和 property_getAttributes
property_getName
和property_getAttributes
从命名上我们可以看出,这两个方法是用于获取变量的名字和类型。
我们接下来先看一下property_t
的定义,这个结构中只有name
和attributes
两个属性,分别存储了名字和类型。
struct property_t {
const char *name;
const char *attributes;
};
然后我们使用property_getName
和property_getAttributes
两个方法就可以轻松的获取到变量的信息,具体实现如下:
const char *name = property_getName(property);
const char *attr = property_getAttributes(property);
property_copyAttributeList
这个方法并不是我们常用的方法,我们只需要大致了解一下他的实现即可。
// 外部调用方法
objc_property_attribute_t *property_copyAttributeList(objc_property_t prop,
unsigned int *outCount)
{
if (!prop) {
if (outCount) *outCount = 0;
return nil;
}
mutex_locker_t lock(runtimeLock);
return copyPropertyAttributeList(prop->attributes,outCount);
}
// 内部实现方法
objc_property_attribute_t *
copyPropertyAttributeList(const char *attrs, unsigned int *outCount)
{
if (!attrs) {
if (outCount) *outCount = 0;
return nil;
}
// Result size:
// number of commas plus 1 for the attributes (upper bound)
// plus another attribute for the attribute array terminator
// plus strlen(attrs) for name/value string data (upper bound)
// plus count*2 for the name/value string terminators (upper bound)
unsigned int attrcount = 1;
const char *s;
for (s = attrs; s && *s; s++) {
if (*s == ',') attrcount++;
}
size_t size =
attrcount * sizeof(objc_property_attribute_t) +
sizeof(objc_property_attribute_t) +
strlen(attrs) +
attrcount * 2;
objc_property_attribute_t *result = (objc_property_attribute_t *)
calloc(size, 1);
objc_property_attribute_t *ra = result;
char *rs = (char *)(ra+attrcount+1);
attrcount = iteratePropertyAttributes(attrs, copyOneAttribute, &ra, &rs);
assert((uint8_t *)(ra+1) <= (uint8_t *)result+size);
assert((uint8_t *)rs <= (uint8_t *)result+size);
if (attrcount == 0) {
free(result);
result = nil;
}
if (outCount) *outCount = attrcount;
return result;
}
从上面的代码我们可以看到在实现文件里面将属性拆分为T
, C
, N
, V
,分别对应属性的类型,copy
, nonatomic
, 属性名称
, 并输出位一个objc_property_attribute_t *
数组保存属性信息。
property_copyAttributeValue
property_copyAttributeValue
也不是一个常用的方法,我们可以通过T
, C
, N
, V
获取属性中对应的值。
char *copyPropertyAttributeValue(const char *attrs, const char *name)
{
char *result = nil;
iteratePropertyAttributes(attrs, findOneAttribute, (void*)name, &result);
return result;
}
class_addProperty 和 class_replaceProperty
这两个方法用于修改和替换属性,最后都使用_class_addProperty
方法进行操作,通过bool replace
区分是添加变量或者修改变量。
_class_addProperty(Class cls, const char *name,
const objc_property_attribute_t *attrs, unsigned int count,
bool replace)
{
if (!cls) return NO;
if (!name) return NO;
// 判断属性是否存在,如果属性存在则无法添加
property_t *prop = class_getProperty(cls, name);
if (prop && !replace) {
// already exists, refuse to replace
return NO;
}
else if (prop) {
// replace existing
mutex_locker_t lock(runtimeLock);
try_free(prop->attributes);
// 通过`copyPropertyAttributeString`方法生成变量替换的变量
prop->attributes = copyPropertyAttributeString(attrs, count);
return YES;
}
else {
mutex_locker_t lock(runtimeLock);
assert(cls->isRealized());
property_list_t *proplist = (property_list_t *)
malloc(sizeof(*proplist));
proplist->count = 1;
proplist->entsizeAndFlags = sizeof(proplist->first);
proplist->first.name = strdupIfMutable(name);
// 通过`copyPropertyAttributeString`方法生成变量替换的变量
proplist->first.attributes = copyPropertyAttributeString(attrs, count);
cls->data()->properties.attachLists(&proplist, 1);
return YES;
}
}
总结
property_t
可以帮助我们动态的获取和修改类的变量,最常是用的就是在JSON与Model互转,比较知名的就有JSONModel和MJExtension,我们可以通过阅读这些成熟的框架来了解更多关于property_t
的使用。
更好的阅读体验可以参考个人网站:https://zevwings.com