Objective-C中的继承和重载

本文通过具体的示例,深入分析了Objective-C中的继承机制,包括父类方法的调用、重载方法的处理以及实例变量在内存中的分配位置等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载出处: http://www.cnblogs.com/yaski

Objective-C是C的衍生语言,除了继承了所有C语言的特性外,还在语言中融入了面向对象的特点。继承是面向对象编程的一个重要内容。本文从NSObject根类出发,分析继承中父类方法的调用,重载方法的调用,以及实例变量在内存分配的位置等的实现,来理解继承在面向对象编程中发挥的作用。

在objective-c中,允许定义自己的根类,但通常你不想这么做,而是想要利用现有的类。我们所定义的类都属于NSObject的派生类。NSObject位于层次结构的最顶端,因此称它为根类。只要定义一个新类,该类都会继承一些属性。例如,父类的所有实例变量和方法都成为新类的一部分。这意味着子类可以直接访问这些方法和实例变量,就像这些实例变量和方法已经在新类中定义了一样。

从一个简单的例子开始:

首先,定义一个类ClassA,使其继承自NSObject,并定义一个实例变量x和一个初始化实例变量的方法initVar;

//ClassA declaration and definition
@interface ClassA : NSObject
{
	int x;
}
-(void) initVar;
@end

@implementation ClassA
-(void) initVar
{
	x = 100;
}
@end

然后,再定义一个类ClassB,使其继承自ClassA,不定义自己的实例变量,定义一个printVar的方法;

//ClassB declaration and definition
@interface ClassB : ClassA
-(void) printVar;
@end
@implementation ClassB
-(void) printVar
{
	NSLog(@"x = %i in ClassB",x);
}
@end

最后,再定义一个类ClassC,同样使其继承自ClassA,在ClassC中新定义一个实例变量y,并且重新定义自己的initVar方法,同时增加了一个printVar方法。

//ClassC declaration and definition
@interface ClassC : ClassA
{
	int y;
}
-(void) initVar;
-(void) printVar;
@end
@implementation ClassC
-(void) initVar
{
	x = 0;
	y = 5;
}
-(void) printVar
{
	NSLog(@"x = %i in ClassC,y = %i",x,y);
}
@end

主程序如下

int main(int argc, char* argv[])
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	
	ClassC* a= [[ClassA alloc] init];
	ClassB* b= [[ClassB alloc] init];
	ClassC* c= [[ClassC alloc] init];
	
	SEL initFunction_a_SEL = @selector(initVar);
	IMP initFunction_a_IMP = [a methodForSelector:initFunction_a_SEL];
	
	SEL initFunction_b_SEL = @selector(initVar);
	SEL printFunction_b_SEL = @selector(printVar);
	IMP initFunction_b_IMP = [b methodForSelector:initFunction_b_SEL];
	IMP printFunction_b_IMP = [b methodForSelector:printFunction_b_SEL];
	
	SEL initFunction_c_SEL = @selector(initVar);
	SEL printFunction_c_SEL = @selector(printVar);
	IMP initFunction_c_IMP = [c methodForSelector:initFunction_c_SEL];	
	IMP printFunction_c_IMP = [c methodForSelector:printFunction_c_SEL];
	
	Class a_class = a->isa;
	Class b_class = b->isa;
	Class c_class = c->isa;
	
	printFunction_b_IMP(b,printFunction_b_SEL);
	printFunction_c_IMP(c,printFunction_c_SEL);
	
	[a release];
	[b release];
	[c release];
	
	[pool drain];
	return 0;
}

编译,在[pool drain];处打上断点,Debug,查看Debugger。

方法的用:

在objective-C中只要方法的定义相同,那么他们的SEL是完全一样的。因此,我们在图1中可以看到,在ClassA的initFunction_a_SEL和ClassC中的 initFunction_c_SEL 是一样的。

图1 ClassA,ClassB,ClassC中initVar方法的SEL变量值

