构建iOS-Model层(二)类型解析

上篇,讲到了实现Model解析的原理,但是不完整,类型解析只针对了我们特定的类型,在这篇,将完善类型解析这一部分。

Type Encoding

首先,抛出苹果给的文档。

Objective-C Runtime Programming Guide-Type Encodings

Objective-C Runtime Programming Guide-NextPrevious
Declared Properties

其中的Type Encoding类型编码摘要如下:

Code Meaning
c A char
i An int
s A short
l A long ,l is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type…} A structure
(name=type…) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

另外,可以通过@encode来获得编码:

NSLog(@"int        : %s", @encode(int));

上面输出:

​ int :i

然而,除了从属性中的attribute中处理类型信息,比如T@"NSString",C,N,V_name根据上篇的解析,我们还需要对属性的内存管理语义。

Code Meaning
R The property is read-only (readonly).
C The property is a copy of the value last assigned (copy).
& The property is a reference to the value last assigned (retain).
N The property is non-atomic (nonatomic).
G The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,).
S The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,).
D The property is dynamic (@dynamic).
W The property is a weak reference (__weak).
P The property is eligible for garbage collection.
t Specifies the type using old-style encoding.

至此,对于编码,我们已经全部列出来了,下面就来解析它。

在这里,我们将只解析类型信息,而关于内存管理语义不作解析,因为Model转换时,不需要关心内存管理语义,它由ARC统一管理。

流行的Model解析库中YYModel对内存管理语义也做了解析,而MJExtension只对类型作了解析。在这里,为了简化,也只对类型做解析。

下面是定义的基本类型:

/**
 Type encoding's type.
 */
typedef NS_OPTIONS(NSUInteger, JSEncodingType) {
    JSEncodingTypeMask              = 0xFF, ///< mask of type value
    JSEncodingTypeChar,             //c
    JSEncodingTypeInt,              //i
    JSEncodingTypeShort,            //s
    JSEncodingTypeLong,             //l
    JSEncodingTypeLongLong,         //q
    JSEncodingTypeUChar,            //C
    JSEncodingTypeUInt,             //I
    JSEncodingTypeUShort,           //S
    JSEncodingTypeULong,            //L
    JSEncodingTypeULongLong,        //Q
    JSEncodingTypeFloat,            //f
    JSEncodingTypeDouble,           //d
    JSEncodingTypeBool,             //B
    JSEncodingTypeVoid,             //v
    JSEncodingTypePointer,          //*
    JSEncodingTypeId,               //@
    JSEncodingTypeUnknown,          //?
    JSEncodingTypeBlock,            //@?
    JSEncodingTypeClass,            //#
    JSEncodingTypeSEl,              //:
    JSEncodingTypeCArray,           //[
    JSEncodingTypeUnion,            //(
    JSEncodingTypeStruct,           //{
    JSEncodingTypeIvar,             //^{objc_ivar=}
    JSEncodingTypeMethod,           //^{objc_method=}
};

而且,针对ivarmethod类型不做处理。只处理基本的类型,我们还要考虑到一点就是针对Foundation中定义的一些常见类型,如NSStringNSNumber等,没有在上面的类型中列出,所以,我们需要处理这些特定的类。

Foundation类

比如,从T@"NSString",C,N,V_name读出的就是NSString类。

而假如是Tq,N,V_age中,q就是指long long类型。

所以,分析出T之后的类型,下面就是处理基本数据类型:

JSEncodingType JSEncodingGetType(NSString *typeEncoding) {

    const char *type = [typeEncoding UTF8String];
    if (!type) return JSEncodingTypeUnknown;
    size_t len = strlen(type);
    if(len == 0) return JSEncodingTypeUnknown;

    switch (*type) {
        case 'c':
            return JSEncodingTypeChar;
            break;
        .......
        .......
        .......
        case '(':
            return JSEncodingTypeUnion;
            break;
        case '{':
            return JSEncodingTypeStruct;
            break;
        case '@':
            if (len == 2 && *(type+1) == '?') {
                return JSEncodingTypeBlock;
            }else{
                return JSEncodingTypeId;
            }
            break;
        case '^':
            //暂不处理
            break;
        default:
            break;
    }

    return JSEncodingTypeUnknown;
}

处理完基本数据类型之后,需要处理的是那些Foundation提供的类。

首先可以得知的是T@"..."这样的字符串代表的是Foundation类。

截取出T@"..."中的类型,并查询是否为Foundation中的类。

// 去掉@"和",截取中间的类型名称
typeStr = [typeStr substringWithRange:NSMakeRange(2, typeStr.length-3)];
_cls = NSClassFromString(typeStr);
_isNSClass = [self isClassFromFoundation:_cls];

