Objective-C programming - classes and inheritance

About an important concept in object-oriented - inheritance, the use of inheritance can be easily extended basis in existing classes, based on the definition of a parent class has all the features of the new class.

Parent and child classes

We define a new class, we often encounter To define a new class is an extension of a class or a class of correction this situation right. If additional content on the basis of existing class to define a new class, you define a new class will become easier.

像这种通过扩展或者修改既有类来定义新类的方法叫作 继承 (inheritance)。在继承关系中,被继 

Order of the class is called the parent class (superclass), newly created by the class hierarchy is called a subclass (subclass).

继承意味着子类继承了父类的所有特性,父类的数据成员和成员函数自动成为子类的数据成员 

And member functions. In addition, the sub-category also

● add a new method

● add a new instance variable

● redefine methods of the parent class

Of course, if only to add a new sub-class instance variables without changing the method does not make any sense. Subclass redefine the parent class is called override (override).

Let's look at a few examples. In Figure 3-1, B is a subclass of class A, class B inherits the methods and instance variables of the class A, but rewrite method2. Class C is a subclass of class A, class C adds a new instance variables z and new methods method3. Class B and Class C is a subclass of class A, regardless of any instance variable of class A, class B and class C are able to perform a method method1 and method2.

 
enter image description here

Parent and child classes are a relative address. For example, in the above example, if the parent class Class B Class D sent birth to a child, then the type B with respect to the class is a subclass of A, Class D, but with respect to a parent class.

Further, in the set language, the subset refers to a set of relatively small (relative to the parent set), but in the case of the lower sub-class of the class generally extend the parent class. To avoid confusion on this nomenclature, C ++ in the base class is called the parent class (base class), or handle class is called a derived class derived class (derived class). Taking into account the object-oriented programming is generally used in the parent class is called a subclass, the book also use this name.

A developer, there is a learning atmosphere is particularly important with a circle of exchange, this is one of my iOS exchange group: 1012951431, sharing BAT, A inside test questions, interview experience, technical discussions, to share learning and growing together! Hope that helps developers avoid detours.

Class hierarchy

If several sub-classes to generate a class as a parent class, then inherit these sub-classes and generate more sub-categories, so the cycle continues it may generate an inverted tree, and it is associated to each other by inheriting from classes such a tree is called class hierarchy (class hierarchy).

Topmost class hierarchy of classes are called root class (root class), shown in Figure 3-2.

 
enter image description here

NSObject class is the root environment in Cocoa, Cocoa classes are all directly or indirectly inherits NSObjectA. Any new class must be a subclass of NSObject or its successor class. The method defined in NSObject substantially all the Objective-C object.

Because of this kind of hierarchy, all the objects in Objective-C inherit various attributes defined in the NSObject class. Objective-C objects can be used as a target, because NSObject class defines the basic functionality of the object. In object-oriented languages, and some Objective-C as a unique root class, such as Java and Smalltalk; some are not unique root class, such as C ++ is present.

Use inheritance to define new classes

Inherited definition

If you want to inherit a subclass for a class definition, how to do it?

Objective-C inheritance declaration in the interface section subclasses. In 2.2, we have explained how to define an interface class, and then introduce it again here.

 

When defining the parent class A sub-class B "class name" is the new class B, after the colon "parent name" is the need to inherit the class A.

Thus far in this book uses the parent class NSObject, because Objective-C in all classes must inherit the root class, NSObject in Objective-C is the root class of all classes. If there is a subclass of class wanted inherited, it must directly specify the class as a parent class, otherwise you will need to specify the parent class NSObject. Volume definition of the class earlier in time, because the Volume class does not particularly want to inherit the class, NSObject directly used as the parent class.

Declare instance variables only need to declare a new variable. If no new variables, only need to add {}, {} and sometimes even can be omitted. Declaration method requires only an additional new method. If the parent class method declared (rewrite) to be covered, you need to re-declare a method in the interface. We will rewrite the usual method of adding annotations to understand.

The following shows the case where the sub-class definition of class A B interface section. Method1 variables x and methods inherited from class A, there is no need to re-statement, the method method2 statement may be omitted.

 

Class definitions and header files

Suppose you have a class that has defined the Alpha, then the header files Alpha.h should already exist. When you want to define a subclass of Alpha Beta class, the header file must be included in Beta.h Alpha.h. I do not know if the parent class definition is not possible to define subclasses. Therefore, the header file that contains the parent class interface is necessary.

To be introduced to achieve some type of file header contains the interface part of the class. Achieve some need to include new methods of implementation and rewriting. Of course, to achieve local portion may also define various functions and variables.

