YYModel底层解析- Runtime

摘要:
一概述概括YYModel是一个轻量级的JSON模型转换库,它的思路非常清晰代码风格也很好,所以还是建议大家看一下底层实现的逻辑,也可以从源码加深对Runtime的理解。YYClassInfo功能主要是将Runtime层级中的一些结构体封装到NSObject中调用;NSObject+YYModel功能是提供调用的接口以及实现具体的模型转换逻辑。

这段时间一直在忙新的需求,没有时间来整理代码,发表自己技术博客,今天我们来看一下YYModel的底层解析以及如何使用,希望对大家有所帮助!

一 概述

概括

YYModel是一个轻量级的JSON模型转换库,它的思路非常清晰代码风格也很好,所以还是建议大家看一下底层实现的逻辑,也可以从源码加深对Runtime的理解。

简介

下面是YYModel第三方库的一些代码结构。

YYModel底层解析- Runtime第1张

YYModel的总共文件只有5个文件

YYModel底层解析- Runtime第2张

除掉YYModel.h之外,只剩下了YYClassInfo和NSObject+YYModel两个模块啦!

  • YYClassInfo功能主要是将Runtime层级中的一些结构体封装到NSObject中调用;

YYModel底层解析- Runtime第3张

  • NSObject+YYModel功能是提供调用的接口以及实现具体的模型转换逻辑。

前面已经讲到YYClassInfo主要功能是将Runtime层级的结构体封装到NSObject层级以便调用。下面是YYClassInfo与Runtime层级对比:

YYModel底层解析- Runtime第4张

二、详细

1.YYClassIvarInfo

YYClassIvarInfo && objc_ivar

下面是YYClassIvarInfo

/**
 Instance variable information.
 */
@interfaceYYClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar;              ///< ivar opaque struct
@property (nonatomic, strong, readonly) NSString *name;         ///< Ivar's name
@property (nonatomic, assign, readonly) ptrdiff_t offset;       ///< Ivar's offset
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding
@property (nonatomic, assign, readonly) YYEncodingType type;    ///< Ivar's type

/**
 Creates and returns an ivar info object.
 @param ivar ivar opaque struct
 @return A new object, or nil if an error occurs.
 */
-(instancetype)initWithIvar:(Ivar)ivar;
@end

紧接着我们看一下Runtime的objc_ivar表示变量的结构体

structobjc_ivar{
char*_Nullableivar_nameOBJC2_UNAVAILABLE;//变量名称
char*_Nullableivar_typeOBJC2_UNAVAILABLE;//变量类型
intivar_offsetOBJC2_UNAVAILABLE;//变量偏移量
#ifdef__LP64__//如果已定义__LP64__则表示正在构建64位目标
intspaceOBJC2_UNAVAILABLE;//变量空间
#endif
}

注:日常开发中,NSString类型的属性会用copy修饰,看上面YYClassIvarInfo中typeEncoding和name是用strong修饰。这是因为其内部先是通过Runtime方法拿到const char * 之后通过 stringWithUTF8String 方法之后转为 NSString 的。所以 NSString 这类属性在确定其不会在初始化之后出现被修改的情况下,使用 strong来修饰 做一次单纯的强引用在性能上是比 copy 要高的。

YYClassMethodInfo && objc_method

下面是YYClassMethodInfo

@interfaceYYClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; ///< 方法
@property (nonatomic, strong, readonly) NSString *name; ///< 方法名称
@property (nonatomic, assign, readonly) SEL sel; ///< 方法选择器
@property (nonatomic, assign, readonly) IMP imp; ///< 方法实现,指向实现方法函数的函数指针
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< 方法参数和返回类型编码
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< 返回值类型编码
@property (nullable, nonatomic, strong, readonly) NSArray<nsstring *>*argumentTypeEncodings; ///< 参数类型编码数组
 
-(instancetype)initWithMethod:(Method)method;
@end

YYClassMethodInfo则是对Rutime里面的objc_method的封装,紧接着我们看Runtime的objc_method结构体

structobjc_method {
    SEL _Nonnull method_name OBJC2_UNAVAILABLE; //方法名称
    char * _Nullable method_types OBJC2_UNAVAILABLE; //方法类型
    IMP _Nonnull method_imp OBJC2_UNAVAILABLE; //方法实现(函数指针)
}