ClassB是ClassA的子类,继承了ClassA的initVar方法。从图1中我们可以看到,ClassB中虽然没有定义initVar方法,但是initFunction_b_SEL并不为空,而是与initFunction_a_SEL相同。并且指向initVar方法的地址initFunction_b_IMP也与和父类ClassA中的initFunction_a_IMP完全一样的:如图2所示。

图2 ClassA和ClassB中initVar方法的入口地址

initFunction_b_IMP的地址和initFunction_a_IMP完全一样,说明他们使用的是相同的代码段。ClassB是ClassA的子类,它是通过怎样的方式来继承ClassA的initVar方法的呢?要弄清楚这其中的根本原因,得从继承的根结点NSObject开始说起。

查看 interface NSObject ,可以看到如下代码:

@interface NSObject <NSObject> 
{
    Class	isa;
}
+ (void)load;
……

NSObject里面只有一个变量,就是Class类型的isa。isa的英文的意思就是is a pointer的意思。也就是说NSObject里面只有一个实例变量isa。 Class的定义是:

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;

Class实际上是一个objc_class的指针类型, id实际上是objc_object结构的一个指针,里面只有一个元素那就是Class. 我们无法在Xcode里面看到Class也就是objc_class的定义。不过,可以通过开源的代码查看到具体的内容:

#include <stddef.h>
typedef const struct objc_selector 
{
    void *sel_id;
    const char *sel_types;
} *SEL;
typedef struct objc_object {
    struct objc_class*  class_pointer;
} *id;

typedef id (*IMP)(id, SEL,... ); 

typedef char *STR;

typedef struct objc_class *MetaClass;
typedef struct objc_class *Class;
struct objc_class {     
    MetaClass           class_pointer;         
    struct objc_class*  super_class;            
    const char*         name;                 
    long                version;               
    unsigned long       info;                  
    long                instance_size;          
    struct objc_ivar_list* ivars;              
    struct objc_method_list*  methods;          
    struct sarray *    dtable;                  
    struct objc_class* subclass_list;           
    struct objc_class* sibling_class;
    struct objc_protocol_list *protocols;         
    void* gc_object_type;
};

typedef struct objc_protocol {
    struct objc_class* class_pointer;
    char *protocol_name;
    struct objc_protocol_list *protocol_list;
    struct objc_method_description_list *instance_methods, *class_methods; 
} Protocol; 

typedef void* retval_t;        
typedef void(*apply_t)(void);    
typedef union arglist {
    char *arg_ptr;
    char arg_regs[sizeof (char*)];
} *arglist_t;            

typedef struct objc_ivar* Ivar_t;
typedef struct objc_ivar_list {
    int   ivar_count;                             
    struct objc_ivar {
        const char* ivar_name;                    
        const char* ivar_type;                      
        int        ivar_offset;                           
    } ivar_list[1];                              
} IvarList, *IvarList_t;

typedef struct objc_method {
    SEL         method_name;                  
    const char* method_types;                 
    IMP         method_imp;                  
} Method, *Method_t;

typedef struct objc_method_list {
    struct objc_method_list*  method_next;   
    int            method_count;              
    Method method_list[1];                   
} MethodList, *MethodList_t;

struct objc_protocol_list {
    struct objc_protocol_list *next;
    size_t count;
    Protocol *list[1];
};

从上面的这段代码中,可以截取出objc_class的定义:

struct objc_class {     
    MetaClass           class_pointer;         
    struct objc_class*  super_class;            
    const char*         name;                 
    long                version;               
    unsigned long       info;                  
    long                instance_size;          
    struct objc_ivar_list* ivars;              
    struct objc_method_list*  methods;          
    struct sarray *    dtable;                  
    struct objc_class* subclass_list;           
    struct objc_class* sibling_class;
    struct objc_protocol_list *protocols;         
    void* gc_object_type;
};

