MjExtention

MJExtension概述
MJExtension是是一个非常易用且功能强大的第三方Model和JSON相互转化的商业化第三方库,帮助开发者节省了从JSON或者Foundation object转换成Model所需的时间,而且强大的拓展功能,满足了开发者的大部分数据模型化的需求。
Objective-C runtime运行时机制

Runtime的特性主要是消息(方法)传递,如果消息(方法)在对象中找不到,就进行转发,具体怎么实现的呢。我们从下面几个方面探寻Runtime的实现机制。

Runtime介绍
Runtime消息传递
Runtime消息转发
Runtime应用

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。
Runtime消息传递
一个对象的方法像这样[obj foo],编译器转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程是这样的:

首先,通过obj的isa指针找到它的 class ;
在 class 的 method list 找 foo ;
如果 class 中没到 foo,继续往它的 superclass 中找 ;
一旦找到 foo 这个函数,就去执行它的实现IMP 。
但这种实现有个问题,效率低。但一个class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class 中另一个重要成员objc_cache 做的事情 - 再找到foo 之后,把foo 的method_name 作为key ,method_imp作为value 给存起来。当再次收到foo 消息的时候,可以直接在cache 里找到,避免去遍历objc_method_list。从前面的源代码可以看到objc_cache是存在objc_class 结构体中的。

objec_msgSend的方法定义如下:

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
那消息传递是怎么实现的呢?我们看看对象(object),类(class),方法(method)这几个的结构体:

//对象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
//类
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;

if !OBJC2

Class super_class                                        OBJC2_UNAVAILABLE;
const char *name                                         OBJC2_UNAVAILABLE;
long version                                             OBJC2_UNAVAILABLE;
long info                                                OBJC2_UNAVAILABLE;
long instance_size                                       OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;

endif

} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;

ifdef LP64

int space                                                OBJC2_UNAVAILABLE;

endif

/* variable length structure */
struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;

} OBJC2_UNAVAILABLE;
//方法
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}

系统首先找到消息的接收对象,然后通过对象的isa找到它的类。
在它的类中查找method_list,是否有selector方法。
没有则查找父类的method_list。
找到对应的method,执行它的IMP。
转发IMP的return值。
下面讲讲消息传递用到的一些概念:

类对象(objc_class)
实例(objc_object)
元类(Meta Class)
Method(objc_method)
SEL(objc_selector)
IMP
类缓存(objc_cache)
Category(objc_category)

类对象(objc_class)
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。

typedef struct objc_class *Class;

struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

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;

struct objc_class结构体定义了很多变量,通过命名不难发现,
结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等,
一个类包含的信息也不就正是这些吗?没错,类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),
该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例。

实例(objc_object)
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?
就是从isa指针指向的结构体创建,类对象的isa指针指向的我们称之为元类(metaclass),
元类中保存了创建类对象以及类方法所需的所有信息,因此整个结构应该如下图所示:

image.png

元类(Meta Class)
通过上图我们可以看出整个体系构成了一个自闭环,struct objc_object结构体实例它的isa指针指向类对象,
类对象的isa指针指向了元类,super_class指针指向了父类的类对象,
而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己。

元类(Meta Class)是一个类对象的类。
在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。
任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

Method(objc_method)
先看下定义

runtime.h
/// An opaque type that represents a method in a class definition.代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
Method和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码,比如:

  • (void)logName
    {
    NSLog(@"name");
    }
    这段代码,就是一个函数。

我们来看下objc_method这个结构体的内容:

SEL method_name 方法名
char *method_types 方法类型
IMP method_imp 方法实现
在这个结构体重,我们已经看到了SEL和IMP,说明SEL和IMP其实都是Method的属性。

SEL(objc_selector)
先看下定义

Objc.h
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;
objc_msgSend函数第二个参数类型为SEL,它是selector在Objective-C中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:

@property SEL selector;
可以看到selector是SEL的一个实例。

A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.
其实selector就是个映射到方法的C字符串,你可以用 Objective-C 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个 SEL 类型的方法选择器。

selector既然是一个string,我觉得应该是类似className+method的组合,命名规则有两条:

同一个类,selector不能重复
不同的类,selector可以重复
这也带来了一个弊端,我们在写C代码的时候,经常会用到函数重载,就是函数名相同,参数不同,但是这在Objective-C中是行不通的,因为selector只记了method的name,没有参数,所以没法区分不同的method。

比如:

  • (void)caculate(NSInteger)num;
  • (void)caculate(CGFloat)num;
    是会报错的。

我们只能通过命名来区别:

  • (void)caculateWithInt(NSInteger)num;
  • (void)caculateWithFloat(CGFloat)num;
    在不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。

IMP
看下IMP的定义

/// A pointer to the function of a method implementation. 指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...);