Figure 3-3 Gamma.m file method of calling a method doSomething, this method is inherited from the Alpha class. Header Files Gamma.m introduced Gamma.h introduced Beta.h, Beta.h he also introduced Alpha.h, so Gamma.m can call the method doSomething.

 

Class definition can continue to use inheritance extended downward, but no matter how extended, long way to ensure that the introduction of this header file, any derived class will be able to use variables and methods defined in the parent class.

Inheritance and method calls

子类中定义的方法,除了能够访问新追加的实例变量外,也能够访问父类中定义的实例变量。

另外,因为继承的原因,子类也可以响应父类中定义的消息。但如果子类中重写了父类的方法, 就需要注意实际运行中到底哪个方法(父类的还是子类的)被执行了。

如图 3-4 所示,类 A 包含方法 method1、method2、method3。类 B 是类 A 的子类,类 B 中重新 定义了 method2。类 C 是类 B 的子类,类 C 中重新定义了 method1。

 

我们来看看给类 B 的实例变量发送消息时的情况。首先,假设向类 B 的实例对象发送了对应 method1 的消息,即进行了方法调用。虽然类 B 中没有 method1 的定义,但因为类 B 的父类类 A 中 定义了 method1,所以会找到类 A 的 method1,调用成功。消息 method3 的情况下也是同样的道理, 类 A 中定义的 method3 会被执行。method2 同前两个消息不同,类 B 中定义了 method2,所以会使 用自身定义的 method2 来响应这个消息。

而给类 C 的实例发送消息的话会怎么样呢?类 C 中有 method1 的定义,所以会直接使用类 C 中 定义的 method1 来响应这个消息。类 C 中没有 method2 的定义,所以调用的时候会使用类 B 中定义 的 method2 来响应。类 C 和类 B 中都没有定义 method3,所以类 A 中的定义 method3 会被调用。

调用父类的方法

子类继承了父类之后,有时就可能会希望调用父类的方法来执行子类中定义的其他处理,或者 根据情况进行和父类一样的处理或子类中单独定义的处理。让我们来看看图 3-4 中的例子,如果要在 类 B 的 method2 的定义中调用类 A 的 method2,那么该怎么办呢?通过 self 调用 method2 的话,就 会变成递归调用自身定义的 method2。

如果子类中想调用父类的方法,可以通过 super 关键字来发送消息。使用 super 发送消息后,就 会调用父类或父类的父类中定义的方法。如图 3-5 所示,类 C 中定义了 method1 和 method3。类 C  的 method1 中通过 super 调用了 method3,这时被调用的 method3 是类 A 中定义的 method3。

 

super 和 self 不同,并不确定指向某个对象。所以 super 只能被用于调用父类的方法,不能通过 super 完成赋值,也不能把方法的返回值指定为 super。

初始化方法的定义

新追加的实例变量有时需要被初始化。另外,子类也可能需要同父类不同的初始化方法。这些 情况下就需要为子类定义自己的初始化方法。

子类中重写 init 初始化方法的时候,通常按照以下逻辑。其他以 init 开头的初始化方法也是同理。

 

请注意第一行调用了父类的init 方法,父类的init 方法会初始化父类中定义的实例变量。下 面是子类专有的初始化操作。

如果所有的类的初始化方法都这样写,那么根类 NSObject 的init 方法就一定会被执行。否则 生成的对象就无法使用。与此同时,这样做也可以防止漏掉父类中定义的实例变量的初始化。

执行的时候父类的初始化方法可能会出错。出错时则会返回 nil,这种情况下子类也不需要再进 

行初始化,直接返回 nil 就可以了。

如果父类是 NSObject,则基本上不可能初始化出错,因此不判断这个返回值也是可以的。使用 传入的参数或通过从文件读入变量进行初始化时,因为值的类型错误或读取文件失败等原因,初始 化有可能会失败。这种情况下,需要确认父类的初始化方法的返回值。另外,上例中对 self 进行了 赋值,关于这个赋值的含义我们会在第 8 章中详细说明,这里只需要记住这是初始化方法的一种固 定写法即可。

生成实例对象的方法alloc 会把实例对象的变量都初始化为 0(后面会提到的实例变量 isa 除 外)。所以,如果子类中新追加的实例变量的初值可以为 0,则可以跳过子类的初始化。但是为了明确是否可以省略,最好为初值可为 0 的变量加上注释。

从程序的书写角度来说,设定初始值的方法有两种,即可以在初始化方法中一次性完成实例变量 的初始化,也可以在初始化方法中先设置实例变量为默认值,然后再调用别的方法来设置实例变量 的值。例如,类 Volume 也可以通过先调用初始化方法init ,然后再调用setMax: 等方法来设定音 量的最大值、最小值和变化幅度。原则上来说,初始赋值之后值不再发生变化的变量和需要显示设 定初值的变量,都需要通过带参数的初始化方法来进行初始化。