注意到这里的methods变量,里面保存的就是类的方法名字(SEL)定义,方法的指针地址(IMP)。当我们执行IMP initFunction_b_IMP = [b methodForSelector:initFunction_b_SEL];时,runtime会通过dtable这个数组,快速的查找到我们需要的函数指针,查找函数的定义如下:

_inline__ IMP
objc_msg_lookup(id receiver, SEL op)
{
	if(receiver)
		return sarray_get(receiver->class_pointer->dtable, (sidx)op);
	else
		return nil_method;
	...

到这里,我们了解到initFunction_a_IMP的值从哪里得来的。

对于initFunction_b_IMP的地址和initFunction_a_IMP完全一样的解释如下:

objc_class的定义中有一句是这样的:

struct objc_class*  super_class;

对应于我们的程序中来说,runtime在ClassB中没有找到initVar方法,此时它会去父类ClassA中寻找。在ClassA中找到了initVar方法的执行地址入口,就把这个地址赋给initFunction_b_IMP。由于initFunction_b_IMPinitFunction_a_IMP都是指向ClassA里定义的initVar方法,所以两者指向一致,值相等。

以同样的方式,可以理解,在ClassB中能够使用继承的实例变量x。

重载方法

在ClassC中重新定义了initVar方法,此方法与父类ClassA中的重名。此时我们发现initFunction_c_IMP的地址和initFunction_a_IMP的地址是不同的,如图3.


图3 ClassA和ClassC中initVar方法的入口地址

这是因为runtime在ClassC中寻找initVar方法的时候,会首先在类ClassC里面寻找,寻找到之后寻找的过程也就结束了,同时把这个方法的IMP返回给我们。由于在ClassC中定义了initVar方法,因此runtime不会再到父类ClassA中去查找,所以initFunction_c_IMPinitFunction_a_IMP应该是不一样的。

 

父类和子类中的Class

Class是一个objc_class的指针,在类进行内存分配的时候,对于一个类而言,runtime需要找到这个类的父类,然后把父类的Class的指针地址赋值给isa里面的super_class。通过这样的方式,将父类和子类的关系紧紧联系在一起。

由于ClassA是ClassB的父类,因此,a_classb_classsuper_class的是完全相同的,如图4。

图4 ClassA与ClassB的superclass是相同的

实例变量的内存分配的位置

创建对象的时候,类的内容需要被调入到内存当中我们称之为内存分配(Allocation),然后需要把实体变量进行初始化(Initialization),当这些步骤都结束了之后,我们的类就被实例化了,我们把实例化完成的类叫做对象(Object)。

对于内存分配的过程,runtime需要知道分配多少内存还有各个实例变量的位置。

查看是objc_class定义中的如下内容:

typedef struct objc_ivar* Ivar_t;
typedef struct objc_ivar_list 
{   int   ivar_count;                             
    struct objc_ivar 
   {    const char* ivar_name;                    
        const char* ivar_type;                      
        int        ivar_offset;                           
    } ivar_list[1];                              
} IvarList, *IvarList_t;

objc_ivar可以得到实例变量的名字(name),类型(type),和在内存中的位置偏移(offset)。runtime从类的isa里面取得了这些信息之后就知道了如何去分配内存。

首先看看ClassA的实例变量在内存中的位置,图5:


图5 ClassA的实例变量在内存中的位置

在ClassA里面,我们看到了第一个实例变量是isa,第二个就是我们定义的x。isa是父类NSObject的变量,x是ClassA类的变量。

通过图6还能发现,x的offset为4,也就是说,ClassA给isa预留了一个位置。我们可以看出来,runtime总是把父类的变量放在前头,然后才是子类的变量。

图6 ClassA的实例变量x在内存中的位置偏移

然后看看子类ClassC的实例变量在内存中的位置,图7:

图7 ClassC的实例变量在内存中的位置偏移

ClassC中定义了自己的实例变量y,y的位置偏移为8,也就是说,runtime为isa和x预留了两个位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值