前言
Objective-C是一门十分动态的语言。为什么会体现出动态性呢?是因为Objective-C将许多工作放在了运行时去完成,例如Objective-C中的类与对象就是在运行时动态创建和绑定的。因此,理解好Objective-C中的类与对象模型对于理解好Objective-C的运行期系统有很大帮助。而运行期系统又是Objective-C的核心,因此,理解好oc中的类与对象模型对整体理解oc这门语言十分有帮助。
对象
在OC中我们经常会跟对象打交道,那么OC中的对象到底是什么东西有着怎样的结构才使得它能够实现很强的动态性呢?以及OC中还有一个特殊的类型id
,它能指代任意的OC对象类型,它又是如何实现的呢?我们常说OC本身知道一个对象到底是属于什么类型的,这又是为什么呢?
要想搞清楚这些问题,最好的方法就是查看OC的运行时源代码了。从Apple Open Source下载好最新的源代码(我下载的是objc-647)用Xcode打开后,在objc.h
中有这么几句:
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;
这么几句已经很能说明问题了,首先从这几句代码中我们知道Class
类型其实是一个指针,指向一个objc_class
的结构体。id
类型也是一个指针,指向一个objc_object
的结构体。而objc_object
这个结构题就是OC中对象的定义,可以看到里面只有一个Class
类型的isa
属性,用于指明对象所属的类型。
看过这几行代码就应该明白了为啥id
类型能够指代任意的OC对象类型了。因为id
其实就是一个指向对象的指针。也应该明白了为啥OC本身知道一个对象到底是属于什么类型的,因为对象本身有个isa
属性来描述其类型啊。
不过需要注意一点
上面的实现是旧版本的实现,在Objective-C 2.0中新的实现的细节已经不同了,但是大体上对象还是这么个结构
为什么这么说,看在OC2.0中的objc_object
的定义
在objc-private.h
中
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*indexed=false*/);
void initClassIsa(Class cls /*indexed=maybe*/);
void initProtocolIsa(Class cls /*indexed=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasIndexedIsa();
bool isTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool indexed, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
bool overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Unified retain count manipulation for nonpointer isa
id rootRetain(bool tryRetain, bool handleOverflow);
bool rootRelease(bool performDealloc, bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
bool rootRelease_underflow(bool performDealloc);
void clearDeallocating_weak();
// Side table retain count overflow for nonpointer isa
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
bool sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable *table);
bool sidetable_release(bool performDealloc = true);
bool sidetable_release_slow(SideTable *table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if !NDEBUG
bool sidetable_present();
#endif
};
新的实现是用c++写的,其中isa
属性的类型也不再是Class
,而是一个isa_t
类型,这个isa_t
类型的定义如下:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
......
定义很长,只贴比较关键的部分。
这里的isa_t
类型不再是一个指针,而直接是一个联合体,但其中有个属性Class cls
,这就相当于原先的Class isa
。
因此说原先的实现与新的实现总体结构上是一样,但是具体底层细节不一样。因此按照原先的实现去理解对象模型比较好理解。而要具体深究底层细节的话还有很多技术需要搞懂。
类对象
继续看类的定义。
在runtime.h
中
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;
/* Use `Class` instead of `struct objc_class *` */
同样,这也是旧版本的实现,在OC 2.0中的实现太负责不好理解,但是类中的这些属性还是有的。
可以看到类的定义中同样有一个Class isa
属性,以及其余的包括父类、名称、实例变量列表、方法列表、方法缓存列表、协议列表等等属性。
在这里最关键的就是这个isa
属性。它说明了在OC中类也是一个对象。
要说类也是一个对象其实看OC2.0的实现比较好理解
在objc-runtime-new.h
中
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
....
同样是用c++写的。定义也很长,下面都是一些方法,在这里不重要,就不贴了。
在上面的代码中,可以看到objc_class
是继承自objc_object
的,因此,现在应该很好理解类也是一个对象这种说法了。
OK,继续往下说。既然说类也是一个对象,那么类又是谁的实例呢?也就是说类的isa
属性是指向哪里的呢?
实际上,类的isa
所指向的类叫做元类(metaclass)
,它用来描述类对象所具有的数据。其中类方法就定义在其中。(实例方法定义在类对象也就是objc_class
中)。
那元类有没有isa
属性?其实是有的,元类的isa
属性是指向根元类(root metaclass)
的。
说过来说过去,是不是已经晕了?那就来一张图片来理一下。
现在仔细去看一下Apple文档中的名为
class
的类方法
其方法声明为
+ (Class)class
返回值为
Return Value
The class object
现在应该知道返回的class object
是什么东西了。
查询类型信息
现在,我们知道了一个对象有isa
属性来描述所属的类,类又有superclass
属性来确立继承关系。因此。现在我们就能用类型信息查询方法来查询类型信息了。
其中,isMemberOfClass:
方法能够判断对象是否是某个特定类的实例,而isKindOfClass:
方法能够判断出对象是否是某类或其派生类的实例。例如:
@interface LXYClass : NSObject
@end
LXYClass *lc = [[LXYClass alloc] init];
NSLog(@"%d",[lc isMemberOfClass:[NSObject class]]);
NSLog(@"%d",[lc isMemberOfClass:[LXYClass class]]);
NSLog(@"%d",[lc isKindOfClass:[NSObject class]]);
NSLog(@"%d",[lc isKindOfClass:[NSString class]]);
输出结果
2015-09-20 15:57:18.329 fkjflsa[1096:51160] 0
2015-09-20 15:57:18.329 fkjflsa[1096:51160] 1
2015-09-20 15:57:18.329 fkjflsa[1096:51160] 1
2015-09-20 15:57:18.330 fkjflsa[1096:51160] 0
注意:尽量不要使用
isMemberOfClass:
方法,除非是在你自己写的类上
例如如下代码:
NSDictionary *dict = [[NSDictionary alloc] init];
NSLog(@"%d",[dict isMemberOfClass:[NSDictionary class]]);
调用后输出
2015-09-20 16:16:50.641 fkjflsa[1344:60121] 0
明明应该是1,可为什么是0呢?这是因为NSDictionary
类实际上是一个类簇,每次初始化返回的实例类型都是NSDictionary
的子类,在其上调用isMemberOfClass:
方法当然会返回NO
了。
当然,因为每个类的类对象都是一个单例,所以我们还有一种方法可以判断出对象是否为某类的实例,如下
NSLog(@"%d",[lc class] == [LXYClass class]);
但是并不推荐使用这种方法。因为这种方法在遇到例如NSProxy
的类型的时候会失效。
总结
说了这么多,总结一下:
- 对象有一个
isa
属性用来指向其所属的类 - 类也是一个对象,称作类对象。类对象所属的类成为元类。
- 通过
isa
指针和super_class
指针我们可以在集成体系中游走并查询类型信息 - 查询类型信息是小心类簇以及不能直接使用
class
方法所返回的类对象直接比较
当然了,OC的运行时系统同时也提供了相应地动态创建类和实例的函数可供使用。了解了类与对象的模型后我们便可以使用这些函数动态地创建类与对象。这些下次再说。