Mantle
是一个用于简化Cocoa
或Cocoa Touch
程序中model
层的第三方库。通常我们的应该中都会定义大量的model
来表示各种数据结构,而这些model
的初始化和编码解码都需要写大量的代码。而Mantle
的优点在于能够大大地简化这些代码。
Mantle
源码中最主要的内容包括:
MTLModel
类:通常是作为我们的Model
的基类,该类提供了一些默认的行为来处理对象的初始化和归档操作,同时可以获取到对象所有属性的键值集合。MTLJSONAdapter
类:用于在MTLModel
对象和JSON
字典之间进行相互转换,相当于是一个适配器。MTLJSONSerializing
协议:需要与JSON
字典进行相互转换的MTLModel
的子类都需要实现该协议,以方便MTLJSONApadter
对象进行转换。
在此就以这三者作为我们的分析点。
基类MTLModel
MTLModel
是一个抽象类,它主要提供了一些默认的行为来处理对象的初始化和归档操作。
初始化
MTLModel
默认的初始化方法-init
并没有做什么事情,只是调用了下[super init]
。而同时,它提供了一个另一个初始化方法:
|
|
其中参数dictionaryValue
是一个字典,它包含了用于初始化对象的key-value
对。我们来看下它的具体实现:
|
|
子类可以重写该方法,以在设置完对象的属性后做进一步的处理或初始化工作,不过需要记住的是:应该通过super
来调用父类的实现。
获取属性的键(key)、值(value)
MTLModel
类提供了一个类方法+propertyKeys
,该方法返回所有@property
声明的属性所对应的名称字符串的一个集合,但不包括只读属性和MTLModel
自身的属性。在这个类方法会去遍历model
的所有属性,如果属性是非只读且其ivar
值不为NULL
,则获取到表示属性名的字符串,并将其放入到集合中,其实现如下:
|
|
有了上面这个类方法,要想获取到对象中所有属性及其对应的值就方法了。为此MTLModel
提供了一个只读属性dictionaryValue
来取一个包含当前model
所有属性及其值的字典。如果属性值为nil
,则会用NSNull
来代替。另外该属性不会为nil
。
|
|
合并对象
合并对象是指将两个MTLModel
对象按照自定义的方法将其对应的属性值进行合并。为此,在MTLModel
定义了以下方法:
|
|
该方法将当前对象指定的key
属性的值与model
参数对应的属性值按照指定的规则来进行合并,这种规则由我们自定义的-merge<Key>FromModel:
方法来确定。如果我们的子类中实现了-merge<Key>FromModel:
方法,则会调用它;如果没有找到,且model
不为nil
,则会用model
的属性的值来替代当前对象的属性的值。具体实现如下:
|
|
此外,MTLModel
还提供了另一个方法来合并两个对象所有的属性值,即:
|
|
需要注意的是model
必须是当前对象所属类或其子类。
归档对象(Archive)
Mantle
将对MTLModel
的编码解码处理都放在了MTLModel
的NSCoding
分类中进行处理了,该分类及相关的定义都放在MTLModel+NSCoding
文件中。
对于不同的属性,在编码解码过程中可能需要区别对待,为此Mentle
定义了枚举MTLModelEncodingBehavior
来确定一个MTLModel
属性被编码到一个归档中的行为。其定义如下:
|
|
具体每个属性的归档行为我们可以在+encodingBehaviorsByPropertyKey
类方法中设置。MTLModel
类为我们提供了一个默认实现,如下:
|
|
任何不在该返回字典中的属性都不会被归档。子类可以根据自己的需要来指定各属性的归档行为。但在实际时应该通过super来调用父类的实现。
而为了从归档中解码指定的属性,Mantle
提供了以下方法:
|
|
默认情况下,该方法会查找当前对象中类似于-decode<Key>WithCoder:modelVersion:
的方法,如果找到便会调用相应方法,并按照自定义的方式来处理属性的解码。如果我们没有实现自定义的方法或者coder
不需要安全编码,则会对指定的key调用-[NSCoder decodeObjectForKey:]
方法。其具体实现如下:
|
|
当然,所有的编码解码工作还得需要我们实现-initWithCoder:
和-encodeWithCoder:
两个方法来完成。我们在定义MTLModel
的子类时,可以根据自己的需要来对特定的属性进行处理,不过最好调用super
的实现来执行父类的操作。MTLModel
对这两个方法的实现请参考源码,在此不多作说明。
适配器MTLJSONApadter
为了便于在MTLModel
对象和JSON
字典之间进行相互转换,Mantle
提供了类MTLJSONApadter
,作为这两者之间的一个适配器。
MTLJSONSerializing协议
Mantle
定义了一个协议MTLJSONSerializing
,那些需要与JSON
字典进行相互转换的MTLModel
的子类都需要实现该协议,以方便MTLJSONApadter
对象进行转换。这个协议中定义了三个方法,具体如下:
|
|
这三个方法都是类方法。其中+JSONKeyPathsByPropertyKey
是必须实现的,它返回的字典指定了如何将对象的属性映射到JSON
中不同的key path
(字符串值或NSNull
)中。任何不在此字典中的属性被认为是与JSON
中使用的key
值相匹配。而映射到NSNull
的属性在JSON
序列化过程中将不进行处理。
+JSONTransformerForKey:
方法指定了如何将一个JSON
值转换为指定的属性值。反过来,转换器也用于将属性值转换成JSON
值。如果转换器实现了+<key>JSONTransformer
方法,则MTLJSONAdapter
会使用这个具体的方法,而不使用+JSONTransformerForKey:
方法。另外,如果不需要执行自定义的转换,则返回nil。
重写+classForParsingJSONDictionary:
方法可以将当前Model
解析为一个不同的类对象。这对象类簇是非常有用的,其中抽象基类将被传递给-[MTLJSONAdapter initWithJSONDictionary:modelClass:]
方法,而实例化的则是子类。
如果我们希望MTLModel
的一个子类能使用MTLJSONApadter
来进行转换,则需要实现这个协议,并实现相应的方法。
初始化
MTLJSONApadter
对象有一个只读属性,该属性即为适配器需要处理的MTLModel
对象,其声明如下:
|
|
可见该对象必须是实现了MTLJSONSerializing
协议的MTLModel
对象。该属性是只读的,因此它只能通过初始化方法来初始化。
MTLJSONApadter
对象不能通过-init
来初始化,这个方法会直接断言。而是需要通过类提供的两个初始化方法来初始化,如下:
|
|
其中-(id)initWithJSONDictionary:modelClass:error:
是使用一个字典和需要转换的类来进行初始化。字典JSONDictionary
表示一个JSON
数据,这个字典需要符合NSJSONSerialization
返回的格式。如果该参数为空,则方法返回nil
,且返回带有MTLJSONAdapterErrorInvalidJSONDictionary
码的error
对象。该方法的具体实现如下:
|
|
另外,MTLJSONApadter
还提供了几个类方法来创建一个MTLJSONApadter
对象,如下:
|
|
具体实现可参考源码。
从对象中获取JSON数据
从MTLModel
对象中获取JSON
数据是上述初始化过程中的一个逆过程。该过程由-JSONDictionary
方法来实现,具体如下:
|
|
从上可以看出,该方法实际上最终获得的是一个字典。而获得字典后,再将其序列化为JSON
串就容易了。
MTLJSONApadter
也提供了一个简便的方法,来从一个model
中获取一个JSON
字典,其定义如下:
|
|
MTLManagedObjectAdapter
为了适应Core Data
,Mantle
专门定义了MTLManagedObjectAdapter
类。该类用作MTLModel
对象与NSManagedObject
对象之前的转换。具体的我们在此不详细描述。
技术点总结
Mantle
的功能主要是进行对象间数据的转换:即如何在一个MTLModel
和一个JSON
字典中进行数据的转换。因此,所使用的技术大都是Cocoa Foundation
提供的功能。除了对于Core Data
的处理之外,主要用到的技术的有如下几条:
KVC
的应用:这主要体现在对MTLModel
子类的属性赋值中,通过KVC
机制来验证值的有效性并为属性赋值。NSValueTransform
:这主要用于对JSON
值转换为属性值的处理,我们可以自定义转换器来满足我们自己的转换需求。NSInvocation
:这主要用于统一处理针对特定key
值的一些方法的调用。比如-merge<Key>FromModel:
这一类方法。Run time
函数的使用:这主要用于对从一个字符串中获取到方法对应的字符串,然后通过sel_registerName
函数来注册一个selector
。
当然在Mantle
中还会涉及到其它的一些技术点,在此不多做叙述。