我们在iOS开发过程中,我们常常需要将字典数据(也就是JSON数据)与Model模型之间的转化,例如网络请求返回的微博数据、等等,如果我们自己全部手动去创建模型并赋值,都是一些毫无技术含量的代码,费时费力,而且还可能会赋值出错,让我们很头疼。
MJExtension
框架就是为了解决这个问题而设计得第三方开源库。这个开源库是之前传智博客的讲师李明杰老师写的,现在他自己出来做了,我iOS入门都是看李明杰老师的培训视频学习的,他讲得非常好,我非常喜欢他,他也算是我的老师了,他的作品我还是要学习下的。
MJExtension
框架是利用Obj-C的运行时机制编写的,现在iOS开发语言往Swift语言发展,我不太清楚Swift语言是否也有这种特性,该框架以后会不会在Swift语言上也发展下去不得而知,不过这个框架很轻量级,非常适合初级开发者去看它的源码,对理解Obj-C的运行时机制有非常大的帮助。
Runtime
简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
OC的函数调用类似于消息发送,属于动态调用过程。在编译的时候并不能决定真正调用哪个函数。事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错。只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
1
2
3
4
|
[obj makeTest];
/* 编译时Runtime会将上面的代码转为下面的消息发送 */
objc_msgSend(obj,
@selector
(makeText));
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Class isa;
};
typedef struct objc_class *Class;
struct objc_class {
Class isa;
// 指向metaclass,也就是静态的Class
Class super_class ;
// 指向其父类
const
char
*name ;
// 类名
long
version ;
// 类的版本信息,初始化默认为0
/* 一些标识信息,如CLS_CLASS(0x1L)表示该类为普通class;
CLS_META(0x2L)表示该类为metaclass */
long
info;
long
instance_size ;
// 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars;
// 用于存储每个成员变量的地址
/* 与info的一些标志位有关,如是普通class则存储对象方法,如是metaclass则存储类方法; */
struct objc_method_list **methodLists ;
struct objc_cache *cache;
// 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols;
// 存储该类遵守的协议
};
|
objc_msgSend
函数的调用过程:cache
中通过SEL查找对应函数
method
若
cache
中未找到,再去
methodLists
中查找 若
methodLists
中未找到,则进入
superClass
按前面的步骤进行递归查找 若找到
method
,则将
method
加入到
cache
中,以方便下次查找,并通过
method
中的函数指针跳转到对应的函数中去执行。 如果一直查找到
NSObject
还没查找到,则会进入消息动态处理流程。
1
2
3
4
5
6
7
8
|
+ (BOOL)resolveInstanceMethod:(SEL)sel;
/* 2. 时机处理之二,在这个方法中看代理能不能处理,如果代理对象能处理,则转接给代理对象 */
- (id)forwardingTargetForSelector:(SEL)aSelector;
/* 3. 消息转发之一,该方法返回方法签名,如果返回nil,则转发流程终止,抛出异常 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
/* 4. 消息转发之二,在该方法中我们可以对调用方法进行重定向 */
- (
void
)forwardInvocation:(NSInvocation *)anInvocation;
|
1
2
3
4
5
6
7
8
9
10
11
12
|
class_addIvar(kclass,
"expression"
, size, alignment,
"*"
);
/* 动态向一个类添加方法 */
class_addMethod(kclass,
@selector
(setExpressionFormula:), (IMP)setExpressionFormula,
"v@:@"
);
class_addMethod(kclass,
@selector
(getExpressionFormula), (IMP)getExpressionFormula,
"@@:"
);
static
void
setExpressionFormula(id self, SEL cmd, id value){
NSLog(@
"call setExpressionFormula"
);
}
static
id getExpressionFormula(id self, SEL cmd){
NSLog(@
"call getExpressionFormula"
);
return
nil;
}
|
v
表示void,
@
表示id类型,
:
表示SEL类型
"v@:@"
:表示返回值为void,接受一个id类型、一个SEL类型、一个id类型的方法
"@@:"
:表示返回值为id类型,接受一个id类型和一个SEL类型参数的方法
具体Runtime运行时使用细节,这里就不细讲,只是简单了解下Runtime是可以做到动态向类添加属性和方法就行。
MJExtension
的大部分方法实现都集成到了分类上,不需要使用新的类,只需要包含头文件MJExtension.h
即可。MJExtension
在github上的使用说明已经写得十分明白了。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
SexMale,
SexFemale
} Sex;
@interface
User : NSObject
@property
(copy, nonatomic) NSString *name;
/* 姓名 */
@property
(copy, nonatomic) NSString *icon;
/* 头像 */
@property
(assign, nonatomic) unsigned
int
age;
/* 年龄 */
@property
(copy, nonatomic) NSString *height;
/* 身高 */
@property
(strong, nonatomic) NSNumber *money;
/* 资产 */
@property
(assign, nonatomic) Sex sex;
/* 性别 */
@property
(assign, nonatomic, getter=isGay) BOOL gay;
/* 是否是同性恋 */
@end
|
1
2
3
4
5
6
7
8
9
10
11
|
@
"name"
: @
"Jack"
,
@
"icon"
: @
"lufy.png"
,
@
"age"
:
@20
,
@
"height"
: @
"1.55"
,
@
"money"
:
@100
.9,
@
"sex"
: @(SexFemale),
/* 枚举需要使用NSNumber包装 */
@
"gay"
: @
"NO"
};
//字典转模型,使用的是mj_objectWithKeyValues:方法
User *user = [User mj_objectWithKeyValues:dict];
|
1
2
3
4
5
|
NSString*jsonString = @
"{\"name\":\"Jack\", \"icon\":\"lufy.png\", \"age\":20}"
;
// JSON字符串转模型
User *user = [User mj_objectWithKeyValues:jsonString];
|
1
2
3
4
5
|
@property
(copy, nonatomic) NSString*text;
@property
(strong, nonatomic) User *user;
/* 其他模型类型 */
@property
(strong, nonatomic) Status *retweetedStatus;
/* 自我模型类型 */
@end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@
"text"
: @
"Agree!Nice weather!"
,
@
"user"
: @{
@
"name"
: @
"Jack"
,
@
"icon"
: @
"lufy.png"
},
@
"retweetedStatus"
: @{
@
"text"
: @
"Nice weather!"
,
@
"user"
: @{
@
"name"
: @
"Rose"
,
@
"icon"
: @
"nami.png"
}
}
};
//字典转模型,模型里面含有模型
Status *status = [Status mj_objectWithKeyValues:dict];
NSString *text = status.text;
NSString *name = status.user.name;
NSString *icon = status.user.icon;
NSLog(@
"text=%@, name=%@, icon=%@"
, text, name, icon);
// text=Agree!Nice weather!, name=Jack, icon=lufy.png
NSString *text2 = status.retweetedStatus.text;
NSString *name2 = status.retweetedStatus.user.name;
NSString *icon2 = status.retweetedStatus.user.icon;
NSLog(@
"text2=%@, name2=%@, icon2=%@"
, text2, name2, icon2);
// text2=Nice weather!, name2=Rose, icon2=nami.png
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@property
(copy, nonatomic) NSString*image;
@property
(copy, nonatomic) NSString*url;
@end
@interfaceStatusResult
: NSObject
/** 数组中存储模型Status类型数据 */
@property
(strong, nonatomic) NSMutableArray*statuses;
/** 数组中存储模型Ad类型数据 */
@property
(strong, nonatomic) NSArray*ads;
@property
(strong, nonatomic) NSNumber*totalNumber;
@end
#
import
"MJExtension.h"
/* 数组中存储模型数据,需要说明数组中存储的模型数据类型 */
@implementation
StatusResult
/* 实现该方法,说明数组中存储的模型数据类型 */
+ (NSDictionary *)mj_ objectClassInArray{
return
@{ @
"statuses"
: @
"Status"
,
@
"ads"
: @
"Ad"
};
}
@end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
@
"statuses"
: @[
@{
@
"text"
: @
"Nice weather!"
,
@
"user"
: @{
@
"name"
: @
"Rose"
,
@
"icon"
: @
"nami.png"
}
},
@{
@
"text"
: @
"Go camping tomorrow!"
,
@
"user"
: @{
@
"name"
: @
"Jack"
,
@
"icon"
: @
"lufy.png"
}
}
],
@
"ads"
: @[
@{
@
"image"
: @
"ad01.png"
,
@
"url"
: @
"http://www.ad01.com"
},
@{
@
"image"
: @
"ad02.png"
,
@
"url"
: @
"http://www.ad02.com"
}
],
@
"totalNumber"
: @
"2014"
};
//字典转模型,支持模型的数组属性里面又装着模型
StatusResult *result = [StatusResult mj_objectWithKeyValues:dict];
//打印博主信息
for
(Status *status in result.statuses) {
NSString *text = status.text;
NSString *name = status.user.name;
NSString *icon = status.user.icon;
NSLog(@
"text=%@, name=%@, icon=%@"
, text, name, icon);
}
// text=Nice weather!, name=Rose, icon=nami.png
// text=Go camping tomorrow!, name=Jack, icon=lufy.png
//打印广告
for
(Ad *ad in result.ads) {
NSLog(@
"image=%@, url=%@"
, ad.image, ad.url);
}
// image=ad01.png, url=http://www.ad01.com
// image=ad02.png, url=http://www.ad02.com
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@property
(copy, nonatomic) NSString *name;
@property
(assign, nonatomic)
double
price;
@end
@interface
Student : NSObject
@property
(copy, nonatomic) NSString *ID;
@property
(copy, nonatomic) NSString *desc;
@property
(copy, nonatomic) NSString *nowName;
@property
(copy, nonatomic) NSString *oldName;
@property
(copy, nonatomic) NSString *nameChangedTime;
@property
(strong, nonatomic) Bag *bag;
@end
#
import
"MJExtension.h"
@implementation
/* 设置模型属性名和字典key之间的映射关系 */
+ (NSDictionary *)mj_replacedKeyFromPropertyName{
/* 返回的字典,key为模型属性名,value为转化的字典的多级key */
return
@{
@
"ID"
: @
"id"
,
@
"desc"
: @
"desciption"
,
@
"oldName"
: @
"name.oldName"
,
@
"nowName"
: @
"name.newName"
,
@
"nameChangedTime"
: @
"name.info[1].nameChangedTime"
,
@
"bag"
: @
"other.bag"
};
}
@end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@
"id"
: @
"20"
,
@
"desciption"
: @
"kids"
,
@
"name"
: @{
@
"newName"
: @
"lufy"
,
@
"oldName"
: @
"kitty"
,
@
"info"
: @[
@
"test-data"
,
@{
@
"nameChangedTime"
: @
"2013-08"
}
]
},
@
"other"
: @{
@
"bag"
: @{
@
"name"
: @
"a red bag"
,
@
"price"
:
@100
.7
}
}
};
//字典转模型,支持多级映射
Student *stu = [Student mj_objectWithKeyValues:dict];
//打印
NSLog(@
"ID=%@, desc=%@, oldName=%@, nowName=%@, nameChangedTime=%@"
,
stu.ID, stu.desc, stu.oldName, stu.nowName, stu.nameChangedTime);
// ID=20, desc=kids, oldName=kitty, nowName=lufy, nameChangedTime=2013-08
NSLog(@
"bagName=%@, bagPrice=%f"
, stu.bag.name, stu.bag.price);
// bagName=a red bag, bagPrice=100.700000
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@{
@
"name"
: @
"Jack"
,
@
"icon"
: @
"lufy.png"
},
@{
@
"name"
: @
"Rose"
,
@
"icon"
: @
"nami.png"
}
];
//字典数组转模型数组,使用的是mj_objectArrayWithKeyValuesArray:方法
NSArray *userArray = [User mj_objectArrayWithKeyValuesArray:dictArray];
//打印
for
(User *user in userArray) {
NSLog(@
"name=%@, icon=%@"
, user.name, user.icon);
}
// name=Jack, icon=lufy.png
// name=Rose, icon=nami.png
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
User *user = [[User alloc] init];
user.name = @
"Jack"
;
user.icon = @
"lufy.png"
;
Status *status = [[Status alloc] init];
status.user = user;
status.text = @
"Nice mood!"
;
//模型转字典,使用的是mj_keyValues属性
NSDictionary*statusDict = status.mj_keyValues;
NSLog(@
"%@"
, statusDict);
/*
{
text = "Nice mood!";
user = {
icon = "lufy.png";
name = Jack;
};
}
*/
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
User *user1 = [[User alloc] init];
user1.name = @
"Jack"
;
user1.icon = @
"lufy.png"
;
User *user2 = [[User alloc] init];
user2.name = @
"Rose"
;
user2.icon = @
"nami.png"
;
NSArray *userArray = @[user1, user2];
//模型数组转字典数组,使用的是mj_keyValuesArrayWithObjectArray:方法
NSArray *dictArray = [User mj_keyValuesArrayWithObjectArray:userArray];
NSLog(@
"%@"
, dictArray);
/*
(
{
icon = "lufy.png";
name = Jack;
},
{
icon = "nami.png";
name = Rose;
}
)
*/
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@
"name"
: @
"Jack"
,
@
"icon"
: @
"lufy.png"
,
@
"age"
:
@20
,
@
"height"
:
@1
.55,
@
"money"
: @
"100.9"
,
@
"sex"
: @(SexFemale),
|