- Objective-C和Sprite Kit游戲開發從入門到精通
- 曹化宇
- 1913字
- 2021-01-07 18:57:43
4.6 繼承
繼承最大的好處就是代碼的復用,在Objective-C代碼中使用類的繼承體系,有一些問題需要我們注意,如成員的訪問級別、屬性和方法的重寫、初始化方法的繼承等。本節就來討論這些問題。
■4.6.1 成員的訪問
在討論類的初始化方法時,我們已經看到了一些成員訪問相關的內容,如:
? super關鍵字用于在子類中訪問父類(基類、超類)中的成員,包括屬性和方法等。但是請注意,這并不包括父類中定義為私有的(private)成員。
? 在接口部分聲明的屬性和方法,其訪問級別是公共的(public),而在實現部分定義的方法的默認訪問級別是受保護的(protected),它們可以在子類中訪問。
? 對于實例變量,定義在接口部分的實例變量默認訪問級別是受保護的,可以在本類或子類中訪問,但我們可以使用@public、@protected和@public指令修改它們的訪問級別。實現部分定義的實例變量,其訪問級別默認則私有的,只能在本類中使用。
? self關鍵字用于訪問當前對象,我們可以在類中使用這個關鍵字訪問當前對象的各種屬性和方法。
接下來,我們會在標準機器人的基礎上創建機器人士兵,定義為CRorotSoldier類,它將繼承CRobot類,其接口部分如下面的代碼(CRobotSoldier.h文件)。
#ifndef __CRobotSoldier_h__ #define __CRobotSoldier_h__ #import <Foundation/Foundation.h> @interface CRobotSoldier : CRobot @property NSString* weapon; -(void) fire; @end #endif
接下來是CRobotSoldier類的實現部分,如下面的代碼(CRobotSoldier.m文件)。
#import "CRobotSoldier.h" @implementation CRobotSoldier @synthesize weapon; -(void) fire { NSLog(@"機器人%@使用%@開火", self.name, self.weapon); } @end
我們可看到,在CRobotSoldier類中的fire方法中,使用self關鍵字調用了name和weapon屬性,其中weapon屬性為CRobotSoldier類中定義的屬性,而name屬性是在CRobot類中定義的,但由于CRobotSoldier類繼承于CRobot類,所以,我們也可以在CRobotSoldier類中使用name類。
下面的代碼演示了CRobotSoldier類的使用。
CRobotSoldier *killer = [[CRobotSoldier alloc] init]; killer.name = @”Killer-1”; killer.weapon = @”脈沖槍"; [killer fire];
■4.6.2 重寫屬性和方法
前面,我們已經討論了如何使用super和self關鍵字分別調用父類或本類的成員。在開發中,有些時候可能需要在子類中完全重寫父類中的成員;在Objective-C中,這個工作很簡單,只需要在子類的實現部分創建一個完全一樣的成員,就可以覆蓋基類中的同名成員。
如下面的代碼(CRobotSoldier.m文件),我們將在CRobotSoldier類的實現部分重寫work方法。
@implementation CRobotSoldier // 其他代碼 -(void) work { NSLog(@"機器人%@正在戰斗", self.name); } @end
在下面的代碼中,我們直接使用CRobotSoldier類中的work方法。
CRobotSoldier *rs = [[CRobotSoldier alloc] init]; rs.name = @"Killer-1"; [rs work];
當子類中重寫了父類的成員以后,我們還是可以在子類中使用super關鍵字訪問到父類中的同名成員。如下面的代碼。
@implementation CRobotSoldier // 其他代碼 -(void) work { [super work]; NSLog(@"機器人%@正在戰斗", self.name); } @end
■4.6.3 繼承關系中的初始化
在類的繼承關系中,了解初始化方法的調用關系非常重要,在前面的示例中,我們并沒有在CRobotSoldier類中定義初始化方法,那么,當我們執行如下面代碼時,初始化方法是怎么工作的呢?
CRobotSoldier *rs = [[CRobotSoldier alloc] init];
實際上,當我們調用初始化方法init時,代碼會從當前類向上(父類)的順序開始查找初始化方法,也就是說,此代碼中的init方法的查找順序應該是CRobotSoldier→CRobot→NSObject。由于CRobotSoldier和CRobot類都沒有定義init方法,所以,最終調用的就是NSObject類中的init方法。
1.對象初始化完整性
在4.5節中,我們提到,初始化方法的調用應保證對象初始化的完整性,所以,當我們在子類中定義了新的init方法以后,一般情況下,還應該首先調用基類的init方法以完成對象的前期初始化工作,如下面的代碼。
-(instancetype) init { self = [super init]; // 當前對象初始化代碼 return self; }
接下來,我們將在CRobot和CRobotSoldier類中創建init初始化方法,然后,我再來觀察它們的調用順序。
首先,在CRobot類中重寫init方法,如下面的代碼(CRobot.m文件)。
@implementation CRobot // 其他代碼 -(instancetype) init { self = [super init]; if (self) { NSLog(@"正在組裝機器人"); } return self; } @end
下面是CRobotSoldier類中重寫的init方法(CRobotSoldier.m文件)。
@implementation CRobotSoldier // 其他代碼 -(instancetype) init { self = [super init]; if (self) { NSLog(@"正在改造機器人士兵"); } return self; } @end
下面的代碼,我們觀察這幾個初始化方法調用的情況。
CRobotSoldier *killer = [[CRobotSoldier alloc] init];
當我們調用CRobotSoldier類的init方法初始化對象時,實際會調用三個init方法,它們的調用順序是[NSObject init]→[CRobot init]→[CRobotSoldier init],這樣,我們就不難看出這一行代碼會輸出什么內容了,即:
正在組裝機器人 正在改造機器人士兵
請注意信息的順序,這實際顯示了初始化方法調用的關系。此外,[NSObject init]方法并不是我們定義的,而且沒有顯示信息,但應注意,在CRobot類中的init方法中,我們的確使用[super init]語句調用它了。
2. id與instancetype類型
如果看到較早版本的Objective-C代碼,你可能會發現類的初始化方法返回值類型被定義為id類型,而這個類型可以存放任意類型的對象。那么id和instancetype類型有什么區別呢?
首先,我們可以理解instancetype實際上是初始化方法的專用關鍵字,它只用于定義初始化方法(類方法或實例方法)的返回值類型;它的含義是,本方法返回的結果是方法所在類的實例(對象)。使用instancetype關鍵字,可以明確方法的作用和目的,在編譯或運行時都能夠更有效地發現對象初始化過程中可能出現的問題。
id類型表示任意類型的對象,在代碼中,和其他類型一樣可以定義對象、方法的返回值,或者是參數類型等,所以,id類型的應用會更靈活,但同時也應該非常注意,因為從字面上看,它的類型是不明確的。但是不用著急,關于如何動態地處理類和對象,本章稍后會有討論。