转载出处: 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_IMP和initFunction_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_IMP和initFunction_a_IMP应该是不一样的。
父类和子类中的Class
Class是一个objc_class的指针,在类进行内存分配的时候,对于一个类而言,runtime需要找到这个类的父类,然后把父类的Class的指针地址赋值给isa里面的super_class。通过这样的方式,将父类和子类的关系紧紧联系在一起。
由于ClassA是ClassB的父类,因此,a_class和b_class中super_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预留了两个位置。