endif

就是指向最终实现程序的内存地址的指针。

在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

类缓存(objc_cache)

当Objective-C运行时通过跟踪它的isa指针检查对象时,它可以找到一个实现许多方法的对象。然而,你可能只调用它们的一小部分,并且每次查找时,搜索所有选择器的类分派表没有意义。所以类实现一个缓存,每当你搜索一个类分派表,并找到相应的选择器,它把它放入它的缓存。所以当objc_msgSend查找一个类的选择器,它首先搜索类缓存。这是基于这样的理论:如果你在类上调用一个消息,你可能以后再次调用该消息。

为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在上述的objc_cache,所以在实际运行中,大部分常用的方法都是会被缓存起来的,Runtime系统实际上非常快,接近直接执行内存地址的程序速度。

Category(objc_category)
Category是表示一个指向分类的结构体的指针,其定义如下:

struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};

name:是指 class_name 而不是 category_name。
cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
instanceMethods:category中所有给类添加的实例方法的列表。
classMethods:category中所有添加的类方法的列表。
protocols:category实现的所有协议的列表。
instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。
从上面的category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量。

Runtime消息转发
前文介绍了进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。那么消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会。

动态方法解析
备用接收者
完整消息转发

image.png

动态方法解析
首先,Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。

实现一个动态方法解析的例子如下:

  • (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //执行foo函数
    [self performSelector:@selector(foo:)];
    }
  • (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(foo:)) {//如果是执行foo函数,就动态解析,指定新的IMP
    class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
    return YES;
    }
    return [super resolveInstanceMethod:sel];
    }

void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");//新的foo函数
}

可以看到虽然没有实现foo:这个函数,但是我们通过class_addMethod动态添加fooMethod函数,并执行fooMethod这个函数的IMP。从打印结果看,成功实现了。

如果resolve方法返回 NO ,运行时就会移到下一步:forwardingTargetForSelector。

备用接收者
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。
完整消息转发
如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil ,Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation 对象并发送 -forwardInvocation:消息给目标对象。

Runtime应用
Runtime简直就是做大型框架的利器。它的应用场景非常多,下面就介绍一些常见的应用场景。

关联对象(Objective-C Associated Objects)给分类增加属性
方法魔法(Method Swizzling)方法添加和替换和KVO实现
消息转发(热更新)解决Bug(JSPatch)
实现NSCoding的自动归档和自动解档
实现字典和模型的自动转换(MJExtension)
关联对象(Objective-C Associated Objects)给分类增加属性
我们都是知道分类是不能自定义属性和变量的。下面通过关联对象实现给分类添加属性

//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)

基本原理

基本原理非常简单,Fabric在这里简略介绍一下

第一步:获取NSObject中的所有属性

    unsigned int propertyCount = 0;    ///通过运行时获取当前类的属性    objc_property_t *propertys = class_copyPropertyList([self class], &propertyCount);        //把属性放到数组中    for (int i = 0; i < propertyCount; i ++) {    ///取出第一个属性    objc_property_t property = propertys[i];    //得到属性对应的名称    NSString *name = @(property_getName(property));        NSLog(@"name:%@", name);    }复制代码

这就是一个对于获取结构体对应指针值的一个很简单的包装。

第二步:在Foundation object(数组、字典等)以name 为key,寻找到对应的value值,然后将对应值填充入相应的Model当中

- (void)setValue:(id)value forObject:(id)object{    if (self.type.KVCDisabled || value == nil) return;    [object setValue:value forKey:self.name];}复制代码

ps:这里需要着重介绍一下,NSObject可以通过-[setValue:forKey:]的方式对相应的属性进行赋值,了解这点对于了解MJExtension原理很有必要。

MJExtension的优势

看了Fabric刚才的基本原理介绍,大家可能认为JSON转化为model非常简单嘛,核心代码也就几句。但是,MJExtension作为商业化SDK,它的强大优势在于它的兼容性好,拓展性强。开发者可以替换key值的名称,可以将数组里面的字典转化为对应的model,可以忽略某些转换的属性,也可以定义所有需要转换的属性,还可以针对于一些旧值,转换为新值,例如时间戳和时间的相互转换。

MJExtension详细原理

MJExtension内部结构

Fabric画了一张原理图,大致地将MJExtension的内部结构和类与类之间的相互关系描述了出来。

  • MJExtensionConst类: 类中定义了一些字符串常量,分别表示property的属性类型,具体的类型字符串是存放在MJProperty的属性MJPropertyType中的code属性下的。不同类型的属性由不同的encode字符串表示,读者可以自己@encode(int) @encode(float) @encode(NSString)打印出一些常用类型属性的encode值来加深理解。
  • MJPropertyType类: 用于记录MJProperty的一些相关的特性,例如要转换的对象的类型,是否是Foundation object类型对象等等,主要包括:

