Objective-C对象模型及应用
Posted pengyuan_D
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Objective-C对象模型及应用相关的知识,希望对你有一定的参考价值。
前言
原创文章,转载请注明出自唐巧的技术博客。
本文主要介绍Objective-C对象模型的实现细节,以及Objective-C语言对象模型中对isa swizzling
和method swizzling
的支持。希望本文能加深你对Objective-C对象的理解。
ISA指针
Objective-C是一门面向对象的编程语言。每一个对象都是一个类的实例。在Objective-C语言的内部,每一个对象都有一个名为isa的指针,指向该对象的类。每一个类描述了一系列它的实例的特点,包括成员变量的列表,成员函数的列表等。每一个对象都可以接受消息,而对象能够接收的消息列表是保存在它所对应的类中。
在XCode中按Shift + Command + O
, 然后输入NSObject.h和objc.h,可以打开NSObject的定义头文件,通过头文件我们可以看到,NSObject就是一个包含isa指针的结构体,如下图所示:
按照面向对象语言的设计原则,所有事物都应该是对象(严格来说Objective-C并没有完全做到这一点,因为它有象int, double这样的简单变量类型)。在Objective-C语言中,每一个类实际上也是一个对象。每一个类也有一个名为isa的指针。每一个类也可以接受消息,例如[NSObject alloc]
,就是向NSObject这个类发送名为alloc
消息。
在XCode中按Shift + Command + O
, 然后输入runtime.h,可以打开Class的定义头文件,通过头文件我们可以看到,Class也是一个包含isa指针的结构体,如下图所示。(图中除了isa外还有其它成员变量,但那是为了兼容非2.0版的Objective-C的遗留逻辑,大家可以忽略它。)
因为类也是一个对象,那它也必须是另一个类的实列,这个类就是元类(metaclass
)。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。
元类(metaclass
)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass
)。根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面提到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
我们再来看看继承关系,由于类方法的定义是保存在元类(metaclass
)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以,为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
我很想把关系说清楚一些,但是这块儿确实有点绕,下面这张图或许能够让大家对isa和继承的关系清楚一些(该图片来自这里)
该图中,最让人困惑的莫过于Root Class了。在实现中,Root Class是指NSObject,我们可以从图中看出:
- NSObject类包括它的对象实例方法。
- NSObject的元类包括它的类方法,例如alloc方法。
- NSObject的元类继承自NSObject类。
- 一个NSObject的类中的方法同时也会被NSObject的子类在查找方法时找到。
类的成员变量
如果把类的实例看成一个C语言的结构体(struct),上面说的isa指针就是这个结构体的第一个成员变量,而类的其它成员变量依次排列在结构体中。排列顺序如下图所示(图片来自《ios 6 Programming Pushing the Limits》):
为了验证该说法,我们在XCode中新建一个工程,在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 | |
我们将断点下在 @autoreleasepool
处,然后在Console中输入p *child
,则可以看到Xcode输出如下内容,这与我们上面的说法一致。
1 2 3 4 5 6 7 8 9 10 | |
可变与不可变
因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时动态给对象增加成员变量。
相对的,对象的方法定义都保存在类的可变区域中。Objective-C 2.0并未在头文件中将实现暴露出来,但在Objective-C 1.0中,我们可以看到方法的定义列表是一个名为 methodLists
的指针的指针(如下图所示)。通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。这也是Category
实现的原理。同时也说明了为什么Category
只可为对象增加成员方法,却不能增加成员变量。
需要特别说明一下,通过objc_setAssociatedObject
和 objc_getAssociatedObject
方法可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构。
除了对象的方法可以动态修改,因为isa本身也只是一个指针,所以我们也可以在运行时动态地修改isa指针的值,达到替换对象整个行为的目的。不过该应用场景较少。
系统相关API及应用
isa swizzling的应用
系统提供的KVO的实现,就利用了动态地修改isa指针的值的技术。在苹果的文档中可以看到如下描述:
Key-Value Observing Implementation Details
Automatic key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.
类似的,使用isa swizzling的技术的还有系统提供的Key-Value Coding(KVC)。 (谢谢大家指出错误,KVC并没有使用到isa swizzling)
Method Swizzling API说明
Objective-C提供了以下API来动态替换类方法或实例方法的实现:
-
class_replaceMethod
替换类方法的定义 -
method_exchangeImplementations
交换2个方法的实现 -
method_setImplementation
设置1个方法的实现
这3个方法有一些细微的差别,给大家介绍如下:
-
class_replaceMethod
在苹果的文档(如下图所示)中能看到,它有两种不同的行为。当类中没有想替换的原方法时,该方法会调用class_addMethod
来为该类增加一个新方法,也因为如此,class_replaceMethod
在调用时需要传入types
参数,而method_exchangeImplementations
和method_setImplementation
却不需要。
Java 内存模型及GC原理