第3章 类和继承
3.1.1父类和子类
当要定义的新类是某个类的扩展或者是某个类的修改时,可以通过在已有类的基础上追加内容来定义新类。
通过扩展或者修改来定义新类的方法叫做继承(inheritance)。在继承关系中,被继承的类叫做父类,新建的类叫做子类。
继承意味着子类继承了父类的所有特性,父类的变量和方法自动成为子类的变量和方法。另外,子类还可以
追加新的变量
追加新的方法
重新定义父类中的方法
子类中只增加新的变量而不变更方法则没有意义。子类中重新定义父类的方法叫做重写
3.1.2 类的层次结构
以某父类生成若干子类,这些子类在派生出更多的子类,如此下去就会形成一颗倒立的树,它由通过继承关系的类组成,这种树成为类层次结构,位于类层次最顶端的类称为根类(rootclass)
NSObject 是Cocoa 环境下的根类,Cocoa中所有类都直接或者间接的继承了NSObject。新建的任何类都必须是NSObject或它的继承类的子类。NSObject中定义了所有Objective-C对象的基本方法。
由于这种类的层次关系,Objective-C的所有对象都继承了NSObject类中定义的各种属性。Objective-C的对象能够作为对象来使用,就是因为类NSOjbect中定义了对象的基本功能。
在面向对象语言中,有的有唯一根类,有的根类则不唯一
3.2 利用继承定义新类
@interface 类名 : 父类名{
变量的声明;
}
方法的声明;
@end
多文件编译中 子类文件中必须具有含有父类接口的文件
子类重写init初始化方法时,需要按照一下逻辑,其他以init开头的初始化方法也是同理
- (id)init
{
self = [super init]; //一定要在第一行调用父类的init方法
if (self != nil)
{
......
}
return self;
}
其中父类的init方法会初始化子类从父类中继承的变量,下面则是子类的专属初始化操作
当所有类的init方法都这么写,那么根类NSObject的init就一定会执行,否则生成的对象就无法使用。
如果父类是NSObject,则初始化不可能出错,可以省略if语句,使用传入的参数或通过从文件读入变量进行初始化时,因为值的类型错误或读取文件失败等原因初始化可能会失败。
生成实例方法alloc会把实例对象 的 变量都初始化为0,除了变量isa,如果子类新增的变量初值为0,则可以跳过子类的初始化,此时应该加上注释避免错误
3.3使用继承的程序示例
定义一个带有静音功能的类 MuteVolume,该类的功能:收到mute消息时将音量设置为最小。
MuteVolume的定义从类Volume中继承而来 并且增加新的方法mute方法实现功能
- (id) mute;
- (id) mute{
val = min;
return self;
}
@end//
3.3.2方法重写的例子
新增变量 BOOL muting
- (id)initWithMin:(int)a max:(int)b step:(int)s
/* override */
{
self = [super initWithMin:a max:b step:s];
if(self != nil){
muting = NO;
}
return self
}
3.4继承和方法调用
3.4.1使用self调用方法
如果想在一个方法中调用当前类中定义的方法,可以利用self,但如果存在继承关系,通过self时要格外注意
类A派生出类B,类B派生出类C
类A有3中方法method1,2,3
类B重写method1,3;
类C重写method2;
假设类B的method3想调用method1,2;通过self调用method1,2会变成什么样
B调用method3,[self method1],[self method2],即[B method1],[B method2],B中没有method2,会调用A方法中的method2
C调用method3,C会继承B的method3,B的method3调用[C method1] , [C method2],
C要调用B的method1
也就是说,self指的是收到消息的当前变量,即便是同一个程序,根据类的不同,调用方法也可能不同
3.4.2 使用super调用方法
3.5 方法定义时的注意事项
3.5.1局部方法
实现接口声明的方法中,可把具备独立功能的部分独立出来定义成子方法,这些子方法,一般只在内部调用,不需要包含在类的接口中公开
这种情况下,局部方法可以只在.m文件中实现,不需要在接口部分中进行声明,这样即使其他模版引用了接口文件,也无法获得这个方法的定义,无法调用这个方法,从而实现了局部方法,但这是说的是无法从接口中获得这个方法的定义,但这个方法仍然是存在的,只要发送信息就会执行
比如
@interface ClickVolume:Volume
- (id) up;
- (id) down;
@end//
@implementation ClickVolume
- (void) playClick{
// 实现方法 //
}
- (id) up{
[self playClick];
return [super up];
}
- (id) down{
[self playClick];
return [super down];
}
@end//
没有在接口中声明的局部方法和没有进行属性声明的C语言一样,只能被定义在局部方法之后的方法调用。playClick必须在up和down的前面,需要先声明在使用
使用局部方法可以增加程序的可维护性,但在继承的时候可能会出现问题,例如子类新追加的方法可能并不知道父类已经实现了局部方法而去重新实现一个父类的局部方法,为了避免出现这种bug,苹果公司建议为局部方法名添加固定的前缀
3.5.2 指定初始化方法
根据需求可能会为一个类定义多个不同的初始化方法,需要一个指定数值的初始化方法,一个直接使用的默认初始化,又比如能从文件读入变量完成初始化的方法等。指定初始化方法就是要能确保所有实例变量都能被初始化,类的非初始化方法会调用指定初始化方法完成初始化。通常,接收参数最多的初始化方法就是指定初始化方法
子类的指定初始化通常通过向super发送消息来调用超类的指定初始化方法。除此之外,还有一些通过封装来调用指定初始化方法的方法叫做非指定初始化方法,每一个类可以有多个指定初始化方法
如果子类想要重写指定初始化方法,必须调用超类的指定初始化方法,不能调用超类的非指定初始化方法,不然会造成循环递归,无法终止。
Obejctive-C通过注释来表现谁是指定初始化方法 Cocoa API文档中都会标注指定初始化方法