+ (instancetype)cachedTypeWithCode:(NSString *)code用于查找缓存的type类型。

BOOL idType BOOL numberType BOOL boolType等一堆代表要转换value对象具体类型的属性。

Class typeClass表明value对象的类型。

NSString *code,用来写入Property的encode值。

BOOL fromFoundation,用来表示要转换的 对象是否是NSDictionary``NSArray``NSSet等基本的Foundation object类型,简单来说就是如果要转换的对象是NSObject的子类且不是NSManagedObject类就返回NO

KVCDisabled该对象是否能被监听

  • MJPropertyKey类: 负责将value写入MJProperty,主要包括: - (id)valueInObject:(id)object用于将value值写入MJProperty对象。 MJPropertyKeyType type用于表明当前的MJProperty中需要转换的value是一个字典里面的value还是在数组里面的value。 NSString *name用于表示当前的NSDictionary中value的key值或者NSArray中value的index。
  • MJFoundation类: 判断当前对象是不是Foundation object(NSDictionary, NSArray等)
  • MJProperty类: MJExtension包装属性值的基本单位,每一个objc_property_t值都有一个MJProperty来进行包装,这个类是整个MJExtension代码中最核心的一个类,对于该类的作用,Fabric会在下文为大家介绍。
  • NSObject+MJProperty类: 为开发者预留了一些可以重写的方法和block,开发者可以用这些方法可以将字典中的key替换为Model中对应的Property,可以指定NSArray中的字典对应的Model。
  • NSObject+MJClass类: 设置JSON和Model互转的黑白名单,设置归档的黑白名单。
  • NSObject+MJKeyValue类: JSON和Model互转的实现类。
  • NSString+MJExtension类: 一些字符串特殊处理的方法,包括大小写互相转换,驼峰命名法和下划线命名法字符串的相互转换等。
  • NSObject+MJCoding类: 重写了-[encodeObject:forKey:]-[decoderObject:forkey:]两个方法,使得对象可以直接进行归档操作。

MJExtension核心代码分析