YYClassPropertyInfo && property_t

YYClassPropertyInfo是对Runtime中property_t的封装

@interfaceYYClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property; ///< 属性
@property (nonatomic, strong, readonly) NSString *name; ///< 属性名称
@property (nonatomic, assign, readonly) YYEncodingType type; ///< 属性类型
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< 属性类型编码
@property (nonatomic, strong, readonly) NSString *ivarName; ///< 变量名称
@property (nullable, nonatomic, assign, readonly) Class cls; ///< 类型
@property (nullable, nonatomic, strong, readonly) NSArray<nsstring *>*protocols; ///< 属性相关协议
@property (nonatomic, assign, readonly) SEL getter; ///< getter 方法选择器
@property (nonatomic, assign, readonly) SEL setter; ///< setter 方法选择器
 
-(instancetype)initWithProperty:(objc_property_t)property;
@end</nsstring *>

然后来看一下Runtime的property_t结构体

structproperty_t {
    const char *name; //名称
    const char *attributes; //修饰
};

YYClassInfo && objc_class

YYClassInfo封装了Runtime的objc_class,下面看一下YYClassInfo

YYClassInfo

@interfaceYYClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< 类
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< 超类
@property (nullable, nonatomic, assign, readonly) Class metaCls;  ///< 元类
@property (nonatomic, readonly) BOOL isMeta; ///< 元类标识,自身是否为元类
@property (nonatomic, strong, readonly) NSString *name; ///< 类名称
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< 父类(超类)信息
@property (nullable, nonatomic, strong, readonly) NSDictionary<nsstring *, yyclassivarinfo *>*ivarInfos; ///< 变量信息
@property (nullable, nonatomic, strong, readonly) NSDictionary<nsstring *, yyclassmethodinfo *>*methodInfos; ///< 方法信息
@property (nullable, nonatomic, strong, readonly) NSDictionary<nsstring *, yyclasspropertyinfo *>*propertyInfos; ///< 属性信息
 
- (void)setNeedUpdate;
-(BOOL)needUpdate;
+(nullable instancetype)classInfoWithClass:(Class)cls;
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end

objc_class

//objc.h
typedef struct objc_class *Class;
//runtime.h
structobjc_class {
    Class _Nonnull isa OBJC_ISA_AVAILABILITY; //isa 指针
#if !__OBJC2__
    Class _Nullable super_class OBJC2_UNAVAILABLE; //父类(超类)指针
    const char * _Nonnull name OBJC2_UNAVAILABLE; //类名
    long version OBJC2_UNAVAILABLE; //版本
    long info OBJC2_UNAVAILABLE; //信息
    long instance_size OBJC2_UNAVAILABLE; //初始尺寸
    struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; //变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; //方法列表
    struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; //缓存
    struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; //协议列表
#endif
} OBJC2_UNAVAILABLE;

注解:下面是Runtime关于class的知识

YYModel底层解析- Runtime第5张

下面是对应的讲解。

YYModel底层解析- Runtime第6张

YYClassInfo 的初始化

+(instancetype)classInfoWithClass:(Class)cls {
    //判空入参
    if (!cls) returnnil;
    //单例缓存 classCache 与 metaCache,对应缓存类和元类
    staticCFMutableDictionaryRef classCache;
    staticCFMutableDictionaryRef metaCache;
    staticdispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        //这里把 dispatch_semaphore 当做锁来使用(当信号量只有 1 时)
        lock = dispatch_semaphore_create(1);
    });
    //初始化之前,首先会根据当前 YYClassInfo 是否为元类去对应的单例缓存中查找
    //这里使用了上面的 dispatch_semaphore 加锁,保证单例缓存的线程安全 
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
    //如果找到了,且找到的信息需要更新的话则执行更新操作
    if (info && info->_needUpdate) {
        [info _update];
    }
    dispatch_semaphore_signal(lock);
    //如果没找到,才会去老实初始化
    if (!info) {
        info =[[YYClassInfo alloc] initWithClass:cls];
        if (info) { //初始化成功
            //线程安全
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            //根据初始化信息选择向对应的类/元类缓存注入信息,key = cls,value = info
            CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
            dispatch_semaphore_signal(lock);
        }
    }
    returninfo;
}

