YY怎么我的世界快速升级级?我刚弄一个YY,准备...

yy陪玩app下载|yy陪我版最新版 v7.0.4_5577我机网
您的位置: →
→ yy陪我版最新版 v7.0.4
yy陪我版最新版提供给大家,yy陪我版app是一款yy语音最新推出的版本,yy语音7.0版本已经全线升级成为yy陪我版,快乐大本营欢乐吐篮球游戏就是这款软件的游戏,一款非常好玩的游戏陪玩软件,希望大家喜欢。【软件内容】快乐大本营推荐游戏“隔空抓娃娃”、“欢乐吐篮球”、“陪我券”等全球最新互动直播游戏玩法全面上线!上YY, 无聊全走开,陪你一起玩到嗨!超过10亿用户的忠实选择,7×24陪你乐翻天!【达人主播陪你玩】唱歌、跳舞、脱口秀、户外猎奇、游戏直播、交友互动、嘻哈、喊麦达人等应用尽有。还有天佑、阿哲、文er、球球等当红主播齐聚YY。不管何时何地,开心或忧伤,上YY,给你最温暖的陪伴!【花式玩法陪你玩】欢乐吐篮球、隔空夹娃娃、陪我、AR直播寻宝箱、欢乐斗等直播/小视频互动游戏给你最酷最IN玩法新体验,还有何炅老师陪你一起同框PK玩吐篮球!【主要功能】1.【隔空抓娃娃】让你躺在家就能抓娃娃,抓到的娃娃还能包邮送到家!新一代的撩妹黑科技!现在下载YY APP,玩隔空抓娃娃,还有快本明星限量版签名玩偶等你来抢!2.【欢乐吐篮球】用嘴吐篮球,和朋友同框竞技PK口技,老铁闺蜜聚会必备杀时间、找乐子神器!现在下载YY APP,还能和何炅老师同框PK口技哦!3.【陪我券】通过陪我功能选择主播服务类型下单,选择心仪的抢单主播专属为你唱、跳,陪你一起玩!用陪我券,花小钱,当直播间真土豪!现在下载YY APP,更有亿元陪我券等你抢,万千达人主播等你翻牌,陪你一起玩!4.【找到同城玩家】覆盖百城万人,随时找到你身边城会玩的玩家,和万千美女鲜肉干掉无聊,一起玩!5.【近百项功能优化】V7.0“陪我版”升级优化82个功能点,只为让你在YY随时随地一起玩到嗨!更多惊喜玩法尽在YY APP V7.0“陪我版”,快来和我们一起玩吧!
共有:40款
随着电子竞技的火热,衍生出了许多五花八门的行业,如游戏陪玩就是其中一个,网络上也涌现了许多游戏陪玩app,在这些网络游戏陪玩平台中,你如果是游戏大神可以赚佣金,你如果是游戏小白可以找大神带,又或者你想找美女玩家也可以选择这些网络游戏陪玩平台!
竞哥哥手机版作为国内兴起的诸多交流交换app,本着与时俱进的心态加入了当下年轻人最流行的电竞元素。大量的女性用户让竞哥哥app获得了巨大的活跃度,其独具特色的游戏分享和女神陪玩的功能更是让许多年轻人感受到了
一个人不管做什么都是无聊的,又找不到一同的好友?没事,今天为你分享雇佣兵app,用户可以在无聊的时候可以通过雇佣兵app找人陪玩游戏、陪运动或者陪聊天。当然了,这是有偿雇佣。还等什么,快来雇佣属于你的雇佣兵
你还在一个人孤单寂寞打游戏吗?快来猫耳陪玩app上找陪玩吧!在这里你可以找到大神玩家带你躺着上分,还能找到妹子玩家一起征战游戏世界,边玩边撩,乐趣无穷!
女神陪你LOL是一款专为电竞爱好者打造的电竞陪玩软件,用户可以在这里约上女神一起组队游戏,还可以出售自己的时间和技能赚钱~
猫咪陪玩约玩平台是专门为寂寞单身男女打造的最新在线交友平台,你可以在平台发布自己的交友信息,一键搜索心仪的猫咪,即可展开真实的陪玩交友啦,想体验朋友赶紧下载试试!
BiBi女神是一款基于地理位置的游戏陪玩社交软件,用户可以在线寻找女神一起玩游戏,还能和附近的人搭讪交友,既能找到志趣相投的小伙伴,还能和女神轻松玩游戏。
共有:15034款
手机破解软件是搞机达人程序猿们比较爱干的一件事情,通过破解手段,可以将一些付费、带广告、会员权限等功能的软件权限去除掉,然后大家就可以免费的来使用这些软件了。安卓破解软件使用起来非常方便,不过需要将原版本的软件卸载才能使用哦,一款软件在手机中是不能共存的哦。
如何改掉手机说说的小尾巴?怎样更改手机QQ发表信息的来源后缀?我机小编特意给大家带来一款QQ说说神器供大家使用,轻松帮助大家解决这个问题,希望大家喜欢!该版本为破解版,破解更改积分,想怎么发表说说就怎么发
钱鹿锁屏是一款让你快速赚钱的软件,该版本为去除任务限制破解版,再也不用担心权限不够而无法挣钱,你还等什么?赶快利用闲余时间就能赚点小小零花钱哦!
专为高品质生活人群带来的好用音乐播放器软件,该版本为内购破解版,具有去广告、去升级、去推送等一系列烦人推广,而且免费享受无暇音乐带给你的听觉盛宴,你值得拥有!
今天要给大家介绍的是波波播放器,而且还是破解版,这个软件能够让你轻松的完成在床上看片的梦想!准备好了吗?要起飞了哦!
这是一款传说中用户体验最好的音乐播放器,这款软件极小的占用也能让你使用的非常得心应手!赶紧来试试看吧!希望大家能够喜欢哦!
今天要给大家介绍的是一个伪装偷拍工具,能够伪装成为浏览器进行偷拍,想想也是醉了!有点想法的魂淡们快来试试看吧!
共有:238款
随着手机平台各种聊天工具功能的完善,不少的聊天软件已经支持语言聊天功能,通过发送语音来对话,体验更亲密和详细的聊天内容!这里整合好用的语音聊天软件,如:QQ、微信等,那么你也来试试吧!
打游戏的时候最注重的是什么?心情!没错,在游戏的时候一边和自己的朋友聊天,一遍快乐的进行游戏总是能让人格外欢畅哦!
《微信语音输入插件》是一款为微信开发的语音插件,应用于安卓手机平台,让安卓微信用户使用更加的方便和快捷,通过语音进行各种软件操作!【软件特色】1、为免费的软件工具类;2、聊天利器,每分钟输入100字;
《讯飞语音+》是一款为微信用户精心开发的语音软件。这款软件能够帮助你体验最新的语音交互功能,帮助微信用户更加方便快速的输入。是一款非常不错的微信助手!
玩客游戏语音app软件是一款专门为爱玩游戏的人士开发,这款软件中有较多的萌妹子,无聊或是心情不愉快的生活,都可以来语音频道邀请妹子们聊天,一边游戏一边语音,玩客游戏语音app软件让你玩游戏更加有趣。
多玩我的世界盒子联机版等你来下载试玩,是你游戏体验过程中的必备神器,对这款软件有需求的小伙伴千万不要错过咯,联机最新版为你送上,你值得拥有!
来电app是一款腾讯旗下的社交app应用,不仅可以作为网络***,拨打手机中的***号码,还可以将QQ联系人导入,和QQ好友进行语音视频群聊。是一款非常不错的社交聊天应用。
相关合集推荐
本类下载排行
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友
聊天.社区.交友yy爵位怎么升级
13-08-27 &匿名提问手把手带你撸一个 YYModel 的精简版 - 简书
手把手带你撸一个 YYModel 的精简版
读完这篇文章你可以自己写一个 YYModel 这样的神器,这篇文章类似一个源码解析,但不同的是,它不光光是解析,更是实战,因为我觉得学习一个东西必须要自己写一遍才算是真的学了一遍,否则即便是读完了源码印象还是不会太深刻,so,开始吧。
注:为了简单起见,我的例子只是实现了一个精简的版本,YYModel 有很多功能,我这里就实现了一个核心的功能,JSON -& Model。
注:文章的最后有完整的代码
从JSON映射到Model的原理
想一下平时我们是怎么使用类似这样子的库的,当我们有一个JSON的时候,我们把所有JSON的字段(比如name、page)全部写成对应的类中的属性。然后库会自动把你JSON对应字段的值赋值到这些对应的属性里去。属性我们用 @property 来定义,就意味着编译器会帮你生成对应的get``set方法,我们使用的 . 其实也是在调用get``set方法来实现赋值的。在 Objective-C 中有一个著名的函数 objc_msgSend(...) 我们所有的类似 [obj method] 的方法调用(发送消息)都会被转换成 objc_msgSend(...) 的方式来调用。(具体这个函数怎么用后面再说)
所以对于一个库来说,要调用你这个 Model 的 set 方法,用 objc_msgSend(...) 会容易的多,所以JSON映射到Model的原理其实就是调用这个函数而已。
所以整个流程就是,你给我一个 Model 类,我会用 runtime 提供的各种函数来拿到你所有的属性和对应的get``set,判断完相应的类型以后,调用objc_msgSend(...)。说起来真的非常简单,做起来就有点麻烦...
前期的准备工作
为了后面的方便,我们需要把一些关键的东西封装起来,我们需要单独封装 ivar property method,也就是实例变量、属性、方法,但事实上我们的这个精简版的YYModel并不需要 method ivar 的封装,为了保证完整性,我还是打算写下来。
先来封装 ivar,看一下头文件 CPClassIvarInfo.h(YYModel只有4个文件,两个 .h 两个 .m 我为了让代码看起来更清楚,所以我自己在重写 YYModel 的时候把所有可以拆出来的类都分别拆成了一对.h .m)并把前缀改成了 CP 意思是 copy。
#import &Foundation/Foundation.h&
#import &objc/runtime.h&
#import "CPCommon.h"
@interface CPClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) I
@property (nonatomic, strong, readonly) NSString *
@property (nonatomic, strong, readonly) NSString *typeE
@property (nonatomic, assign, readonly) CPEncodingT
- (instancetype)initWithIvar:(Ivar)
Ivar 代表一个实例变量,你所有和实例变量有关的操作都必须要把 Ivar 传进去,等一下就能看到。
name 是这个实例变量的变量名
typeEncoding 是对类型的编码,具体可以看 对于不同的类型就会有对应的编码,比如 int 就会变编码成 i,可以用 @encode(int)这样的操作来看一个类型的编码。
type 是一个自定义的枚举,它描述了 YYMode 规定的类型。
一个强大的枚举
然后重新再创建一个文件(CPCommon),作为一个公共的文件 CPEncodingType 这个枚举就写在这里。
我们要创建的这个枚举需要一口气表示三种不同的类型,一种用于普通的类型上(int double object),一种用来表示关键词(const),一种表示 Property 的属性(Nonatomic weak retain)。
我们可以用位运算符来搞定这三种类型,用8位的枚举值来表示第一种,16位的表示第二种,24位的表示第三种,然后为了区别这三种类型都属于多少位的,我们可以分别搞三个 mask ,做一个该类型和某一个 mask 的与(&)的操作就可以知道这个类型是具体是哪一个类型了,例子在后面。
这个枚举我们可以这样定义:
typedef NS_OPTIONS(NSUInteger, CPEncodingType) {
CPEncodingTypeMask
= 0xFF, //8 bit
CPEncodingTypeUnknown
CPEncodingTypeVoid
CPEncodingTypeBool
CPEncodingTypeInt8
CPEncodingTypeUInt8
CPEncodingTypeInt16
CPEncodingTypeUInt16
CPEncodingTypeInt32
CPEncodingTypeUInt32
CPEncodingTypeInt64
CPEncodingTypeUInt64
CPEncodingTypeFloat
CPEncodingTypeDouble
CPEncodingTypeLongDouble = 13,
CPEncodingTypeObject
CPEncodingTypeClass
CPEncodingTypeSEL
CPEncodingTypeBlock
CPEncodingTypePointer
CPEncodingTypeStruct
CPEncodingTypeUnion
CPEncodingTypeCString
CPEncodingTypeCArray
CPEncodingTypeQualifierMask
CPEncodingTypeQualifierConst
CPEncodingTypeQualifierIn
CPEncodingTypeQualifierInout
= 1 && 10,
CPEncodingTypeQualifierOut
= 1 && 11,
CPEncodingTypeQualifierBycopy = 1 && 12,
CPEncodingTypeQualifierByref
= 1 && 13,
CPEncodingTypeQualifierOneway = 1 && 14,
CPEncodingTypePropertyMask
= 0xFF0000, // 24 bit
CPEncodingTypePropertyReadonly
= 1 && 16,
CPEncodingTypePropertyCopy
= 1 && 17,
CPEncodingTypePropertyRetain
= 1 && 18,
CPEncodingTypePropertyNonatomic
= 1 && 19,
CPEncodingTypePropertyWeak
= 1 && 20,
CPEncodingTypePropertyCustomGetter = 1 && 21,
CPEncodingTypePropertyCustomSetter = 1 && 22,
CPEncodingTypePropertyDynamic
= 1 && 23,
比如有一个类型是这样的
CPEncodingType type = CPEncodingTypeD
假设我们并不知道它是 CPEncodingTypeDouble 类型,那我们要怎么样才能知道它是什么类型呢?只要这样:
NSLog(@"%lu",type & CPEncodingTypeMask);
在枚举的定义中
CPEncodingTypeDouble
假设这个枚举值有很多种混在一起
CPEncodingType type = CPEncodingTypeDouble | CPEncodingTypePropertyR
NSLog(@"%lu",type & CPEncodingTypePropertyMask); //输出 &&18的十进制表示)
NSLog(@"%lu",type & CPEncodingTypeMask); //输出 12
可能有人知道这种神奇的用法,但在我读YYModel之前我没用过这种方法(技术比较菜)。
然后还有一个函数,这个函数可以把类型编码(Type Encoding)转换成刚才的枚举值,很简单却很长的一个函数:
CPEncodingType CPEncodingGetType(const char *typeEncoding) {
char *type = (char *)typeE
if (!type) return CPEncodingTypeU
size_t len = strlen(type);
if (len == 0) return CPEncodingTypeU
CPEncodingType qualifier = 0;
bool prefix =
while (prefix) {
switch (*type) {
case 'r': {
qualifier |= CPEncodingTypeQualifierC
case 'n': {
qualifier |= CPEncodingTypeQualifierIn;
case 'N': {
qualifier |= CPEncodingTypeQualifierI
case 'o': {
qualifier |= CPEncodingTypeQualifierO
case 'O': {
qualifier |= CPEncodingTypeQualifierB
case 'R': {
qualifier |= CPEncodingTypeQualifierB
case 'V': {
qualifier |= CPEncodingTypeQualifierO
default: { prefix = }
len = strlen(type);
if (len == 0) return CPEncodingTypeUnknown |
switch (*type) {
case 'v': return CPEncodingTypeVoid |
case 'B': return CPEncodingTypeBool |
case 'c': return CPEncodingTypeInt8 |
case 'C': return CPEncodingTypeUInt8 |
case 's': return CPEncodingTypeInt16 |
case 'S': return CPEncodingTypeUInt16 |
case 'i': return CPEncodingTypeInt32 |
case 'I': return CPEncodingTypeUInt32 |
case 'l': return CPEncodingTypeInt32 |
case 'L': return CPEncodingTypeUInt32 |
case 'q': return CPEncodingTypeInt64 |
case 'Q': return CPEncodingTypeUInt64 |
case 'f': return CPEncodingTypeFloat |
case 'd': return CPEncodingTypeDouble |
case 'D': return CPEncodingTypeLongDouble |
case '#': return CPEncodingTypeClass |
case ':': return CPEncodingTypeSEL |
case '*': return CPEncodingTypeCString |
case '^': return CPEncodingTypePointer |
case '[': return CPEncodingTypeCArray |
case '(': return CPEncodingTypeUnion |
case '{': return CPEncodingTypeStruct |
case '@': {
if (len == 2 && *(type + 1) == '?')
return CPEncodingTypeBlock |
return CPEncodingTypeObject |
default: return CPEncodingTypeUnknown |
很简单,不用多讲了。
回到 CPClassIvarInfo 刚才我们只给出了头文件,现在看一下实现。
- (instancetype)initWithIvar:(Ivar)ivar {
if (!ivar){
self = [super init];
if (self){
const char *name = ivar_getName(ivar);
if (name){
_name = [NSString stringWithUTF8String:name];
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding){
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = CPEncodingGetType(typeEncoding);
只有一个方法,这里就用到了两个 runtime 函数 ivar_getName(ivar) 和 ivar_getTypeEncoding(ivar) 传入 ivar 就行。
封装Method
然后看一下对于 Method 的封装,看一下头文件(CPClassMethodInfo.h)
#import &Foundation/Foundation.h&
#import &objc/runtime.h&
NS_ASSUME_NONNULL_BEGIN
@interface CPClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) M
@property (nonatomic, strong, readonly) NSString *
@property (nonatomic, assign, readonly) SEL
@property (nonatomic, assign, readonly) IMP
@property (nonatomic, strong, readonly) NSString *typeE
@property (nonatomic, strong, readonly) NSString *returnTypeE
@property (nullable, nonatomic, strong, readonly) NSArray&NSString *& *argumentTypeE
- (instancetype)initWithMethod:(Method)
NS_ASSUME_NONNULL_END
Objective-C 的 Optional
NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 是成对出现的,因为 Swift 可以和 Objective-C 混用,但是 Swift 有 Optional 类型,而 Objective-C 没有这样的概念,为了和 Swift 保持一致,现在 Objective-C 有了 _Nullable 可空 _Nonnull不可空这样的关键字,这两个关键字可以在变量、方法返回值、方法参数上使用,比如:
@property (nonatomic, strong) NSString
- (NSString * _Nonnull)method:(NSString *_Nonnull)
还有另外一对 nullable nonnull,它们可以这样用
@property (nullable, nonatomic, strong) NSString
- (nullable NSString *)method:(nullable NSString *)
对了,这些关键词只能用在指针上,其他类型是不能用的。
当你一旦在某个地方写上关键词 nullable的时候,编译器就会提出警告,Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified) 然后你就可以加上NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END来表示只有我标记为 nullable 的地方才可空,其余地方都是 nonnull。
回到刚才的头文件代码,method 表示一个方法
name 很明显就是方法名了
sel 和 imp 是一个对应关系,一个对象的所有方法都会保存在一张表里,通过 sel 就能找到这个方法的 imp,我讲的可能有点简单,如果想要深入的了解可以查一下文档或者博客。
typeEncoding 又是一个编码,这里是参数和返回值的编码
returnTypeEncoding 返回值的编码
argumentTypeEncodings 所有参数的编码
实现还是很简单
- (instancetype)initWithMethod:(Method)method {
if (!method) {
self = [super init];
if (self){
_sel = method_getName(method);
_imp = method_getImplementation(method);
const char *name = sel_getName(_sel);
if (name) {
_name = [NSString stringWithUTF8String:name];
const char *typeEncoding = method_getTypeEncoding(method);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
char *returnTypeEncoding = method_copyReturnType(method);
if (returnTypeEncoding) {
_returnTypeEncoding = [NSString stringWithUTF8String:returnTypeEncoding];
free(returnTypeEncoding);
//得到参数的数目,遍历取得所有参数的类型
unsigned int count = method_getNumberOfArguments(method);
if (count & 0) {
NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:10];
for (unsigned int i = 0; i & i++) {
char *argumentsType = method_copyArgumentType(method, i);
NSString *type = argumentsType ? [NSString stringWithUTF8String:argumentsType] :
[types addObject:type ? type : @""];
if (argumentsType) {
free(argumentsType);
_argumentTypeEncodings =
和前面套路一样。
封装 Property
老样子,看头
#import &Foundation/Foundation.h&
#import &objc/runtime.h&
#import "CPCommon.h"
NS_ASSUME_NONNULL_BEGIN
@interface CPClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_
@property (nonatomic, strong, readonly) NSString *
@property (nonatomic, assign, readonly) CPEncodingT
@property (nonatomic, strong, readonly) NSString *typdE
@property (nonatomic, strong, readonly) NSString *ivarN
@property (nullable, nonatomic, assign, readonly) C
@property (nonatomic, assign, readonly) SEL
@property (nonatomic, assign, readonly) SEL
- (instancetype)initWithProperty:(objc_property_t)
NS_ASSUME_NONNULL_END
这是在精简版的YYModel中会用到的一个类,这里尤其要注意的是type和typdEncoding两个属性,希望读者能够仔细调试一下,看一下主要的一段代码:
CPEncodingType type = 0;
unsigned int outC
objc_property_attribute_t *attrs = property_copyAttributeList(property, &outCount);
//遍历所有的Property的属性
for (unsigned int i = 0; i & outC i++) {
switch (attrs[i].name[0]) {
if (attrs[i].value) {
_typdEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = CPEncodingGetType(attrs[i].value);
if((type & CPEncodingTypeMask) == CPEncodingTypeObject){
//如果该类型为一个对象 比如 @"NSString" ,截取中间的,结果为 NSString,目的是为了得到这个类的 Class
size_t len = strlen(attrs[i].value);
if (len & 3) {
char name[len - 2];
name[len - 3] = '\0';
memcpy(name, attrs[i].value + 2, len - 3);
_cls = objc_getClass(name);
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
type |= CPEncodingTypePropertyR
type |= CPEncodingTypePropertyC
type |= CPEncodingTypePropertyR
type |= CPEncodingTypePropertyN
type |= CPEncodingTypePropertyD
type |= CPEncodingTypePropertyW
type |= CPEncodingTypePropertyCustomG
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
type |= CPEncodingTypePropertyCustomS
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
我们通过property_copyAttributeList这个函数得到一个指向一个结构体objc_property_attribute_t的指针,这个结构体的结构如下:
typedef struct {
const char *
/**& The name of the attribute */
const char *
/**& The value of the attribute (usually empty) */
} objc_property_attribute_t;
说是一个指针,其实它是一个结构体数组,指针指向的其实是这个数组第一个元素。
这个结构体表示的是一个 Property 的属性,关于 Property 的类型编码可以看
要说清这个数组里每一个结构体元素的name和value都存了什么,我们可以看一下下面这段代码:
Class cls = objc_getClass("CPBook");
objc_property_t property = class_getProperty(cls, "name");
const char* attr = property_getAttributes(property);
NSLog(@"%s",attr);
这里比如有一个类是 CPBook ,我们通过这个类的 Class 来拿到一个叫做 name 的 Property,然后在拿到这个 Property 所有属性,输出的结果是 T@"NSString",&,N,V_name
其实,我们用和上面一样返回一个结构体数组的方式来获取这个 Property 的属性的话,那么这个结构体应该会有4个元素。
第一个元素 name = T,value = @"NSString",第二个元素 name = &,value 没有值,第三个元素 name = N,value 仍然没有值,第四个元素 name = V,value = _name。不信可以运行一下下面的代码来看看。
Class cls = objc_getClass("CPBook");
objc_property_t *prop = class_copyPropertyList(cls, &acount);
objc_property_attribute_t *attr1 = property_copyAttributeList(prop[2], &acount);
NSLog(@"%s",attr1[0].name);
NSLog(@"%s",attr1[0].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[1].name);
NSLog(@"%s",attr1[1].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[2].name);
NSLog(@"%s",attr1[2].value);
NSLog(@"-------------------");
NSLog(@"%s",attr1[3].name);
NSLog(@"%s",attr1[3].value);
至于 V N & 这样的符号是什么意思,可以打开上面给出的链接自己看一下文档,一看便知。
这样一来在 switch 分支中,只要匹配到 T 就能得到这个 Property 的类型是什么,这样就可以得到这个类型的 Type Encoding,并且能够得到该类的 Class。只要匹配到 V 就能得到这个 Property 实例变量名。
该类全部代码如下:
- (instancetype)initWithProperty:(objc_property_t)property {
if (!property) {
self = [super init];
if (self) {
_property =
const char *name = property_getName(property);
if (name) {
_name = [NSString stringWithUTF8String:name];
CPEncodingType type = 0;
unsigned int outC
objc_property_attribute_t *attrs = property_copyAttributeList(property, &outCount);
//遍历所有的Property的属性
for (unsigned int i = 0; i & outC i++) {
switch (attrs[i].name[0]) {
if (attrs[i].value) {
_typdEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = CPEncodingGetType(attrs[i].value);
if((type & CPEncodingTypeMask) == CPEncodingTypeObject){
//如果该类型为一个对象 比如 @"NSString" ,截取中间的,结果为 NSString,目的是为了得到这个类的 Class
size_t len = strlen(attrs[i].value);
if (len & 3) {
char name[len - 2];
name[len - 3] = '\0';
memcpy(name, attrs[i].value + 2, len - 3);
_cls = objc_getClass(name);
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
type |= CPEncodingTypePropertyR
type |= CPEncodingTypePropertyC
type |= CPEncodingTypePropertyR
type |= CPEncodingTypePropertyN
type |= CPEncodingTypePropertyD
type |= CPEncodingTypePropertyW
type |= CPEncodingTypePropertyCustomG
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
type |= CPEncodingTypePropertyCustomS
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
if (attrs) {
free(attrs);
attrs = NULL;
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
if (!_setter) {
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
这样一来,我们就有了 ivar Method Property 的封装类。接下来,我们需要一个叫做CPClassInfo的类,来封装一些类的信息,并且把以上三个类也封装进去,用来描述整个类。
封装 Class
继续看头:
#import &Foundation/Foundation.h&
#import &objc/runtime.h&
@class CPClassIvarI
@class CPClassMethodI
@class CPClassPropertyI
NS_ASSUME_NONNULL_BEGIN
@interface CPClassInfo : NSObject
@property (nonatomic, assign, readonly) C
@property (nonatomic, assign, readonly) Class superC
@property (nonatomic, assign, readonly) Class metaC
@property (nonatomic, readonly) BOOL isM
@property (nonatomic, strong, readonly) NSString *
@property (nullable, nonatomic, strong, readonly) CPClassInfo *superClassI
@property (nullable, nonatomic, strong, readonly) NSDictionary&NSString *, CPClassIvarInfo *& *ivarI
@property (nullable, nonatomic, strong, readonly) NSDictionary&NSString *, CPClassMethodInfo *& *methodI
@property (nullable, nonatomic, strong, readonly) NSDictionary&NSString *, CPClassPropertyInfo *& *propertyI
- (void)setNeedU
- (BOOL)needU
+ (nullable instancetype)classInfoWithClass:(Class)
NS_ASSUME_NONNULL_END
Class 类型用来描述一个类,你可以使用
model.class
[model class]
[CPTestModel class]
object_getClass(model)
等方法来取到这个 Class。?注意object_getClass()和其他方式 有些不同具体看
其余的 Property 不用多介绍了,看到它们的名字就大概能猜到干嘛的了。
最后的几个 NSDictionary 用来存所有的 ivar Method Property。
有些时候,一个类有可能被更改,可能改掉了方法或者是 Property,那么这时候应该通知CPClassInfo来重新获取到更改过后的类的信息。所以我们有两个相关的方法来实现这个目的。
- (void)setNeedU
- (BOOL)needU
先来看一下初始化方法
- (instancetype)initWithClass:(Class)cls{
if (!cls) {
self = [super init];
if (self) {
_superClass = class_getSuperclass(cls);
_isMeta = class_isMetaClass(cls);
if (_isMeta) {
_metaClass = objc_getMetaClass(class_getName(cls));
_name = NSStringFromClass(cls);
[self _update];
_superClassInfo = [self.class classInfoWithClass:_superClass];
你没看错,这和头文件定义的classInfoWithClass:不是一个方法,头文件定义的那个方法用来缓存,因为实例化这个方法还是有点开销的,所以没有必要每一次都去实例化。
这里有一个 _update 方法,刚才说过,如果这个类会在某一个时刻发生变化,应该通知,收到通知后,我们去执行一些更新的操作,所以把会发生变化的一部分代码单独拿出来更好,现在看一下 _update 方法。
- (void)_update{
_ivarInfos =
_propertyInfos =
_methodInfos =
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(self.cls, &ivarCount);
if (ivars) {
_ivarInfos = [NSMutableDictionary new];
for (unsigned int i = 0; i & ivarC i++) {
CPClassIvarInfo *ivarInfo = [[CPClassIvarInfo alloc] initWithIvar:ivars[i]];
if (ivarInfo.name) {
[_ivarInfos setValue:ivarInfo forKey:ivarInfo.name];
free(ivars);
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(self.cls, &propertyCount);
if (properties) {
_propertyInfos = [NSMutableDictionary new];
for (unsigned int i = 0; i & propertyC i++) {
CPClassPropertyInfo *propertyInfo = [[CPClassPropertyInfo alloc] initWithProperty:properties[i]];
if (propertyInfo.name) {
[_propertyInfos setValue:propertyInfo forKey:propertyInfo.name];
free(properties);
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(self.cls, &methodCount);
if (methods) {
_methodInfos = [NSMutableDictionary new];
for (unsigned int i = 0; i & methodC i++) {
CPClassMethodInfo *methodInfo = [[CPClassMethodInfo alloc] initWithMethod:methods[i]];
if (methodInfo.name) {
[_methodInfos setValue:methodInfo forKey:methodInfo.name];
free(methods);
if (!_ivarInfos) {
_ivarInfos = @{};
if (!_methodInfos) {
_methodInfos = @{};
if (!_propertyInfos) {
_propertyInfos = @{};
_needUpdate = NO;
其实这个方法就是拿到一个类所有的 ivar Method Property ,一个类发生变化是不是主要就是这三个玩意的变化?
最后一行的 _needUpdate 是一个全局变量,用来标识是否发生的变化,它被定义在这里,以免暴露给外面。
@implementation CPClassInfo{
BOOL _needU
当外界需要通知自己已经发生变化或者查一下是否发生变化时就调用这两个相关方法
- (BOOL)needUpdate {
return _needU
- (void)setNeedUpadte {
_needUpdate = YES;
现在来看一下classInfoWithClass:
+ (instancetype)classInfoWithClass:(Class)cls{
if (!cls) {
static NSMutableDictionary *metaC
static NSMutableDictionary *classC
static dispatch_semaphore_
static dispatch_once_t onceT
dispatch_once(&onceToken, ^{
metaCache = [NSMutableDictionary dictionary];
classCache = [NSMutableDictionary dictionary];
lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CPClassInfo *
if (class_isMetaClass(cls)) {
info = [metaCache valueForKey:NSStringFromClass(cls)];
info = [classCache valueForKey:NSStringFromClass(cls)];
if (info && info-&_needUpdate) {
[info _update];
dispatch_semaphore_signal(lock);
if (!info) {
info = [[CPClassInfo alloc] initWithClass:cls];
if (info) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
if (info.isMeta) {
[metaCache setValue:info forKey:NSStringFromClass(cls)];
[classCache setValue:info forKey:NSStringFromClass(cls)];
dispatch_semaphore_signal(lock);
两个 NSMutableDictionary 都是用来缓存的,并声明在了静态区,并且使用dispatch_once()来确保只会被初始化一次,然后我们需要保证线程安全,因为有可能会在多线程的场景里被用到,所以使用信号量dispatch_semaphore_t来搞定,信号量就像停车这样的场景一样,如果发现车满了,就等待,一有空位就放行,也就是说,当一个线程要进入临界区的时候,必须获取一个信号量,如果没有问题就进入临界区,这时另一个线程进来了,也要获取,发现信号量并没有释放,就继续等待,直到前面一个信号量被释放后,该线程才准许进入。我们可以使用dispatch_semaphore_wait()来获取信号量,通过dispatch_semaphore_signal()来释放信号量。
在这段代码里,我们首先确保要实例化的这个对象有没有被缓存,用传进来的 cls 作为 key,如果缓存命中,那直接取出缓存,然后判断一下,有没有更新,如果有更新,调用_update刷新一遍,返回,否则直接返回。缓存没有命中的话,还是乖乖的调用实例化方法,然后缓存起来。
CPModelPropertyMeta
先建一个文件,叫做 CPMeta.h 和 CPMeta.m,我们要在这里写两个类,一个是对 Property 的再次封装,一个是对 Class 的再次封装。
我直接把头文件代码全拿出来了:
#import &Foundation/Foundation.h&
#import &objc/runtime.h&
#import "CPCommon.h"
@class CPClassI
@class CPClassPropertyI
typedef NS_ENUM (NSUInteger, CPEncodingNSType) {
CPEncodingTypeNSUnknown = 0,
CPEncodingTypeNSString,
CPEncodingTypeNSMutableString,
CPEncodingTypeNSValue,
CPEncodingTypeNSNumber,
CPEncodingTypeNSDecimalNumber,
CPEncodingTypeNSData,
CPEncodingTypeNSMutableData,
CPEncodingTypeNSDate,
CPEncodingTypeNSURL,
CPEncodingTypeNSArray,
CPEncodingTypeNSMutableArray,
CPEncodingTypeNSDictionary,
CPEncodingTypeNSMutableDictionary,
CPEncodingTypeNSSet,
CPEncodingTypeNSMutableSet,
@interface CPModelMeta : NSObject{
CPClassInfo *_clsI
NSDictionary *_
NSArray *_allPropertyM
NSUInteger _keyMappedC
CPEncodingNSType _nsT
+ (instancetype)metaWithClass:(Class)
@interface CPModelPropertyMeta : NSObject{
NSString *_
CPEncodingType _
CPEncodingNSType _nsT
BOOL _isCN
Class _genericC
BOOL _isKVCC
NSString *_mappedToK
CPClassPropertyInfo *_
+ (instancetype)modelWithClassInfo:(CPClassInfo *)clsInfo propretyInfo:(CPClassPropertyInfo *)propertyInfo generic:(Class)
可以看到这里有两个类,姑且叫做 CPModelPropertyMeta 和 CPModelMeta 以及一个枚举,这个枚举表示一个NS的类型,因为在上一个枚举当中,我们对于对象只定义了 CPEncodingTypeObject 这一个类型,没法区分它到底是 NSString 还是别的,所以这里要细化一下,类型判断清楚很重要,如果不把这部分做好,那么在JSON转换的时候,类型上出错就直接蹦了。
先来看一下 CPModelPropertyMeta 。(在 YYModel 中,这两个类其实是和一个叫做NSObject+CPModel的扩展放在一起的,但是我强制把它们拆出来了,为了看起来清楚,所以我把 @package 的成员变量都写到了 interface 里面,这么做是不合理的,但这里为了清晰和学习起见,所以我乱来了。)这个类中多了几个成员变量,我就说几个看起来不那么清楚的成员变量。
_isCNumber 这里变量表示是不是一个C语言的类型,比如int这样的。
_genericCls这个变量在精简版里没用到,我只是放在这里,YYModel 可以给容器型的属性转换,具体可以看YY大神的文档。
_isKVCCompatible 能不能支持 KVC
_mappedToKey 要映射的 key,把 JSON 转成 Model 的时会根据这个 key 把相同字段的 JSON 值赋值给这个 Property。
为了判断 NS 的类型和是否是 C 类型,在 .m 里有两个函数
#define force_inline __inline__ __attribute__((always_inline))
static force_inline CPEncodingNSType CPClassGetNSType(Class cls) {
if (!cls) return CPEncodingTypeNSU
if ([cls isSubclassOfClass:[NSMutableString class]]) return CPEncodingTypeNSMutableS
if ([cls isSubclassOfClass:[NSString class]]) return CPEncodingTypeNSS
if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return CPEncodingTypeNSDecimalN
if ([cls isSubclassOfClass:[NSNumber class]]) return CPEncodingTypeNSN
if ([cls isSubclassOfClass:[NSValue class]]) return CPEncodingTypeNSV
if ([cls isSubclassOfClass:[NSMutableData class]]) return CPEncodingTypeNSMutableD
if ([cls isSubclassOfClass:[NSData class]]) return CPEncodingTypeNSD
if ([cls isSubclassOfClass:[NSDate class]]) return CPEncodingTypeNSD
if ([cls isSubclassOfClass:[NSURL class]]) return CPEncodingTypeNSURL;
if ([cls isSubclassOfClass:[NSMutableArray class]]) return CPEncodingTypeNSMutableA
if ([cls isSubclassOfClass:[NSArray class]]) return CPEncodingTypeNSA
if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return CPEncodingTypeNSMutableD
if ([cls isSubclassOfClass:[NSDictionary class]]) return CPEncodingTypeNSD
if ([cls isSubclassOfClass:[NSMutableSet class]]) return CPEncodingTypeNSMutableS
if ([cls isSubclassOfClass:[NSSet class]]) return CPEncodingTypeNSS
return CPEncodingTypeNSU
static force_inline BOOL CPEncodingTypeIsCNumber(CPEncodingType type) {
switch (type & CPEncodingTypeMask) {
case CPEncodingTypeBool:
case CPEncodingTypeInt8:
case CPEncodingTypeUInt8:
case CPEncodingTypeInt16:
case CPEncodingTypeUInt16:
case CPEncodingTypeInt32:
case CPEncodingTypeUInt32:
case CPEncodingTypeInt64:
case CPEncodingTypeUInt64:
case CPEncodingTypeFloat:
case CPEncodingTypeDouble:
case CPEncodingTypeLongDouble: return YES;
default: return NO;
这两个函数不用多说了,很简单,要说明一下宏定义 force_inline 所有标记了 force_inline 的函数叫做内联函数,在调用的时候都不是一般的调用,而是在编译的时候就已经整个丢进了调用这个函数的方法或函数里去了,这和平时定义一个宏一样,你在哪里使用到了这个宏,那么在编译的时候编译器就会把你使用这个宏的地方替换成宏的值。为什么要这么做呢?因为效率,调用一个函数也是有开销的,调用一个函数有压栈弹栈等操作。如果你的函数很小,你这么一弄就免去了这些操作。
然后看一下CPModelPropertyMeta的初始化方法
+ (instancetype)modelWithClassInfo:(CPClassInfo *)clsInfo propretyInfo:(CPClassPropertyInfo *)propertyInfo generic:(Class)generic{
CPModelPropertyMeta *meta = [self new];
meta-&_name = propertyInfo.
meta-&_type = propertyInfo.
meta-&_info = propertyI
meta-&_genericCls =
if ((meta-&_type & CPEncodingTypeMask) == CPEncodingTypeObject) {
meta-&_nsType = CPClassGetNSType(propertyInfo.cls);
meta-&_isCNumber = CPEncodingTypeIsCNumber(meta-&_type);
meta-&_cls = propertyInfo.
if (propertyInfo.getter) {
if ([clsInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
meta-&_getter = propertyInfo.
if (propertyInfo.setter) {
if ([clsInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
meta-&_setter = propertyInfo.
if (meta-&_setter && meta-&_getter) {
switch (meta-&_type & CPEncodingTypeMask) {
case CPEncodingTypeBool:
case CPEncodingTypeInt8:
case CPEncodingTypeUInt8:
case CPEncodingTypeInt16:
case CPEncodingTypeUInt16:
case CPEncodingTypeInt32:
case CPEncodingTypeUInt32:
case CPEncodingTypeInt64:
case CPEncodingTypeUInt64:
case CPEncodingTypeFloat:
case CPEncodingTypeDouble:
case CPEncodingTypeObject:
case CPEncodingTypeClass:
case CPEncodingTypeBlock:
case CPEncodingTypeStruct:
case CPEncodingTypeUnion: {
meta-&_isKVCCompatible = YES;
判断一下是否是 object 的类型,然后拿到具体的 NS 类型,或者判断一下是不是 C 类型,然后拿到 getter setter 最后判断一下能不能 KVC。
CPModelPropertyMeta
这个类主要是生成一个映射表,这个映射表就是 _mapper 这个变量,这个类也需要被缓存起来,套路和上面讲到的缓存套路一样
+ (instancetype)metaWithClass:(Class)cls {
static CFMutableDictionaryR
static dispatch_once_t onceT
static dispatch_semaphore_
dispatch_once(&onceToken, ^{
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CPModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);
if (!meta || meta-&_clsInfo.needUpdate) {
meta = [[CPModelMeta alloc] initWithClass:cls];
if (meta) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
dispatch_semaphore_signal(lock);
缓存没命中就调用 initWithClass: 来进行初始化
- (instancetype)initWithClass:(Class)cls{
if (!cls) {
self = [super init];
if (self) {
CPClassInfo *clsInfo = [CPClassInfo classInfoWithClass:cls];
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
CPClassInfo *curClsInfo = clsI
//连同当前的类和其父类的属性一起放入allPropertyMetas数组,(NSObject和NSProxy是没有父类的)
while (curClsInfo && curClsInfo.superClass != nil) {
for (CPClassPropertyInfo *propertyInfo in curClsInfo.propertyInfos.allValues) {
if (!propertyInfo.name)
CPModelPropertyMeta *meta = [CPModelPropertyMeta modelWithClassInfo:clsInfo propretyInfo:propertyInfo generic:nil];
if (!meta || !meta-&_name)
if (!meta-&_setter || !meta-&_getter)
if (allPropertyMetas[meta-&_name])
allPropertyMetas[meta-&_name] =
curClsInfo = clsInfo.superClassI
if (allPropertyMetas.count) {
_allPropertyMetas = allPropertyMetas.allValues.
NSMutableDictionary *mapper = [NSMutableDictionary new];
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *
_Nonnull name, CPModelPropertyMeta *
_Nonnull meta, BOOL * _Nonnull stop) {
meta-&_mappedToKey =
mapper[name] =
if (mapper.count) _mapper =
_clsInfo = clsI
_keyMappedCount = _allPropertyMetas.
_nsType = CPClassGetNSType(cls);
把 CPClassInfo 里所有的 propertyInfo 遍历出来,实例化成一个 CPModelPropertyMeta ,还顺便把 CPClassInfo 父类的所有 propertyInfo 也拿出来,这样一来,你的 Model 即便有一个父类也能把父类的 Property 赋值。
然后生成一个映射表,就基本完成了初始化工作了,这张映射表是关键,等一下所有的 JSON 的转换都依赖这一张表。
从 JSON 到 Model 的转换
现在进入正餐环节,我们刚才已经把所有的准备工作完成了,现在要开始正式的完成从 JSON 到 Model 的转换了。
首先,先建一个 Category,取名 CPModel,因为我们只完成整个 YYMode 的一个主要功能,所以我们只给出一个接口就行了,所以头文件很简单。
#import &Foundation/Foundation.h&
@interface NSObject (CPModel)
+ (instancetype)modelWithJSON:(id)
使用者只需要调用 + modelWithJSON: 即可完成转换的操作。
现在看看这个方法要怎么实现:
+ (instancetype)modelWithJSON:(id)json {
NSDictionary *dic = [self _cp_dictionaryWithJSON:json];
if (!dic || dic == (id)kCFNull) {
if (![dic isKindOfClass:[NSDictionary class]]) {
Class cls = [self class];
NSObject *one = [cls new];
if ([one modelSetWithDictionary:dic]) {
首先先把 JSON 转换成 NSDictionary ,然后得到该 Model 的 Class 去实例化这个 Model,接着调用一个叫做- modelSetWithDictionary: 的方法。
把 JSON 转换成 NSDictionary 的方法很简单
+ (NSDictionary *)_cp_dictionaryWithJSON:(id)json{
if (!json || json == (id)kCFNull) {
NSDictionary *dic =
NSData *data =
if ([json isKindOfClass:[NSDictionary class]]) {
}else if ([json isKindOfClass:[NSString class]]) {
data = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
}else if ([json isKindOfClass:[NSData class]]) {
if (data) {
dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
if (![dic isKindOfClass:[NSDictionary class]]) {
然后看一下 - modelSetWithDictionary:
- (BOOL)modelSetWithDictionary:(NSDictionary *)dic{
if (!dic || dic == (id)kCFNull) {
return NO;
if (![dic isKindOfClass:[NSDictionary class]]) {
return NO;
CPModelMeta *meta = [CPModelMeta metaWithClass:object_getClass(self)]; //①
if (meta-&_keyMappedCount == 0) {
return NO;
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(meta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
if (meta-&_keyMappedCount &= dic.count) {
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
return YES;
这里有一个结构体,这个结构体用来存储 model(因为是给这个Model 里的 Property 赋值)、modelMeta(刚才也看到了,这里存放了映射表)、dictionary(这是由 JSON 转换过来的),这个结构体的定义如下:
typedef struct {
void *modelM
} ModelSetC
然后在- modelSetWithDictionary:有这么一行代码
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
这个代码的作用是,把一对 Key - Value 拿出来,然后调用你传进去的函数ModelSetWithDictionaryFunction(),你有多少对Key - Value,它就会调用多少次这个函数,相当于便利所有的Key - Value,为什么要这样做,而不用一个循环呢?在作者的博客里有这么一段
遍历容器类时,选择更高效的方法
相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦。
然后我们来看一下ModelSetWithDictionaryFunction()的实现
static void ModelSetWithDictionaryFunction(const void *key, const void *value, void *context) {
ModelSetContext *ctx =
__unsafe_unretained CPModelMeta *modelMeta = (__bridge CPModelMeta *)(ctx-&modelMeta);
__unsafe_unretained CPModelPropertyMeta *propertyMeta = [modelMeta-&_mapper objectForKey:(__bridge id)(key)];
__unsafe_unretained id model = (__bridge id)(ctx-&model);
if (propertyMeta-&_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)value, propertyMeta);
为什么在变量前都加了__unsafe_unretained,作者也说了
避免多余的内存管理方法
在 ARC 条件下,默认声明的对象是 strong 类型的,赋值时有可能会产生 retain/release 调用,如果一个变量在其生命周期内不会被释放,则使用 unsafe_unretained 会节省很大的开销。
访问具有 weak 属性的变量时,实际上会调用 objc_loadWeak() 和 objc_storeWeak() 来完成,这也会带来很大的开销,所以要避免使用 weak 属性。
继续,根据 key(这个 key 就是 JSON 里的字段,应该和你 Model 定义的 Property 名相同,否则就匹配不了,在 YYMode 中有一个自定义映射表的支持,我把它去掉了,有兴趣的可以下载 YYMode 的源码看一下) 取出映射表里的 propertyMeta。现在我们有了要转换的 model 对象,和一个和 JSON 里字段对应的 propertyMeta 对象,已经该 JSON 字段的值,现在要赋值的条件全部具备了,我们只需要调用propertyMeta中的setter方法,然后把值传进去就完成了,这部分的工作由 ModelSetValueForProperty()函数完成,这个函数里有大量的类型判断,为了简单起见,我就判断了NSString NSNumber 和普通C语言类型,代码如下:
static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained CPModelPropertyMeta *meta) {
if (meta-&_isCNumber) {
NSNumber *num = CPNSNumberCreateFromID(value);
ModelSetNumberToProperty(model, num, meta);
if (num) [num class];
} else if (meta-&_nsType) {
if (value == (id)kCFNull) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta-&_setter, (id)nil);
switch (meta-&_nsType) {
case CPEncodingTypeNSString:
case CPEncodingTypeNSMutableString: {
if ([value isKindOfClass:[NSString class]]) {
if (meta-&_nsType == CPEncodingTypeNSString) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta-&_setter, value);
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta-&_setter, ((NSString *)value).mutableCopy);
} else if ([value isKindOfClass:[NSNumber class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta-&_setter,
(meta-&_nsType == CPEncodingTypeNSString) ?
((NSNumber *)value).stringValue :
((NSNumber *)value).stringValue.mutableCopy);
} else if ([value isKindOfClass:[NSData class]]) {
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta-&_setter, string);
} else if ([value isKindOfClass:[NSURL class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta-&_setter,
(meta-&_nsType == CPEncodingTypeNSString) ?
((NSURL *)value).absoluteString :
((NSURL *)value).absoluteString.mutableCopy);
} else if ([value isKindOfClass:[NSAttributedString class]]) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
meta-&_setter,
(meta-&_nsType == CPEncodingTypeNSString) ?
((NSAttributedString *)value).string :
((NSAttributedString *)value).string.mutableCopy);
case CPEncodingTypeNSNumber:{
if ([value isKindOfClass:[NSNumber class]]) {
if (meta-&_nsType == CPEncodingTypeNSNumber) {
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,meta-&_setter,value);
关于 objc_msgSend() 我们随便拿一行例子来举例,比如这个:
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta-&_setter, value);
这是一个可以调用者决定返回值和参数的函数,一般的函数是做不到的,默认情况下这个函数是长这样
objc_msgSend(id, SEL)
id 是指调用某一个方法的对象,在这里这个对象就是你的 Model
SEL 是指你这个对象要调用的方法是什么,在这里这个方法就是 setter方法
然而,setter 方法是有参数的,这个参数怎么传进去?这就需要强制类型转换了,我们把这个函数强制转换成这个模样:
((void (*)(id, SEL, id))(void *) objc_msgSend)
这样代表这个函数是一个没有返回值,并且有3个参数的函数,分别是 id SEL id,前面两个参数之前讲过了,第三个参数就是你要调用的这个 setter 方法需要的参数,所以经过强制类型转换之后的变异版就成了一开始的那种样子。
其余的都没有什么好讲的了,都很简单,都是一些烦人的类型判断,只要仔细一点一行行看就能看懂了。
全部搞定以后,和原版的 YYModel 一样,你可以这么来测试
CPTestModel *model = [CPTestModel modelWithJSON:@"{\"name\": \"Harry Potter\",\"index\": 512,\"number\": 10,\"num\": 100}"];
如果你自己亲自动手写完了这个精简版的 YYMode ,你再去看完整版的会容易很多,我写的这篇文章是把我从读 YYModel 源码中学到的一些有用的东西分享给大家,如有什么写错的地方,欢迎指正。
iOS 开发者

参考资料

 

随机推荐