MJExtension的设计非常巧妙,涉及的方法也非常多,有限的篇幅里面很难说的细致入微,所以Fabric决定带大家一起探索一下MJExtension实现Model转换的核心的方法: - (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context。我们按照方法里面的代码,由上之下,从内而外执行下去:

//将JSON转换为Foundation object对象(NSDictionary, NSArray等)keyValues = [keyValues mj_JSONObject];复制代码
//设置黑名单和白名单NSArray *allowedPropertyNames = [clazzmj_totalAllowedPropertyNames];NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];复制代码
//声称了所有的MJProperty属性并进行遍历输出 [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) { //遍历所有的MJProperty对象,设置到Model的对应的属性当中 }复制代码

现在我们探究一下,每一个MJProperty对象是怎么生成的,结合我上面的MJExtension内部结构图,大家可能理解起来比较容易。 在NSObject+MJProperty类中,+ (NSMutableArray *)properties这个方法是专门负责生成所有的MJProperty对象的。

//首先从缓存中读取存储的MJProperty数组NSMutableArray *cachedProperties = [self dictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];复制代码

在这里需要注意两点:

1、掌握缓存的技巧可以提高项目性能,减少代码重复执行。

2、Fabric认为这里并不需要用字典来存储MJproperty对象数组,因为每一个Model都对应一个class,所以不存在两个class公用一个cachedProperty的情况,因此直接用数组来承接MJProperty属性数组就可以了。

继续探究+ (NSMutableArray *)properties方法, 如果没有缓存,就遍历所有的非Foundation object基本类型的对象,取出objc_property_t数组,包装成MJProperty数组。

        unsigned int outCount = 0;        objc_property_t *properties = class_copyPropertyList(c, &outCount);        for (unsigned int i = 0; i

Fabric认为

        if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;        property.srcClass = c;复制代码

这两行代码有些鸡肋,因为srcClass只有两种可能:Model类型或者nil,所以完全可以使用一个BOOL值来判断srcClass是否是Foundation object类型,而不用一个Class *srcClass属性。

在这里需要着重理解这几行代码

[property setOriginKey:[self propertyKey:property.name] forClass:self];[property setObjectClassInArray:[self propertyObjectClassInArray:property.name]复制代码

第一个方法是把所有要替换的key值包装成数组存储到NSMutableDictionary *propertyKeysDict对象中;

第二个方法是把数组中对应的想要转换成的Model的Class类型保存到NSMutableDictionary *objectClassInArrayDict字典中。

理解了这两个方法也就理解了+ (NSDictionary *)mj_replacedKeyFromPropertyName+ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray两个功能函数的实现原理了。

继续看如何包装objc_property_t property,先从缓存中读取MJProperty。

//这里需要注意property指针指向的内存地址每次都是不变的,所以可以这样动态关联MJProperty *propertyObj = objc_getAssociatedObject(self, property);复制代码

如果没有缓存就把property包装成一个MJProperty,

- (void)setProperty:(objc_property_t)property{    _property = property;        MJExtensionAssertParamNotNil(property);        // 1.属性名    _name = @(property_getName(property));        // 2.成员类型    NSString *attrs = @(property_getAttributes(property));    NSUInteger dotLoc = [attrs rangeOfString:@","].location;    NSString *code = nil;    NSUInteger loc = 1;    if (dotLoc == NSNotFound) { // 没有,        code = [attrs substringFromIndex:loc];    } else {        code = [attrs substringWithRange:NSMakeRange(loc, dotLoc - loc)];    }    _type = [MJPropertyType cachedTypeWithCode:code];}复制代码

这里代码已经很清楚了,获取property的name和attrs中的encode属性通过截取字符串来获得属性的类型。 下面来看一下MJProperty的type属性是如何设置的,

#pragma mark - 公共方法- (void)setCode:(NSString *)code{    _code = code;        MJExtensionAssertParamNotNil(code);        if ([code isEqualToString:MJPropertyTypeId]) {        _idType = YES;    } else if (code.length == 0) {        _KVCDisabled = YES;    } else if (code.length > 3 && [code hasPrefix:@"@\""]) {        // 去掉@"和",截取中间的类型名称        _code = [code substringWithRange:NSMakeRange(2, code.length - 3)];        _typeClass = NSClassFromString(_code);        _fromFoundation = [MJFoundation isClassFromFoundation:_typeClass];        _numberType = [_typeClass isSubclassOfClass:[NSNumber class]];            } else if ([code isEqualToString:MJPropertyTypeSEL] ||               [code isEqualToString:MJPropertyTypeIvar] ||               [code isEqualToString:MJPropertyTypeMethod]) {        _KVCDisabled = YES;    }        // 是否为数字类型    NSString *lowerCode = _code.lowercaseString;    NSArray *numberTypes = @[MJPropertyTypeInt, MJPropertyTypeShort, MJPropertyTypeBOOL1, MJPropertyTypeBOOL2, MJPropertyTypeFloat, MJPropertyTypeDouble, MJPropertyTypeLong, MJPropertyTypeLongLong, MJPropertyTypeChar];    if ([numberTypes containsObject:lowerCode]) {        _numberType = YES;                if ([lowerCode isEqualToString:MJPropertyTypeBOOL1]            || [lowerCode isEqualToString:MJPropertyTypeBOOL2]) {            _boolType = YES;        }    }}复制代码

以上代码判断了property属性的具体类型,参考@encode()函数和MJExtensionConst方法,大家应该能够理解以上代码。

到这里,MJProperty的包装就基本说完了。

紧接着,取出keyValues中对应的值,

// 1.取出属性值    id value;    NSArray *propertyKeyses = [property propertyKeysForClass:clazz];    for (NSArray *propertyKeys in propertyKeyses) {        value = keyValues;        for (MJPropertyKey *propertyKey in propertyKeys) {            value = [propertyKey valueInObject:value];        }        if (value) break;    }复制代码

处理value值,

// 值的过滤            id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];            if (newValue != value) { // 有过滤后的新值                [property setValue:newValue forObject:self];                return;            }                        // 如果没有值,就直接返回            if (!value || value == [NSNull null]) return;复制代码

最后是根据MJProperty的type中的typeClass,将不可扩展的集合转换成可变集合,方便后续的操作。对特定的值进行处理,如果是模型属性就继续进行递归转化,

if (!type.isFromFoundation && propertyClass) { // 模型属性                value = [propertyClass mj_objectWithKeyValues:value context:context];            }复制代码

如果是数组,就遍历数组,如果数组中还是数组就继续递归,如果不是调用+[mj_objectWithKeyValues:context]转化成对应的Model元素放入数组,

for (NSDictionary *keyValues in keyValuesArray) {        if ([keyValues isKindOfClass:[NSArray class]]){            [modelArray addObject:[self mj_objectArrayWithKeyValuesArray:keyValues context:context]];        } else {            id model = [self mj_objectWithKeyValues:keyValues context:context];            if (model) [modelArray addObject:model];        }    }复制代码

对于其他的propertyClass类型,Fabric在这里不做赘述,大家可以自己研究,并不复杂。

最后一步,将经过处理的value值代入Model当中,

// 3.赋值[property setValue:value forObject:self];复制代码

在模型转换完成之后,大家还可以重写- (void)mj_keyValuesDidFinishConvertingToObject这个函数进行后续操作。

你可能感兴趣的:(MjExtention)