美文网首页
ObjC 学习笔记(三):property

ObjC 学习笔记(三):property

作者: zevwings | 来源:发表于2019-07-21 12:07 被阅读0次

    在我们将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++] = &prop;
            }
            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互转,比较知名的就有JSONModelMJExtension,我们可以通过阅读这些成熟的框架来了解更多关于property_t的使用。

    更好的阅读体验可以参考个人网站:https://zevwings.com

    相关文章

      网友评论

          本文标题:ObjC 学习笔记(三):property

          本文链接:https://www.haomeiwen.com/subject/xgumlctx.html