一、前言
如果你没有Objective-C基础,请学习了基础的iOS开发再来,这个1小时是给有一定iOS基础的童鞋的。如果你是大牛或者你感觉Objective-C Runtime太简单不用1小时学习的,也请您绕道,这或许只是我的私人笔记了。
请跟着教程“一步步来”,请不要大概地扫两眼就说看不懂——以这种态度写成什么样你也看不懂。这是1小时入门教程,请不要试图在1分钟内入门!
二、本文目标
1小时让你知道什么是Objective-C Runtime,并对它有一定的基本了解,可以在开发过程中运用自如。
三、Objective-C Runtime到底是什么东西?
简而言之,Objective-C Runtime是一个将C语言转化为面向对象语言的扩展。
我们将C++和Objective进行对比,虽然C++和Objective-C都是在C的基础上加入面向对象的特性扩充而成的程序设计语言,但二者实现的机制差异很大。C++是基于静态类型,而Objective-C是基于动态运行时类型。也就是说用C++编写的程序编译时就直接编译成了可令机器读懂的机器语言;用Objective-C编写的程序不能直接编译成可令机器读懂的机器语言,而是在程序运行的时候,通过Runtime把程序转为可令机器读懂的机器语言。Runtime是Objective不可缺少的重要一部分。
传送门->runtime源码
四、Objective-C的元素认知
4.1 id和Class
打开/Public Headers/objc.h文件可以看到如下定义:
1
2
3
4
5
6
7
8
9
10
11
12
|
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef
struct
objc_class *
Class
;
/// 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
;
#endif
|
Class是一个指向objc_class结构体的指针,而id是一个指向objc_object结构体的指针,其中的isa是一个指向objc_class结构体的指针。其中的id就是我们所说的对象,Class就是我们所说的类。
打开/Public Headers/runtime.h文件
objc_class的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
typedef
struct
objc_class *
Class
;
struct
objc_class
{
Class
isa
OBJC_ISA_AVAILABILITY
;
// metaclass
#if !__OBJC2__
Class
super_class
OBJC2_UNAVAILABLE
;
// 父类
const
char
*
name
OBJC2_UNAVAILABLE
;
// 类名
long
version
OBJC2_UNAVAILABLE
;
// 类的版本信息,默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
long
info
OBJC2_UNAVAILABLE
;
// 类信息,供运行时期使用的一些位标识,如CLS_CLASS (0x1L) 表示该类为普通 class,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long
instance_size
OBJC2_UNAVAILABLE
;
// 该类的实例变量大小(包括从父类继承下来的实例变量)
struct
objc_ivar_list *
ivars
OBJC2_UNAVAILABLE
;
// 该类的成员变量地址列表
struct
objc_method_list *
*
methodLists
OBJC2_UNAVAILABLE
;
// 方法地址列表,与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
struct
objc_cache *
cache
OBJC2_UNAVAILABLE
;
// 缓存最近使用的方法地址,用于提升效率;
struct
objc_protocol_list *
protocols
OBJC2_UNAVAILABLE
;
// 存储该类声明遵守的协议的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */
|
由以上代码可见,类与对象的区别就是类比对象多了很多特征成员,类也可以当做一个objc_object来对待,也就是说类和对象都是对象,分别称作类对象(class object)和实例对象(instance object),这样我们就可以区别对象和类了。
isa:objc_object(实例对象)中isa指针指向的类结构称为class(也就是该对象所属的类)其中存放着普通成员变量与动态方法(“-”开头的方法);此处isa指针指向的类结构称为metaclass,其中存放着static类型的成员变量与static类型的方法(“+”开头的方法)。
super_class: 指向该类的父类的指针,如果该类是根类(如NSObject或NSProxy),那么super_class就为nil。
类与对象的继承层次关系如图(图片源自网络):
所有的metaclass中isa指针都是指向根metaclass,而根metaclass则指向自身。根metaclass是通过继承根类产生的,与根class结构体成员一致,不同的是根metaclass的isa指针指向自身。
4.2 SEL
SEL是selector在Objective-C中的表示类型。selector可以理解为区别方法的ID。
1
|
typedef
struct
objc_selector *
SEL
;
|
objc_selector的定义如下:
1
2
3
4
|
struct
objc_selector
{
char
*
name
;
OBJC2_UNAVAILABLE
;
// 名称
char
*
types
;
OBJC2_UNAVAILABLE
;
// 类型
}
;
|
name和types都是char类型。
4.3 IMP
终于到IMP了,它在objc.h中得定义如下:
1
|
typedef
id
(
*
IMP
)
(
id
,
SEL
,
.
.
.
)
;
|
IMP是“implementation”的缩写,它是由编译器生成的一个函数指针。当你发起一个消息后(下文介绍),这个函数指针决定了最终执行哪段代码。
4.4 Method
Method代表类中的某个方法的类型。
1
|
typedef
struct
objc_method *
Method
;
|
objc_method的定义如下:
1
2
3
4
5
|
struct
objc_method
{
SEL
method_name
OBJC2_UNAVAILABLE
;
// 方法名
char
*
method_types
OBJC2_UNAVAILABLE
;
// 方法类型
IMP
method_imp
OBJC2_UNAVAILABLE
;
// 方法实现
}
|
方法名method_name类型为SEL,上文提到过。
方法类型method_types是一个char指针,存储着方法的参数类型和返回值类型。
方法实现method_imp的类型为IMP,上文提到过。
4.5 Ivar
Ivar代表类中实例变量的类型
1
|
typedef
struct
objc_ivar *
Ivar
;
|
objc_ivar的定义如下:
1
2
3
4
5
6
7
8
|
struct
objc_ivar
{
char
*
ivar_name
OBJC2_UNAVAILABLE
;
// 变量名
char
*
ivar_type
OBJC2_UNAVAILABLE
;
// 变量类型
int
ivar_offset
OBJC2_UNAVAILABLE
;
// 基地址偏移字节
#ifdef __LP64__
int
space
OBJC2_UNAVAILABLE
;
// 占用空间
#endif
}
|
4.6 objc_property_t
objc_property_t是属性,它的定义如下:
1
|
typedef
struct
objc_property *
objc_property_t
;
|
objc_property是内置的类型,与之关联的还有一个objc_property_attribute_t,它是属性的attribute,也就是其实是对属性的详细描述,包括属性名称、属性编码类型、原子类型/非原子类型等。它的定义如下:
1
2
3
4
|
typedef
struct
{
const
char
*
name
;
// 名称
const
char
*
value
;
// 值(通常是空的)
}
objc_property_attribute_t
;
|
4.7 Cache
Catch的定义如下:
1
|
typedef
struct
objc_cache *
Cache
|
objc_cache的定义如下:
1
2
3
4
5
|
struct
objc_cache
{
unsigned
int
mask
OBJC2_UNAVAILABLE
;
unsigned
int
occupied
OBJC2_UNAVAILABLE
;
Method
buckets
[
1
]
OBJC2_UNAVAILABLE
;
}
;
|
mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置。
occupied: 实际占用cache buckets的总数。
buckets: 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。
objc_msgSend(下文讲解)每调用一次方法后,就会把该方法缓存到cache列表中,下次的时候,就直接优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法。
4.8 Catagory
这个就是我们平时所说的类别了,很熟悉吧。它可以动态的为已存在的类添加新的方法。
它的定义如下:
1
|
typedef
struct
objc_category *
Category
;
|
objc_category的定义如下:
1
2
3
4
5
6
7
|
struct
objc_category
{
char
*
category_name
OBJC2_UNAVAILABLE
;
// 类别名称
char
*
class_name
OBJC2_UNAVAILABLE
;
// 类名
struct
objc_method_list *
instance_methods
OBJC2_UNAVAILABLE
;
// 实例方法列表
struct
objc_method_list *
class_methods
OBJC2_UNAVAILABLE
;
// 类方法列表
struct
objc_protocol_list *
protocols
OBJC2_UNAVAILABLE
;
// 协议列表
}
|
因为是入门,以上就列举这些吧!
五、Objective-C的消息传递
5.1 基本消息传递
在面向对象编程中,对象调用方法叫做发送消息。在编译时,程序的源代码就会从对象发送消息转换成Runtime的objc_msgSend函数调用。
例如某实例变量receiver实现某一个方法oneMethod
1
|
[
receiver
oneMethod
]
;
|
Runtime会将其转成类似这样的代码
1
|
objc_msgSend
(
receiver
,
selector
)
;
|
具体会转换成什么代码呢?
Runtime会根据类型自动转换成下列某一个函数:
objc_msgSend:普通的消息都会通过该函数发送
objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值
objc_msgSendSuper:和objc_msgSend类似,这里把消息发送给父类的实例
objc_msgSendSuper_stret:和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值
当消息被发送到实例对象时,是如图所示处理的(图片源自网络):
objc_msgSend函数的调用过程:
- 第一步:检测这个selector是不是要忽略的。
- 第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉。
- 第三步:
1.调用实例方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,如果找不到则会通过class的super_class指针找到父类的类对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根class;
2.当我们调用某个某个类方法时,它会首先通过自己的isa指针找到metaclass,并从其中methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根metaclass; - 第四部:前三部都找不到就会进入动态方法解析(看下文)。
5.2 消息动态解析
动态解析流程图(图片来自网络):
请参照图片品味以下步骤(实例请看下文《6.6 苍老师的唱歌篇》):
- 第一步:通过resolveInstanceMethod:方法决定是否动态添加方法。如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No,则进入下一步;
- 第二步:这步会进入forwardingTargetForSelector:方法,用于指定备选对象响应这个selector,不能指定为self。如果返回某个对象则会调用对象的方法,结束。如果返回nil,则进入第三部;
- 第三部:这步我们要通过methodSignatureForSelector:方法签名,如果返回nil,则消息无法处理。如果返回methodSignature,则进入下一步;
- 第四部:这步调用forwardInvocation:方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector方法,若我们没有实现这个方法,那么就会crash。
到这里大家可能晕乎乎的,下面看实战篇吧!苍老师必须让你懂!
六、Runtime实战
请大家放心,以下所有实战篇,在最后都会分享Demo给大家!
6.1 苍老师问好篇
苍老师见到我们广大的粉丝们,第一反应当然是:大家好!
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
47
48
49
50
51
52
53
|
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
// 自定义一个方法
void
sayFunction
(
id
self
,
SEL
_cmd
,
id
some
)
{
NSLog
(
@
"%@岁的%@说:%@"
,
object_getIvar
(
self
,
class_getInstanceVariable
(
[
self
class
]
,
"_age"
)
)
,
[
self
valueForKey
:
@
"name"
]
,
some
)
;
}
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
// 动态创建对象 创建一个Person 继承自 NSObject类
Class
People
=
objc_allocateClassPair
(
[
NSObject
class
]
,
"Person"
,
0
)
;
// 为该类添加NSString *_name成员变量
class_addIvar
(
People
,
"_name"
,
sizeof
(
NSString*
)
,
log2
(
sizeof
(
NSString*
)
)
,
@
encode
(
NSString*
)
)
;
// 为该类添加int _age成员变量
class_addIvar
(
People
,
"_age"
,
sizeof
(
int
)
,
sizeof
(
int
)
,
@
encode
(
int
)
)
;
// 注册方法名为say的方法
SEL
s
=
sel_registerName
(
"say:"
)
;
// 为该类增加名为say的方法
class_addMethod
(
People
,
s
,
(
IMP
)
sayFunction
,
"v@:@"
)
;
// 注册该类
objc_registerClassPair
(
People
)
;
// 创建一个类的实例
id
peopleInstance
=
[
[
People
alloc
]
init
]
;
// KVC 动态改变 对象peopleInstance 中的实例变量
[
peopleInstance
setValue
:
@
"苍老师"
forKey
:
@
"name"
]
;
// 从类中获取成员变量Ivar
Ivar
ageIvar
=
class_getInstanceVariable
(
People
,
"_age"
)
;
// 为peopleInstance的成员变量赋值
object_setIvar
(
peopleInstance
,
ageIvar
,
@
18
)
;
// 调用 peopleInstance 对象中的 s 方法选择器对于的方法
// objc_msgSend(peopleInstance, s, @"大家好!"); // 这样写也可以,请看我博客说明
(
(
void
(
*
)
(
id
,
SEL
,
id
)
)
objc_msgSend
)
(
peopleInstance
,
s
,
@
"大家好"
)
;
peopleInstance
=
nil
;
//当People类或者它的子类的实例还存在,则不能调用objc_disposeClassPair这个方法;因此这里要先销毁实例对象后才能销毁类;
// 销毁类
objc_disposeClassPair
(
People
)
;
}
return
0
;
}
|
最后的结果是:18岁的苍老师说:大家好!
在使用
1
|
objc_msgSend
(
peopleInstance
,
s
,
@
"大家好!"
)
;
|
默认会出现以下错误:
objc_msgSend()报错Too many arguments to function call ,expected 0,have3
直接通过objc_msgSend(self, setter, value)是报错,说参数过多。
请这样解决:
Build Setting–> Apple LLVM 7.0 – Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改为 NO
当然你也可以这样写(推荐):
1
|
(
(
void
(
*
)
(
id
,
SEL
,
id
)
)
objc_msgSend
)
(
peopleInstance
,
s
,
@
"大家好"
)
;
|
强制转换objc_msgSend函数类型为带三个参数且返回值为void函数,然后才能传三个参数。
此实战内容是,动态创建一个类,并创建成员变量和方法,最后赋值成员变量并发送消息。其中成员变量的赋值使用了KVC和object_setIvar函数两种方式,这些东西大家举一反三就可以了。
Demo传送门->6.1苍老师问好篇Demo
6.2 苍老师的特征篇
苍老师在大家心目中应该有很多特征吧,下面我们通过代码来获取苍老师的特征。
People.h文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@
interface
People
:
NSObject
{
NSString *
_occupation
;
NSString *
_nationality
;
}
@
property
(
nonatomic
,
copy
)
NSString *
name
;
@
property
(
nonatomic
)
NSUInteger
age
;
-
(
NSDictionary *
)
allProperties
;
-
(
NSDictionary *
)
allIvars
;
-
(
NSDictionary *
)
allMethods
;
@
end
|
People.m文件
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation
People
-
(
NSDictionary
*
)
allProperties
{
unsigned
int
count
=
0
;
// 获取类的所有属性,如果没有属性count就为0
objc_property_t
*properties
=
class_copyPropertyList
(
[
self
class
]
,
&
count
)
;
NSMutableDictionary
*resultDict
=
[
<
a
href
=
"http://www.jobbole.com/members/www821839432"
>
@
{
}
<
/
a
>
mutableCopy
]
;
for
(
NSUInteger
i
=
0
;
i
<
count
;
i
++
)
{
// 获取属性的名称和值
const
char
*propertyName
=
property_getName
(
properties
[
i
]
)
;
NSString
*name
=
[
NSString
stringWithUTF8String
:propertyName
]
;
id
propertyValue
=
[
self
valueForKey
:name
]
;
if
(
propertyValue
)
{
resultDict
[
name
]
=
propertyValue
;
}
else
{
resultDict
[
name
]
=
@"字典的key对应的value不能为nil哦!"
;
}
}
// 这里properties是一个数组指针,我们需要使用free函数来释放内存。
free
(
properties
)
;
return
resultDict
;
}
-
(
NSDictionary
*
)
allIvars
{
unsigned
int
count
=
0
;
NSMutableDictionary
*resultDict
=
[
<
a
href
=
"http://www.jobbole.com/members/www821839432"
>
@
{
}
<
/
a
>
mutableCopy
]
;
Ivar
*ivars
=
class_copyIvarList
(
[
self
class
]
,
&
count
)
;
for
(
NSUInteger
i
=
0
;
i
<
count
;
i
++
)
{
const
char
*varName
=
ivar_getName
(
ivars
[
i
]
)
;
NSString
*name
=
[
NSString
stringWithUTF8String
:varName
]
;
id
varValue
=
[
self
valueForKey
:name
]
;
if
(
varValue
)
{
resultDict
[
name
]
=
varValue
;
}
else
{
resultDict
[
name
]
=
@"字典的key对应的value不能为nil哦!"
;
}
}
free
(
ivars
)
;
return
resultDict
;
}
-
(
NSDictionary
*
)
allMethods
{
unsigned
int
count
=
0
;
NSMutableDictionary
*resultDict
=
[
<
a
href
=
"http://www.jobbole.com/members/www821839432"
>
@
{
}
<
/
a
>
mutableCopy
]
;
// 获取类的所有方法,如果没有方法count就为0
Method
*methods
=
class_copyMethodList
(
[
self
class
]
,
&
count
)
;
for
(
NSUInteger
i
=
0
;
i
<
count
;
i
++
)
{
// 获取方法名称
SEL
methodSEL
=
method_getName
(
methods
[
i
]
)
;
const
char
*methodName
=
sel_getName
(
methodSEL
)
;
NSString
*name
=
[
NSString
stringWithUTF8String
:methodName
]
;
// 获取方法的参数列表
int
arguments
=
method_getNumberOfArguments
(
methods
[
i
]
)
;
resultDict
[
name
]
=
@
(
arguments
-
2
)
;
}
free
(
methods
)
;
return
resultDict
;
}
@end
|
在main.m中运行以下代码
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
|
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
People *
cangTeacher
=
[
[
People
alloc
]
init
]
;
cangTeacher
.
name
=
@
"苍井空"
;
cangTeacher
.
age
=
18
;
[
cangTeacher
setValue
:
@
"老师"
forKey
:
@
"occupation"
]
;
NSDictionary *
propertyResultDic
=
[
cangTeacher
allProperties
]
;
for
(
NSString *
propertyName
in
propertyResultDic
.
allKeys
)
{
NSLog
(
@
"propertyName:%@, propertyValue:%@"
,
propertyName
,
propertyResultDic
[
propertyName
]
)
;
}
NSDictionary *
ivarResultDic
=
[
cangTeacher
allIvars
]
;
for
(
NSString *
ivarName
in
ivarResultDic
.
allKeys
)
{
NSLog
(
@
"ivarName:%@, ivarValue:%@"
,
ivarName
,
ivarResultDic
[
ivarName
]
)
;
}
NSDictionary *
methodResultDic
=
[
cangTeacher
allMethods
]
;
for
(
NSString *
methodName
in
methodResultDic
.
allKeys
)
{
NSLog
(
@
"methodName:%@, argumentsCount:%@"
,
methodName
,
methodResultDic
[
methodName
]
)
;
}
}
return
0
;
}
|
最后的输出结果如下:
是不是有点失望,我没有加一些特殊的技能,留给下文了。此实战内容是通过苍老师的一些特征学习:如何获取对象所有的属性名称和属性值、获取对象所有成员变量名称和变量值、获取对象所有的方法名和方法参数数量。
Demo传送门->6.2苍老师的特征篇Demo
6.3 苍老师增加新技能篇
苍老师要通过Category和Associated Objects增加技能了,快看!
创建People+Associated.h文件如下:
1
2
3
4
5
6
7
8
9
10
|
#import "People.h"
typedef
void
(
^
CodingCallBack
)
(
)
;
@
interface
People
(
Associated
)
@
property
(
nonatomic
,
strong
)
NSNumber *
associatedBust
;
// 胸围
@
property
(
nonatomic
,
copy
)
CodingCallBack
associatedCallBack
;
// 写代码
@
end
|
People+Associated.m如下:
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
|
#import "People+Associated.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation
People
(
Associated
)
-
(
void
)
setAssociatedBust
:
(
NSNumber
*
)
bust
{
// 设置关联对象
objc_setAssociatedObject
(
self
,
@selector
(
associatedBust
)
,
bust
,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
;
}
-
(
NSNumber
*
)
associatedBust
{
// 得到关联对象
return
objc_getAssociatedObject
(
self
,
@selector
(
associatedBust
)
)
;
}
-
(
void
)
setAssociatedCallBack
:
(
CodingCallBack
)
callback
{
objc_setAssociatedObject
(
self
,
@selector
(
associatedCallBack
)
,
callback
,
OBJC_ASSOCIATION_COPY_NONATOMIC
)
;
}
-
(
CodingCallBack
)
associatedCallBack
{
return
objc_getAssociatedObject
(
self
,
@selector
(
associatedCallBack
)
)
;
}
@end
|
在main.m中运行以下代码
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
|
#import "People.h"
#import "People+Associated.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
People *
cangTeacher
=
[
[
People
alloc
]
init
]
;
cangTeacher
.
name
=
@
"苍井空"
;
cangTeacher
.
age
=
18
;
[
cangTeacher
setValue
:
@
"老师"
forKey
:
@
"occupation"
]
;
cangTeacher
.
associatedBust
=
@
(
90
)
;
cangTeacher
.
associatedCallBack
=
^
(
)
{
NSLog
(
@
"苍老师要写代码了!"
)
;
}
;
cangTeacher
.
associatedCallBack
(
)
;
NSDictionary *
propertyResultDic
=
[
cangTeacher
allProperties
]
;
for
(
NSString *
propertyName
in
propertyResultDic
.
allKeys
)
{
NSLog
(
@
"propertyName:%@, propertyValue:%@"
,
propertyName
,
propertyResultDic
[
propertyName
]
)
;
}
NSDictionary *
methodResultDic
=
[
cangTeacher
allMethods
]
;
for
(
NSString *
methodName
in
methodResultDic
.
allKeys
)
{
NSLog
(
@
"methodName:%@, argumentsCount:%@"
,
methodName
,
methodResultDic
[
methodName
]
)
;
}
}
return
0
;
}
|
这次运行结果多出了一个associatedBust(胸围)和一个associatedCallBack(写代码)属性。
如图:
我们成功的给苍老师添加个一个胸围的属性和一个写代码的回调,但是添加属性没有什么意义,我们平时在开发过成功中用的比较多的就是添加回调了。
Demo传送门->6.3苍老师增加新技能篇Demo
6.4 苍老师的资料归档篇
苍老师的资料总要整理一下吧!
创建People.h
1
2
3
4
5
6
7
8
9
10
11
|
#import <Foundation/Foundation.h>
@interface
People
: NSObject
<NSCoding>
@property
(
nonatomic
,
copy
)
NSString
*name
;
// 姓名
@property
(
nonatomic
,
strong
)
NSNumber
*age
;
// 年龄
@property
(
nonatomic
,
copy
)
NSString
*occupation
;
// 职业
@property
(
nonatomic
,
copy
)
NSString
*nationality
;
// 国籍
@end
P
|
People.m
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
|
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation
People
-
(
void
)
encodeWithCoder
:
(
NSCoder
*
)
aCoder
{
unsigned
int
count
=
0
;
Ivar
*ivars
=
class_copyIvarList
(
[
People
class
]
,
&
count
)
;
for
(
NSUInteger
i
=
0
;
i
<
count
;
i
++
)
{
Ivar
ivar
=
ivars
[
i
]
;
const
char
*name
=
ivar_getName
(
ivar
)
;
NSString
*key
=
[
NSString
stringWithUTF8String
:name
]
;
id
value
=
[
self
valueForKey
:key
]
;
[
aCoder
encodeObject
:value
forKey
:key
]
;
}
free
(
ivars
)
;
}
-
(
id
)
initWithCoder
:
(
NSCoder
*
)
aDecoder
{
self
=
[
super
init
]
;
if
(
self
)
{
unsigned
int
count
=
0
;
Ivar
*ivars
=
class_copyIvarList
(
[
People
class
]
,
&
count
)
;
for
(
NSUInteger
i
=
0
;
i
<
count
;
i
++
)
{
Ivar
ivar
=
ivars
[
i
]
;
const
char
*name
=
ivar_getName
(
ivar
)
;
NSString
*key
=
[
NSString
stringWithUTF8String
:name
]
;
id
value
=
[
aDecoder
decodeObjectForKey
:key
]
;
[
self
setValue
:value
forKey
:key
]
;
}
free
(
ivars
)
;
}
return
self
;
}
@end
|
Demo传送门->6.4苍老师的资料归档篇Demo
6.5 苍老师的资料转换篇
服务器返回了大量苍老师的数据,手机端这边接收后如何去转换呢?当然是要将JSON转换为Model啦!
相信平时你们的项目中也用到过这些三方库,下面我们去了解下runtime实现JSON和Model互转。
创建People.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#import <Foundation/Foundation.h>
@interface
People
: NSObject
@property
(
nonatomic
,
copy
)
NSString
*name
;
// 姓名
@property
(
nonatomic
,
strong
)
NSNumber
*age
;
// 年龄
@property
(
nonatomic
,
copy
)
NSString
*occupation
;
// 职业
@property
(
nonatomic
,
copy
)
NSString
*nationality
;
// 国籍
// 生成model
-
(
instancetype
)
initWithDictionary
:
(
NSDictionary
*
)
dictionary
;
// 转换成字典
-
(
NSDictionary
*
)
covertToDictionary
;
@end
|
People.m的代码如下:
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation
People
-
(
instancetype
)
initWithDictionary
:
(
NSDictionary
*
)
dictionary
{
self
=
[
super
init
]
;
if
(
self
)
{
for
(
NSString
*key
in
dictionary
.
allKeys
)
{
id
value
=
dictionary
[
key
]
;
SEL
setter
=
[
self
propertySetterByKey
:key
]
;
if
(
setter
)
{
// 这里还可以使用NSInvocation或者method_invoke,不再继续深究了,有兴趣google。
(
(
void
(
*
)
(
id
,
SEL
,
id
)
)
objc_msgSend
)
(
self
,
setter
,
value
)
;
}
}
}
return
self
;
}
-
(
NSDictionary
*
)
covertToDictionary
{
unsigned
int
count
=
0
;
objc_property_t
*properties
=
class_copyPropertyList
(
[
self
class
]
,
&
count
)
;
if
(
count
!=
0
)
{
NSMutableDictionary
*resultDict
=
[
<
a
href
=
"http://www.jobbole.com/members/www821839432"
>
@
{
}
<
/
a
>
mutableCopy
]
;
for
(
NSUInteger
i
=
0
;
i
<
count
;
i
++
)
{
const
void
*propertyName
=
property_getName
(
properties
[
i
]
)
;
NSString
*name
=
[
NSString
stringWithUTF8String
:propertyName
]
;
SEL
getter
=
[
self
propertyGetterByKey
:name
]
;
if
(
getter
)
{
id
value
=
(
(
id
(
*
)
(
id
,
SEL
)
)
objc_msgSend
)
(
self
,
getter
)
;
if
(
value
)
{
resultDict
[
name
]
=
value
;
}
else
{
resultDict
[
name
]
=
@"字典的key对应的value不能为nil哦!"
;
}
}
}
free
(
properties
)
;
return
resultDict
;
}
free
(
properties
)
;
return
nil
;
}
#pragma mark - private methods
// 生成setter方法
-
(
SEL
)
propertySetterByKey
:
(
NSString
*
)
key
{
// 首字母大写,你懂得
NSString
*propertySetterName
=
[
NSString
stringWithFormat
:
@"set%@:"
,
key
.
capitalizedString
]
;
SEL
setter
=
NSSelectorFromString
(
propertySetterName
)
;
if
(
[
self
respondsToSelector
:setter
]
)
{
return
setter
;
}
return
nil
;
}
// 生成getter方法
-
(
SEL
)
propertyGetterByKey
:
(
NSString
*
)
key
{
SEL
getter
=
NSSelectorFromString
(
key
)
;
if
(
[
self
respondsToSelector
:getter
]
)
{
return
getter
;
}
return
nil
;
}
@end
|
main.m中运行以下代码:
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
|
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
NSDictionary
*dict
=
@
{
@"name"
:
@"苍井空"
,
@"age"
:
@
18
,
@"occupation"
:
@"老师"
,
@"nationality"
:
@"日本"
}
;
// 字典转模型
People
*cangTeacher
=
[
[
People
alloc
]
initWithDictionary
:dict
]
;
NSLog
(
@"热烈欢迎,从%@远道而来的%@岁的%@%@"
,
cangTeacher
.
nationality
,
cangTeacher
.
age
,
cangTeacher
.
name
,
cangTeacher
.
occupation
)
;
// 模型转字典
NSDictionary
*covertedDict
=
[
cangTeacher
covertToDictionary
]
;
NSLog
(
@"%@"
,
covertedDict
)
;
}
return
0
;
}
|
最后输出内容如下:
相信通过前面的学习,这些代码不用写过多的注释你也可以看懂了,我把假设是网络返回的苍老师的资料转化为了model,然后又将model转回字典。这是一个JSON转Model相互转换的一个思路,大家稍后运行Demo细细品味。
Demo传送门->6.5苍老师的资料转换篇Demo
6.6 苍老师的唱歌篇
这个实例主要是验证一下上文《5.2 消息动态解析》
第一首:
添加sing实例方法,但是不提供方法的实现。验证当找不到方法的实现时,动态添加方法。
创建People.h
1
2
3
4
5
6
7
8
9
|
#import <Foundation/Foundation.h>
@interface
People
: NSObject
@property
(
nonatomic
,
copy
)
NSString
*name
;
-
(
void
)
sing
;
@end
|
创建People.m
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
|
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation
People
+
(
BOOL
)
resolveInstanceMethod
:
(
SEL
)
sel
{
// 我们没有给People类声明sing方法,我们这里动态添加方法
if
(
[
NSStringFromSelector
(
sel
)
isEqualToString
:
@"sing"
]
)
{
class_addMethod
(
self
,
sel
,
(
IMP
)
otherSing
,
"v@:"
)
;
return
YES
;
}
return
[
super
resolveInstanceMethod
:sel
]
;
}
void
otherSing
(
id
self
,
SEL
cmd
)
{
NSLog
(
@"%@ 唱歌啦!"
,
(
(
People
*
)
self
)
.
name
)
;
}
|
在main.m中运行以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
People
*cangTeacher
=
[
[
People
alloc
]
init
]
;
cangTeacher
.
name
=
@"苍老师"
;
[
cangTeacher
sing
]
;
}
return
0
;
}
|
结果如下:
我们没有提供苍老师唱歌的方法实现,因此在调用此方法的时候,会调用resolveInstanceMethod方法,我们动态添加了方法。我们也可以返回No,继续向下传递。(此处请返回《5.2 消息动态解析》第一步品味下)
Demo传送门->6.6苍老师唱歌篇(第一首)Demo
第二首
外面的小鸟在唱歌,但是苍老师的歌声盖过了小鸟,我们只能听到苍老师唱歌了。
这里我们不声明sing方法,将调用途中动态更换调用对象。
在第一首代码的基础上,创建Bird的model
Bird.h
1
2
3
4
5
6
7
|
#import <Foundation/Foundation.h>
@
interface
Bird
:
NSObject
@
property
(
nonatomic
,
copy
)
NSString *
name
;
@
end
|
Bird.m
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
|
#import "Bird.h"
#import "People.h"
@implementation
Bird
// 第一步:我们不动态添加方法,返回NO,进入第二步;
+
(
BOOL
)
resolveInstanceMethod
:
(
SEL
)
sel
{
return
NO
;
}
// 第二部:我们不指定备选对象响应aSelector,进入第三步;
-
(
id
)
forwardingTargetForSelector
:
(
SEL
)
aSelector
{
return
nil
;
}
// 第三步:返回方法选择器,然后进入第四部;
-
(
NSMethodSignature
*
)
methodSignatureForSelector
:
(
SEL
)
aSelector
{
if
(
[
NSStringFromSelector
(
aSelector
)
isEqualToString
:
@"sing"
]
)
{
return
[
NSMethodSignature
signatureWithObjCTypes
:
"v@:"
]
;
}
return
[
super
methodSignatureForSelector
:aSelector
]
;
}
// 第四部:这步我们修改调用对象
-
(
void
)
forwardInvocation
:
(
NSInvocation
*
)
anInvocation
{
// 我们改变调用对象为People
People
*cangTeacher
=
[
[
People
alloc
]
init
]
;
cangTeacher
.
name
=
@"苍老师"
;
[
anInvocation
invokeWithTarget
:cangTeacher
]
;
}
@end
|
main.m运行代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
#import "Bird.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
Bird
*bird
=
[
[
Bird
alloc
]
init
]
;
bird
.
name
=
@"小小鸟"
;
(
(
void
(
*
)
(
id
,
SEL
)
)
objc_msgSend
)
(
(
id
)
bird
,
@selector
(
sing
)
)
;
}
return
0
;
}
|
运行结果如下:
成功更换了对象,把对象更换为苍老师了。(此处请返回《5.2 消息动态解析》品味)
Demo传送门->6.6苍老师唱歌篇(第二首)Demo
第三首
苍老师不想唱歌想跳舞了。
这里我是实现不提供声明,不修改调用对象,但是将sing方法修改为dance方法。
创建People.h
1
2
3
4
5
|
#import <Foundation/Foundation.h>
@
interface
People
:
NSObject
@
end
|
People.m
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
47
48
49
50
51
52
53
|
#import "People.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation
People
// 第一步:我们不动态添加方法,返回NO,进入第二步;
+
(
BOOL
)
resolveInstanceMethod
:
(
SEL
)
sel
{
return
NO
;
}
// 第二部:我们不指定备选对象响应aSelector,进入第三步;
-
(
id
)
forwardingTargetForSelector
:
(
SEL
)
aSelector
{
return
nil
;
}
// 第三步:返回方法选择器,然后进入第四部;
-
(
NSMethodSignature
*
)
methodSignatureForSelector
:
(
SEL
)
aSelector
{
if
(
[
NSStringFromSelector
(
aSelector
)
isEqualToString
:
@"sing"
]
)
{
return
[
NSMethodSignature
signatureWithObjCTypes
:
"v@:"
]
;
}
return
[
super
methodSignatureForSelector
:aSelector
]
;
}
// 第四部:这步我们修改调用方法
-
(
void
)
forwardInvocation
:
(
NSInvocation
*
)
anInvocation
{
[
anInvocation
setSelector
:
@selector
(
dance
)
]
;
// 这还要指定是哪个对象的方法
[
anInvocation
invokeWithTarget
:self
]
;
}
// 若forwardInvocation没有实现,则会调用此方法
-
(
void
)
doesNotRecognizeSelector
:
(
SEL
)
aSelector
{
NSLog
(
@"消息无法处理:%@"
,
NSStringFromSelector
(
aSelector
)
)
;
}
-
(
void
)
dance
{
NSLog
(
@"跳舞!!!come on!"
)
;
}
@end
|
在main.m中运行如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#import <Foundation/Foundation.h>
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
#import "People.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
People *
cangTeacher
=
[
[
People
alloc
]
init
]
;
(
(
void
(
*
)
(
id
,
SEL
)
)
objc_msgSend
)
(
(
id
)
cangTeacher
,
@
selector
(
sing
)
)
;
}
return
0
;
}
|
结果如图:
成功更换了方法,苍老师由唱歌改为跳舞了(此处请返回《5.2 消息动态解析》品味)
Demo传送门->6.6苍老师唱歌篇(第三首)Demo
总结
好吧,我承认我骗了你,当你读到这里你肯定花了不止1小时。都是我的错,不是因为你笨,之所以说1小时是为了让你有信心,有耐心继续下去。读到这里恭喜你已经在iOS开发的道路上又向前了一步!同时我也要感谢以下参考文献以及文章,是他们让我更好的理解了runtime,再次表示感谢!这篇文章断断续续写了将近一周的时间,您可以读到这里就是对我最大的鼓励,谢谢!
Demo传送门->所有的Demo打包下载
本文参考文献以及文章:
Objective-C Runtime Reference
Object Model
初识Objective-C Runtime
Objective-C Runtime
Objective-C Runtime 运行时之一:类与对象
Runtime Message Forwarding
runtime模型与字典互转
iOS开发之深入探讨runtime机制
Objective-C runtime之消息(二)
Objective-C runtime之消息转发机制(三)
《编写高质量代码:改善Objective-C程序的61个建议》
《正则表达式30分钟入门教程》(参考写作方式)
from:http://ios.jobbole.com/84028/