使用继承的程序示例

追加新方法的例子

我们来定义一个带有静音功能的类 MuteVolume。该类只有一个功能,即当收到mute 消息时, 设置音量为最小。

类 MuteVolume 的定义非常简单,父类是已经定义好的类 Volume。子类 MuteVolume 除了可以使 用父类 Volume 中定义的所有实例变量和方法之外,还新增加了一个 mute 方法。

 

这里使用了 Volume 作为父类,并引入了头文件 Volume.h。Volume 的父类是 NSObject,所以 , 所以就不需要再进行指定了。

没有定义新的实例变量,意味着子类中没有要追加的实例变量。

 
 

该测试程序的功能是从终端读入输入的字符串,并根据字符串的第一个字符来决定如何设置音 量。具体来说,第一个字符为 u 时表示提高音量,d 表示降低音量,m 表示静音,q 表示退出程序。

编译子类的时候,需要连同父类一起编译和链接,否则就无法使用父类中定义的方法。本例中 

编译所需要的文件一共有 5 个,即 Volume.h、Volume.m、MuteVolume.h、MuteVolume.m、main.m。

 

方法重写的例子

上面通过继承实现静音功能类的例子非常简单,让我们来看一个更实用的例子。

假设该例子要实现两个功能。第一个功能是,当再次收到mute 消息时,音量会恢复原值;第二 个功能是,在静音状态下收到up 或down 消息时,会返回最小音量值,同时改变音量值。

实现这些功能的方法有很多,这里我们增加一个 BOOL 类型的变量 muting,同时修改方 

法initWithMin:max:step:和 方法value的 实现。

 
 
 

初始化方法initWithMin:max:step:首 先调用了父类的初始化方法,然后对新增的实例变量 muting 进行了初始化。如前所述,子类的初始化一定要在父类的初始化之后进行。

value方 法根据当前是否为静音状态返回不同的值。静音状态下,返回最小值 min。mute 方法 

中只需要改变实例变量 muting 的状态来标识是否静音,不需要更改音量值 val。

编译的情况和上一节一样。main.m 直接使用上一节的即可。 

继承和方法调用

使用 self 调用方法

如果想在一个方法中调用当前类中定义的方法,可以利用 self。但如果存在继承关系,通过 self 调用方法时要格外注意。

在图 3-6 的例子中,有三个类 A、B、C。类 A 中定义了 method1、method2 和 method3 三个方法。 类 B 继承了类 A,重写了 method1 和 method3。类 C 继承了类 B,重写了 method2。

 

假设类 B 的方法 method3 想调用 method1 和 method2,通过 self 调用了 method1 和 method2。我 们来分析一下这个过程中到底哪个函数被调用了。对类 B 的实例对象调用 method3 方法时,首先会 通过 self 调用 method1,这个 method1 就是类 B 自身定义的 method1。接着,method3 通过 self 调用 method2,因为类 B 中并没有 method2 的定义,所以就会调用从类 A 中继承而来的 method2。

而如果是类 C 的实例对象调用方法 method3 的话会怎么样呢?我们首先来看看 method3,因为 类 C 中并没有定义 method3,所以调用的是类 B 中定义的 method3。要注意这个时候 self 指的是类 C 的实例对象,当 [self  method1] 执行时,因为类 C 中没有定义 method1,所以调用的是类 B 中 定义的 method1。然后,当 [self  method2] 执行时,因为类 C 中定义了 method2,所以执行的是 类 C 中定义的 method2,而不是上例中类 A 中定义的 method2。另外还有一点需要注意,就算类 B 中定义了 method2,调用的也是类 C 中定义的 method2。

也就是说,self 指的是收到当前消息的实例变量 ,因此,就算是同一个程序,根据实例的类的不 同,实际调用的方法也可能不相同。

使用 self 的时候要一定小心,要仔细分辨到底调用了哪个类的方法。即便如此,利用 self 的特 性来编程也是很常见的,更多详细内容请参考 11.1 节的内容。

使用 super 调用方法

而如果不使用 self 而使用 super,程序执行的结果会怎样呢?

图 3-7 是用 super 替代图 3-6 中的 self 的情况。使用 super 调用方法时,最后被调用的方法是类 B 的父类中定义的方法。所以无论是类 B 还是类 C 的实例变量调用了 method3,最后调用到的都是类 A 中定义的 method1 和 method2。

 

测试程序

我们用一个简单的程序来验证一下上面所描述的内容。这个程序本身并没有太大的意义,仅仅 是用来测试方法调用的。

