ObjC 学习笔记(三):property

在我们将JSON数据转换为Model过程中,我们常常会使用MJExtension或者JSONModel等框架,那他们的实现和在runtime中都是怎么去实现的呢?

首先,我们做一个简化版的JSONModel

// 定义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_copyPropertyListproperty_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_getNameproperty_getAttributes从命名上我们可以看出,这两个方法是用于获取变量的名字和类型。

我们接下来先看一下property_t的定义,这个结构中只有nameattributes两个属性,分别存储了名字和类型。

struct property_t {
    const char *name;
    const char *attributes;
};

然后我们使用property_getNameproperty_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

你可能感兴趣的:(ObjC 学习笔记(三):property)