isClassFromFoundation方法如下:

​ /*
是否是Foundation Class集合中的类
/
​ - (BOOL)isClassFromFoundation:(Class)c
​ {
​ if (c == [NSObject class] || c == [NSManagedObject class]) return YES;

​ __block BOOL result = NO;
​ [[self foundationClass] enumerateObjectsUsingBlock:^(Class foundationClass, BOOL
stop) {
​ if ([c isSubclassOfClass:foundationClass]) {
​ result = YES;
​ *stop = YES;
​ }
​ }];
​ return result;
​ }

而方法中的foundationClass是一个set集合,里面包含了Foundation中主要的类,如下:

- (NSSet*)foundationClass {

    return [NSSet setWithObjects:
            [NSURL class],
            [NSDate class],
            [NSValue class],
            [NSDecimalNumber class],
            [NSNumber class],
            [NSData class],
            [NSMutableData class],
            [NSError class],
            [NSArray class],
            [NSMutableArray class],
            [NSDictionary class],
            [NSMutableDictionary class],
            [NSString class],
            [NSMutableString class],
            [NSAttributedString class],
            [NSSet class],
            [NSMutableSet class],nil];
}

至此,我们已经做完了类型的解析。

回过头来,我们要继续将这些解析出来的类型,用来辅助模型中属性的赋值。首先,我们回顾上篇中赋值的方法:

- (instancetype)modelWithDic:(NSDictionary *)dic {

    NSArray *properties = [self.class properties];

    for (int i = 0; i < properties.count ; i++) {
        JSClassPropertyInfo *propertyInfo = properties[i];
        id value = [dic valueForKey:propertyInfo.name];
        if (!value) continue;

        if (propertyInfo.type == JSEncodingTypeInt){
            // 字符串->数字
            if ([value isKindOfClass:[NSString class]])
                value = [[[NSNumberFormatter alloc]init] numberFromString:value];
        }else if([value isKindOfClass:[NSString class]]){

        }
        [self setValue:value forKey:propertyInfo.name];
    }
    return self;
}

我们惊讶的发现,原来我们拿到的是否为Foundation类并没有什么用,因为对于我们赋值采用的是KVC,即Foundation的类,本来就支持KVC。

数字类型

同时,就算是对于基本类型中对于数字类型(包括整型、浮点型、布尔型等)的处理,也是支持KVC的。然而,根据上篇同样的理由,我们需要将基本数据类型转换为NSNumber类型。

下面就是判断属性是否为数字类型:

- (void)checkIsNumber
{
    if (_cls) {
        if ( [_cls isSubclassOfClass:[NSNumber class]] || [_cls isSubclassOfClass:[NSDecimalNumber class]] ) {
            _isNumber = YES;
        }else{
            _isNumber = NO;
        }
    }else{
        NSArray *numberTypes = [NSArray arrayWithObjects:
                                @(JSEncodingTypeChar),             //c
                                @(JSEncodingTypeInt),              //i
                                @(JSEncodingTypeShort),            //s
                                @(JSEncodingTypeLong),             //l
                                @(JSEncodingTypeLongLong),         //q
                                @(JSEncodingTypeUChar),            //C
                                @(JSEncodingTypeUInt),             //I
                                @(JSEncodingTypeUShort),           //S
                                @(JSEncodingTypeULong),            //L
                                @(JSEncodingTypeULongLong),        //Q
                                @(JSEncodingTypeFloat),            //f
                                @(JSEncodingTypeDouble),           //d
                                @(JSEncodingTypeBool),             //B
                                nil];
        if ([numberTypes containsObject:@(_type)]) {
            _isNumber = YES;
        }else{
            _isNumber = NO;
        }
    }
}

综合上面两步,我们为JSClassPropertyInfo类增加了两个属性:

@property (nonatomic, assign, readonly) BOOL isNSClass;
@property (nonatomic, assign, readonly) BOOL isNumber;

于是,我们可以得到当前属性是否Foundation的类,也是否是数字类型,改写赋值的方法如下:

- (instancetype)modelWithDic:(NSDictionary *)dic {

    NSArray *properties = [self.class properties];

    for (int i = 0; i < properties.count ; i++) {
        JSClassPropertyInfo *propertyInfo = properties[i];
        id value = [dic valueForKey:propertyInfo.name];
        if (!value) continue;

        if (propertyInfo.isNumber){
            // 字符串->数字
            if ([value isKindOfClass:[NSString class]])
                value = [[[NSNumberFormatter alloc]init] numberFromString:value];
        }else if([value isKindOfClass:[NSString class]]){

        }
        [self setValue:value forKey:propertyInfo.name];
    }
    return self;
}

代码

git