下面总结一下初始化主要步骤:

  1. 首先创建单例缓存,类缓存和元类缓存;
  2. 使用dispatch_semaphore 保证缓存线程安全;
  3. 初始化操作之前首先缓存中查找是否已经向缓存中注册过的当前要初始化的YYClassInfo;
  4. 如果查找缓存对象,需要判断对象是否需要更新以及其他相关操作;
  5. 如果没有找到缓存对象,就开始初始化;
  6. 初始化成功之后,向缓存中注册YYClassInfo实例。

2.NSObject+YYModel

YYModel底层解析- Runtime第7张

NSObject+YYModel在YYModel主要任务是利用YYClassInfo层级封装的类来执行JSON模型之间的转换逻辑。下面是NSObject+YYModel讲述的主要内容:

  • 类型编码的解析
  • 数据结构的定义
  • 递归模型的转换
  • 接口相关的代码

下面将部分讲解

数据结构的定义

NSObject+YYModel重新定义了两个类,来使用 YYClassInfo 中的封装。

YYModel底层解析- Runtime第8张

_YYModelPropertyMeta

@interface_YYModelPropertyMeta : NSObject {
    @package
    NSString *_name;             ///< 属性名称
YYEncodingType _type;        ///< 属性类型
YYEncodingNSType _nsType;    ///< 属性在 Foundation 框架中的类型
BOOL _isCNumber;             ///< 是否为 CNumber
Class _cls;                  ///< 属性类
Class _genericCls;           ///< 属性包含的泛型类型,没有则为 nil
SEL _getter;                 ///< getter
SEL _setter;                 ///< setter
BOOL _isKVCCompatible;       ///< 如果可以使用 KVC 则返回 YES
BOOL _isStructAvailableForKeyedArchiver; ///< 如果可以使用 archiver/unarchiver 归/解档则返回 YES
BOOL _hasCustomClassFromDictionary; ///< 类/泛型自定义类型,例如需要在数组中实现不同类型的转换需要用到
     
    /*
     property->key:       _mappedToKey:key     _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:   _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:      _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;      ///< 映射 key
NSArray *_mappedToKeyPath;   ///< 映射 keyPath,如果没有映射到 keyPath 则返回 nil
NSArray *_mappedToKeyArray;  ///< key 或者 keyPath 的数组,如果没有映射多个键的话则返回 nil
YYClassPropertyInfo *_info;  ///< 属性信息,详见上文 YYClassPropertyInfo && property_t 章节
_YYModelPropertyMeta *_next; ///< 如果有多个属性映射到同一个 key 则指向下一个模型属性元
}
@end

_YYModelMeta

@interface_YYModelMeta : NSObject {
    @package
    YYClassInfo *_classInfo;
    ///Key:被映射的 key 与 keyPath, Value:_YYModelPropertyMeta.
    NSDictionary *_mapper;
    ///Array<_YYModelPropertyMeta>, 当前模型的所有 _YYModelPropertyMeta 数组
    NSArray *_allPropertyMetas;
    ///Array<_YYModelPropertyMeta>, 被映射到 keyPath 的 _YYModelPropertyMeta 数组
    NSArray *_keyPathPropertyMetas;
    ///Array<_YYModelPropertyMeta>, 被映射到多个 key 的 _YYModelPropertyMeta 数组
    NSArray *_multiKeysPropertyMetas;
    ///映射 key 与 keyPath 的数量,等同于 _mapper.count
NSUInteger _keyMappedCount;
    ///模型 class 类型
YYEncodingNSType _nsType;
    //忽略
...
}
@end
三、使用

1.简单Model与JSON相互转换

#import <Foundation/Foundation.h>
//"time":"2018-07-04 12:13:52",
//"ftime":"2018-07-04 12:13:52",
//"context":"快件已签收 签收人: 他人代收 感谢使用圆通速递,期待再次为您服
@interfaceIOALogisticsDetailModel : NSObject
@property(nonatomic,copy)NSString   *time;
@property(nonatomic,copy)NSString   *ftime;
@property(nonatomic,copy)NSString   *context;
@end
#import "IOALogisticsDetailModel.h"
@implementationIOALogisticsDetailModel
@end

下面是运用

- (NSArray <IOALogisticsDetailModel *>*)setupOrderWithArray:(NSArray <NSDictionary *>*)array{
    NSMutableArray <IOALogisticsDetailModel *>*modelArray =[NSMutableArray arrayWithCapacity:array.count];
    for(NSDictionary *dic inarray){
IOALogisticsDetailModel *model = [IOALogisticsDetailModel yy_modelWithDictionary:dic];
        if (!model) continue;
        [modelArray addObject:model];
    }
    returnmodelArray;
}

红色部分就是应用。

如果需要Model转为JSON如下

#import <Foundation/Foundation.h>
@interfaceIOAOrderAftersaleRequestModel : NSObject
@property(nonatomic,copy)NSString *order_sn;
@property(nonatomic,copy)NSString *rec_id;
@property(nonatomic,copy)NSString  *is_type;
@property(nonatomic,assign)NSInteger refund_count;
@property(nonatomic,copy)NSString *content;
@property(nonatomic,copy)NSString *return_attachs;
@property(nonatomic,copy)NSString *shop_id;
@property(nonatomic,copy)NSString *reason;
@property(nonatomic,assign)floattotal;
@end
#import "IOAOrderAftersaleRequestModel.h"
@implementationIOAOrderAftersaleRequestModel
@end

下面是运用

//提交退货商品
@interfaceIOAOrderAftersaleRequest:IOARequest
@property (nonatomic,strong)IOAOrderAftersaleRequestModel *requestModel;
@end
//提交退货商品
@implementationIOAOrderAftersaleRequest
- (id)requestArgument{
    NSMutableDictionary *dic = [IOAApiManager getParametersWithService:@"App.Order.SetOrderAftersaleGoodsrefundsList"];
NSDictionary *temDic =[self.requestModel yy_modelToJSONObject];
    [dic addEntriesFromDictionary:temDic];
    returndic;
}

2.Model属性名与JSON中key不同

//JSON:
{
    "n":"Harry Pottery",
    "p": 256,
    "ext": {
        "desc" : "A book written by J.K.Rowing."
    },
    "ID" : 100010
}
//Model:
@interfaceBook : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementationBook
//返回一个 Dict,将 Model 属性名对映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
    return @{@"name" : @"n",
             @"page" : @"p",
             @"desc" : @"ext.desc",
             @"bookID" : @[@"id",@"ID",@"book_id"]};
}

3.Model包含Model

//JSON
{
    "author":{
        "name":"J.K.Rowling",
        "birthday":"1965-07-31T00:00:00+0000"
    },
    "name":"Harry Potter",
    "pages":256
}
//Model: 什么都不用做,转换会自动完成
@interfaceAuthor : NSObject
@property NSString *name;
@property NSDate *birthday;
@end
@implementationAuthor
@end
@interfaceBook : NSObject
@property NSString *name;
@property NSUInteger pages;
@property Author *author; //Book 包含 Author 属性
@end
@implementationBook
@end

下面在我们项目中的使用

YYModel底层解析- Runtime第9张YYModel底层解析- Runtime第10张
#import <Foundation/Foundation.h>
#import "IOAOrder.h"
@interfaceIOAOrderGroup : NSObject
@property(nonatomic,copy)NSString *order_id;
@property(nonatomic,copy)NSString *parent_sn;
@property(nonatomic,copy)NSString *order_sn;
@property(nonatomic,copy)NSString *order_status;
@property(nonatomic,copy)NSString *refund_status;
@property(nonatomic,copy)NSString *return_status;
@property(nonatomic,copy)NSString *pay_status;
@property(nonatomic,copy)NSString *total_amount;
@property(nonatomic,copy)NSString *company_name;
@property(nonatomic,copy)NSString *company_logo;
@property(nonatomic,copy)NSString *shop_id;
@property(nonatomic,copy)NSString *stroe_id;
@property(nonatomic,copy)NSString *order_amount;
@property(nonatomic,assign)intstore_id;
@property (nonatomic,strong)NSArray<IOAOrder *> *goods_list;
@end
#import "IOAOrderGroup.h"
#import <YYModel/YYModel.h>
@implementationIOAOrderGroup
+ (NSDictionary *)modelContainerPropertyGenericClass{
    return @{@"goods_list":[IOAOrder class]};
}
@end
#import <Foundation/Foundation.h>
@interfaceIOAOrder : NSObject
@property(nonatomic,copy)NSString *rec_id;
@property(nonatomic,copy)NSString *order_id;
@property(nonatomic,copy)NSString *brand_name;
@property(nonatomic,copy)NSString *goods_id;
@property(nonatomic,copy)NSString *goods_name;
@property(nonatomic,copy)NSString *goods_sn;
@property(nonatomic,copy)NSString *goods_num;
@property(nonatomic,copy)NSString *market_price;
@property(nonatomic,copy)NSString *goods_price;
@property(nonatomic,copy)NSString *cost_price;
@property(nonatomic,copy)NSString *member_goods_price;
@property(nonatomic,copy)NSString *total_price;
@property(nonatomic,copy)NSString *give_integral;
@property(nonatomic,copy)NSString *spec_key;
@property(nonatomic,copy)NSString *unit;
@property(nonatomic,copy)NSString *spec_key_name;
@property(nonatomic,copy)NSString *bar_code;
@property(nonatomic,copy)NSString *is_comment;
@property(nonatomic,copy)NSString *prom_type;
@property(nonatomic,copy)NSString *prom_id;
@property(nonatomic,copy)NSString *is_send;
@property(nonatomic,copy)NSString *delivery_id;
@property(nonatomic,copy)NSString *add_time;
@property(nonatomic,copy)NSString *update_time;
@property(nonatomic,copy)NSString *image_url;
@property(nonatomic,assign)BOOL selected;
@end
#import "IOAOrder.h"
@implementationIOAOrder
@end
View Code

4.白名单黑名单

@interfaceUser
@property NSString *name;
@property NSUInteger age;
@end
@implementationAttributes
//如果实现了该方法,则处理过程中会忽略该列表内的所有属性
+ (NSArray *)modelPropertyBlacklist {
    return @[@"test1", @"test2"];
}
//如果实现了该方法,则处理过程中不会处理该列表外的属性。
+ (NSArray *)modelPropertyWhitelist {
    return @[@"name"];
}
@end

YYModel的核心是通过runtime获取结构体中得Ivars的值,将此值定义为key,然后给key赋value值,所以我们需要自己遍历容器(NSArray,NSSet,NSDictionary),获取每一个值,然后KVC。

免责声明:文章转载自《YYModel底层解析- Runtime》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇iOS 推送问题全解答《十万个为啥吖?》Android studio插件安装下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

第四季-专题10-字符设备驱动模型

专题10-字符设备驱动模型 第1课-使用字符驱动程序 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。 例子:字符设备驱动程序 编写makefile文件:        obj-m := memdev.o KDIR := /home/S5-driver/les...

iOS获取所有机型

1.手机系统版本:10.3 NSString* phoneVersion = [[UIDevice currentDevice] systemVersion]; 2.手机类型:iPhone 6 NSString* phoneModel = [self iphoneType];//方法在下面 3.手机系统:iPhone OS NSString * ipone...

jenkins初始化和安装插件

1.find / -name 'default.json' 2. sed -i 's/http://updates.jenkins-ci.org/download/https://mirrors.tuna.tsinghua.edu.cn/jenkins/g' /var/lib/jenkins/updates/default.json &&...

linux下/etc/rc.d目录的介绍及redhat启动顺序

init inittab rc0 rc1 rc2 rc3 rc5 rc6 rcS init.dinit 系统启动超级进程inittab 进程启动配置文件rc0 - rc6 各启动级别的启动脚本rcS 单用户模式启动脚本init.d 启动脚本存放目录 init目录有时候是直接在/etc目录下的,/etc目录下有时候也有inittab 、rc0~6等目录,不过...

Android学习——移植tr069程序到Android平台

原创作品,转载请注明出处,严禁非法转载。如有错误,请留言! email:40879506@qq.com 声明:本系列涉及的开源程序代码学习和研究,严禁用于商业目的。 如有任何问题,欢迎和我交流。(企鹅号:408797506)  淘宝店:https://shop484606081.taobao.com 本篇用到的代码下载路径:http://download....

vim命令以及gcc编译器的常用cmd

Gcc常用命令:         -c    仅对源文件进行编译,不链接生成可执行文件。常用于查错和只生成目标文件。     -o    经过gcc处理过后的结果保存在-o后面的文件中,可以是多种文件。如无参数,默认名称不同     -v    查看版本信息     -g    在可执行文件中加入调试信息,相当于Windows下的Debug版本。方便使用g...