iOS开发iOS学习iOS Developer

30分钟撸出一个线程安全的YYModel

2017-07-13  本文已影响1058人  一缕清风扬万里
镇贴图

前言

做iOS开发以来,从最开始没有数据模型,所有数据都靠NSStringNSDictionaryNSArrray等系统基础的对象存储,到后来自己开始手动撸数据模型,再然后就开始接触JSONModel,彻底脱离了枯燥的重复的动作,后来一些国产的一些优秀的数据模型库也开始崭露头角,如MJExtension,如YYModel等。但别人的轮子始终是别人的,要是中途爆了胎还得去人家的店里(Github)提出问题,等待修复,可是现实中大多数的时候时间都不允许我们这样慢慢的等待,所以就有了这篇文章。

在这篇文章中,你可以了解到一些实用的Runtime技巧,一些面向对象的思想,最重要的是可以自己做出一个可以供自己扩展的数据模型轮子。轮子虽小但优点在于方便理解,扩展性强。

废话不多说,直接进入正题。

一张图说目标功能

功能图解

想一想别人的轮子

要将数据模型的实现原理,先回想一下我们平时是怎么用别人的数据模型的。

所以就有了我们的设计思路

得出设计思路

![](https://github.com/dengbin9009/MyFiles/blob/master/DBModel思维导图.png?raw=true =100x200)

下面我们就来实现具体的步骤

Step

   @property (nonatomic, strong ,setter=setGroup: ,getter=group) NSArray<Student> * group;

可以看出这个地方对我们有用的有settergetterNSArrayStudentgroup,当然其中的nonatomicstrong也是一些有用的信息,但我们目前姑且不谈。

关于property苹果在<objc/runtime.h>中给了我们这些Api,如图

Runtime_PropertyRuntime_Property

其中name就可以通过下面这个Api得到是group

/** 
* Returns the name of a property.
* 
* @param property The property you want to inquire about.
* 
* @return A C string containing the property's name.
*/
OBJC_EXPORT const char *property_getName(objc_property_t property) 
   OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

其它的都可以在苹果给我们的另外一个Api中全部获取到

```objectivec
/** 
 * Returns an array of property attributes for a property. 
 * 
 * @param property The property whose attributes you want copied.
 * @param outCount The number of attributes returned in the array.
 * 
 * @return An array of property attributes; must be free'd() by the caller. 
 */
OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
```

而这个函数取出来的是一个关于objc_property_attribute_t的数组,而objc_property_attribute_t是一个这样的结构题:

```objectivec
/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;    
```

这里的这里namevalue的定义可以参考:

name包括N&WRGSVT

这里面的GS正好对应gettersetter,这两个比较好理解,都是对应SEL的name,不过这个这个时候通过value取出来的是一个char型字符串,这个要注意一下。比如getter就是"group"setter就是"setGroup:"

T就稍稍复杂一点一些,这里的T就是@\"NSArray<Student>"\ (如果有两个protocol则是@\"NSArray<Student><Student2>),我们可以将它分为三部分@NSArrayStudent。其中NSArray是这个属性的ClassStudent是对应的protocols,因为protocols可能有多个,所以它是个数组。同样的它们也都是char型字符串。

最关键的是前面的@它代表这个property是个对象,具体这个char所对应的含义可以参考:#####
* *[iOS方法返回值和参数对应的Type Encodings](http://blog.csdn.net/dengbin9009/article/details/72922244)*
其实在objc/runtime.h第1560行至1589行中也有对应的描述。我们将@这样的字符串单独存入一个新定义的属性type中#####

这里有个Tip可以有效的将@\"NSArray<Student><Student2>分成NSArrayStudentStudent2这样的数组。

NSString *type = @"@\"NSArray<Student><Student2>";
NSMutableArray *values = [type componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"@\"<>,"]].mutableCopy;
[values removeObject:@""];
// 最终values = @[@"NSArray",@"Student",@"Student2"];

到这里关于一条Property最重要的一些信息我们都得到了:

然后在补上一些能够让我们更方便使用的属性,比如:

* ```property```:通过*runtime*取出的的*property*本身
* ```isCustomPropetry```:是否是系统类
* ```isMutable```:是否是系统类里面的可变类型
* ```superClsInfo```:父类*ClassInfo*,如果父类为*nil*,则它也是*nil*

ClassInfo中对于本文的有用信息不多,目前我们只取:

* ```name ```:类名,如这里的```person```;
* ```cls ```:类本身,如这里的```PersonDataModel```;
* ```propetryInfos ```:参考第一步    
      >  *获取关键的```ClassPropertyInfo```信息*
* ```superClsInfo```:父类的```ClassInfo```,可用一个递归方法实现。
         
        + (instancetype)classInfoWithClass:(Class)cls{
            if ( !cls ) return nil;
            ...
            if ( !classInfo ) {
                classInfo = [[DBClassInfo alloc] initWithClass:cls];
            }
            return classInfo;
        }
            
        - (instancetype)initWithClass:(Class)cls{
            if ( !cls ) return nil;
            self = [super init];
            if ( self ) {
                ...
                _superCls = class_getSuperclass(cls);
                _superClsInfo = [DBClassInfo classInfoWithClass:_superCls];
                ...
            }
            return self;
        }
        
  由于classInfoWithClass是个类方法,所以这一步一定要确保线程安全,具体方式可以见 *[Demo](https://github.com/dengbin9009/DBModel.git)*
 { "name": "小明","age": 18,"sex": "男"}

那么这个时候我们要找到的就是PersonDataModelnamesexClassPropertyInfo和它对应的Value

而在这个地方我们就可以做一些比较有意思的事情了,比如白名单黑名单过滤,比如属性名称的映射,而这些有意思的方法可以将它都归为一个Option的协议,并将所有协议单独归类出一个文件DBModelProtocol,这样方便阅读,也方便维护。
> 白名单黑名单比较好理解,就是在对应的Model里面接受对应的名单实现是否对这个属性进行赋值或者不赋值。具体使用类似实现以下两个协议即可

```
+ (NSArray *)modelPropertyBlackList{
    return @[@"teacher",@"groupCount",@"groupArray"];
}

+ (NSArray *)modelPropertyWhiteList{
    return @[@"teacher",@"groupCount",@"groupArray"];
}
```

> 属性名称的映射其实就我常用的重命名,比如服务器返回了我们一个```key```为```id```,但```id```是一个隐藏的系统关键字,我们一个会将它重命名为```personId```或者```teacherId```等更容易理解的属性名称

我们重新在PersonDataModel的基础上定义一个TeacherDataModel的数据模型

@interface TeacherDataModel : PersonDataModel
@property (nonatomic, assign) NSUInteger teacherId;
@end

而服务端返回给我们数据模型却是

 { "id": "110", "name": "黄卫民", "age": 38, "sex": "男"}

这个时候我们就可以在这一步进行一些差异化的对比了:

首先我们先实现协议:

+ (NSDictionary *)customKeyMapper{
   return @{@"id":@"teacherId"};
}

当我们轮询到TeacherDataModelnameteacherIdClassPropertyInfo时取出的NSDictionary中对应keyidobject

这一步是逻辑最简单,但也是实现起来最繁琐的一步。

进行到这已经完成对一个NSDictionary->DataModel的全过程。

小结

虽然本文只是讲述了NSDictionary->DataModel的过程,没有其他的Model功能那么完善,如:

但我相信如果能看到这里的同学对其他功能应该是已经可以手到擒来了。

做事之前先理清楚思路,功能点全部归好类才能更好帮助我们完成它!

本文所有代码可以在这里找到:Demo

参考

喜欢的朋友可以点个下面的喜欢,这是最作者最大的支持,谢谢!

上一篇 下一篇

猜你喜欢

热点阅读