测试程序中有三个类 A、B、C。类 A 中定义了方法 method1 和 method2。类 B 中对 method1 进 行了重写,通过 self 调用了 method1,通过 super 调用了 method2。类 C 重写了 method1。

 
 

程序执行之后输出如下。可以看出,类 B 和类 C 的实例分别调用了不同的方法。

 

方法定义时的注意事项

局部方法

实现接口声明中的方法时,可把具备独立功能的部分独立出来定义成子方法。一般情况下,这 些子方法都只供内部调用,不需要包含在类的接口中对外公开。

这种情况下,局部方法可以只在实现部分(通常是 .m 文件)中实现,而不需要在接口部分中进 行声明。这样一来,就算其他模块引用了接口文件,也无法获得这个方法的定义,无法调用这个方 法,从而就实现了局部方法。但这里只是说无法从接口中获得这个方法的定义,这个方法本身还是存在的,只要发送了消息,就能够执行。

让我们来看一个简单的例子,类 ClickVolume 是类 Volume 的一个子类,它的主要功能是当音量 发 生 变 化(提 高 或 降 低 )时 发 出 提 示 音。 提 高 或 降 低 音 量 时 发 出 提 示 音 使 用 一 个 共 同 的 方 法playClick, 定义如下所述。因为这个功能不会在其他地方使用到,所以我们把它定义成一个局 部方法,不在接口文件中声明。

 

未在接口中声明的局部方法和没有进行属性声明的 C 语言函数一样,只能被定义在局部方法之 后的方法调用。在上面的例子中,playClick 就必须定义在up 和down 的前面。定义顺序方面出现的 问题,可以使用第 10 章介绍的“范畴”(category)来解决。

编程的时候使用局部方法可以增强程序的可维护性,但在继承的时候可能会出现问题。例如, 子类新追加的方法可能并不知道父类已经实现了局部方法而去重新实现一个父类的局部方法。

为了避免这一问题,苹果公司建议为局部方法名添加固定的前缀(详情请参考附录 C)。 

指定初始化方法

前面已经介绍过了如何定义初始化方法,但还有一些要注意的地方。

根据需求有时可能需要为一个类定义多个不同的初始化方法。例如,既需要提供一个可指定每 个参数初始值的初始化方法,又需要提供一个每个参数都直接使用默认值的初始化方法;既需要提供 一个用内存变量进行初始化的初始化方法,又需要提供一个能从文件读入变量完成初始化的初始化 方法等。 指定初始化方法 (designated initializer)就是指能确保所有实例变量都能被初始化的方法, 这种方法是初始化的核心,类的非初始化方法会调用指定初始化方法完成初始化。通常,接收参数 最多的初始化方法就是指定初始化方法。

子类的指定初始化方法通常都是通过向 super 发送消息来调用超类的指定初始化方法。除此之外, 还有一些通过封装来调用指定初始化方法的方法叫作 非指定初始化方法 (secondary initializer)。图 3-8展示了指定初始化方法的概念,箭头指明了调用关系。图中每个类都只有一个指定初始化方法,实 际上也可以存在多个。

子类的指定初始化方法,必须调用超类的指定初始化方法。如图 3-8 中所示,按照类层次从底向 上,各个类的指定初始化方法会被连锁调用,一直到最上层的 NSObject 的指定初始化方法——init 为止。

 

如果子类中想重写父类中的指定初始化方法,就一定要调用父类的指定初始化方法,而不能调 用父类的非指定初始化方法。原因是非指定初始化方法内部会调用指定初始化方法,造成递归循环 调用,无法终止。

请看图 3-9 中的例子,类 A 的指定初始化方法是initWithMax :。init 是类 A 的非指定初始 化方法。类 B 是类 A 的子类,在 B 中重写了指定初始化方法initWithMax :。initWithMax :中 调用了父类类 A 的 init。如图所示,如果类 A 的 init 中通过 self 调用了initWithMax :,那么,当 初始化对象是类 B 的实例时,就又会调用到类 B 的initWithMax :,这样就变成了一个递归循环, 调用永远无法结束。

 

再让我们回头看一下图 3-8,图 3-8 中类的非指定初始化方法都调用了指定初始化方法来进行初 始化,同时父类的非指定初始化方法也可以被继承,但定义的时候一定要注意,否则也会出现循环 调用的问题。

Objective-C 没有特殊的语法或关键字来表明哪个方法是指定初始化方法,所以通常需要通过 文档或注释来标明指定初始化方法。Cocoa API 文档中的绝大多数类都标明了哪个方法是指定初始 化方法。

另外,如果你想一起进阶,不妨添加一下交流群1012951431,选择加入一起交流,一起学习。期待你的加入!

 

Guess you like

Origin www.cnblogs.com/Julday/p/12335640.html