理解Objective-C中的类与对象

前言

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)的。

说过来说过去,是不是已经晕了?那就来一张图片来理一下。

object model

现在仔细去看一下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的类型的时候会失效。

总结

说了这么多,总结一下:

  1. 对象有一个isa属性用来指向其所属的类
  2. 类也是一个对象,称作类对象。类对象所属的类成为元类。
  3. 通过isa指针和super_class指针我们可以在集成体系中游走并查询类型信息
  4. 查询类型信息是小心类簇以及不能直接使用class方法所返回的类对象直接比较

当然了,OC的运行时系统同时也提供了相应地动态创建类和实例的函数可供使用。了解了类与对象的模型后我们便可以使用这些函数动态地创建类与对象。这些下次再说。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值