面向對象程式設計之所以成為主流範式,核心支柱便是繼承與多態。雖然各大 OOP 語言皆支援這兩大特性,但實作細節各有差異。Objective-C 與 Java 同樣採用單一繼承模型,不支援多重繼承;反觀 C++ 則允許多重繼承。關於 Objective-C 為何捨棄多重繼承,稍後將詳細探討。
繼承關係常被比喻為「人是人他媽生的,妖是妖他媽生的」——出自《大話西游》。人與妖同屬動物類別,卻各擁獨特屬性與行為。動物為父類別,人、妖為子類別。繼承的本質在於消除代碼重複,貫徹 DRY 原則,讓子類別自動擁有父類別的屬性與方法,並可擴展或覆寫專屬功能。
在 Objective-C 中,super 指向直接父類別,self 指向當前實例,相當於 Java 的 this。值得注意的是,在 @implementation 中可定義 @interface 未聲明的方法,這類方法屬私有,僅供類別內部使用。
Objective-C 幾乎所有類別皆繼承自 NSObject,該類別提供大量強大方法。以下針對常用方法進行實測與說明:
類別載入至運行環境時自動調用,屬類別方法,無需實例化即可觸發。
// 重寫 NSObject 中的 load 方法
+(void) load {
NSLog(@"ObjectTest 中的 load 方法被調用啦!!");
}
運行結果:
2014-07-30 08:58:31.704 HelloOC[550:303] ObjectTest中的load方法被調用啦!!
類別首次被使用時調用,後續不再重複呼叫。
// 重寫 initialize 方法
+(void) initialize {
NSLog(@"initialize 方法被調用了(類第一次被實例化)!");
}
運行結果:
2014-07-30 09:27:53.767 HelloOC[624:303] load方法被調用啦!!
2014-07-30 09:27:53.769 HelloOC[624:303] initialize方法被調用了(類第一次被實例化)!
+ (id)alloc:分配記憶體並返回對象- (id)init:對已分配記憶體的實例進行初始化new:等同於同時呼叫 alloc 與 init可於子類別中重寫 alloc 與 init 觀察調用順序:
// 重寫 alloc 方法
+(id) alloc {
NSLog(@"alloc函數被調用啦");
return [super alloc];
}
// 重寫 init 方法
-(id) init {
NSLog(@"init被調用了");
self = [super init];
return self;
}
測試代碼與結果:
// 第一次使用 ObjectTest 類
ObjectTest *o1 = [[ObjectTest alloc] init];
ObjectTest *o2 = [ObjectTest new];
輸出顯示 new 內部確實依序呼叫 alloc 與 init。
釋放對象自身佔用的記憶體。
// 返回當前對象所屬類別
NSString *className = (NSString *)[self class];
NSLog(@"%@類的display方法", className);
// 返回當前對象的父類別
NSString *superClassName = (NSString *)[self superclass];
NSLog(@"%@類的父類是%@", className, superClassName);
BOOL b = [o2 isKindOfClass:[ObjectTest class]];
if (b == YES) {
NSLog(@"o2是ObjectTest類的對象");
}
BOOL result = [o2 isMemberOfClass:[NSObject class]];
if (result == NO) {
NSLog(@"isMemberOfClass不能判斷是否為NSObject子類的對象");
}
NSString *descript = [o2 description];
NSLog(@"%@", descript);
輸出: <ObjectTest: 0x100206650>
NSUInteger hash = [o2 hash];
NSLog(@"%p", hash);
輸出: 0x100107b70
預設比較記憶體位址,若相等則 hash 值必相同。
NSString *str1 = @"111";
NSString *str2 = str1;
if ([str2 isEqual:str1] == YES) {
NSLog(@"str2 == str1");
} else {
NSLog(@"str2 != str1");
}
繼承關係屬「is-a」關係:貓是動物,動物為父類,貓為子類。子類擁有父類屬性與行為,並可擴展專屬特性——「父類更一般,子類更具體」。
// Richer.h
#import
@interface Richer : NSObject {
@protected
NSString *name;
int age;
NSString *gender;
}
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int age;
@property (copy, nonatomic) NSString *gender;
// 便利初始化方法
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender;
// 便利建構器
+ (id)richerWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender;
// 刷卡方法
- (void)poss;
@end
// Richer.m
#import "Richer.h"
@implementation Richer
@synthesize name, age, gender;
// 便利初始化
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender {
if (self = [super init]) {
self->name = vName;
self->age = vAge;
self->gender = vGender;
}
return self;
}
// 便利建構器
+ (id)richerWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender {
Richer *richer = [[Richer alloc] initWithName:vName AndAge:vAge AndGender:vGender];
return richer;
}
// 刷卡方法
- (void)poss {
NSLog(@"%@有錢你就刷吧", name);
}
@end
// Richer2nd.h
#import "Richer.h"
@interface Richer2nd : Richer
@property (copy, nonatomic) NSString *hobby;
// 便利初始化
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby;
// 便利建構器
+ (id)richer2ndWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby;
// 新增方法
- (void)myHobby;
@end
// Richer2nd.m
#import "Richer2nd.h"
@implementation Richer2nd
@synthesize hobby;
// 便利初始化:復用父類初始化
- (id)initWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby {
if (self = [super initWithName:vName AndAge:vAge AndGender:vGender]) {
self->hobby = vHobby;
}
return self;
}
// 便利建構器
+ (id)richer2ndWithName:(NSString *)vName AndAge:(int)vAge AndGender:(NSString *)vGender AndHobby:(NSString *)vHobby {
Richer2nd *richer = [[Richer2nd alloc] initWithName:vName AndAge:vAge AndGender:vGender AndHobby:vHobby];
return richer;
}
// 重寫刷卡方法
- (void)poss {
[super poss];
NSLog(@"我是富二代,我爸有錢,我就刷!");
}
// 新增愛好方法
- (void)myHobby {
NSLog(@"我是富二代%@,我超喜歡%@", name, hobby);
}
@end
2014-07-30 08:38:12.956 HelloOC[483:303] Bill有錢你就刷吧
2014-07-30 08:38:12.957 HelloOC[483:303] BILL`s son有錢你就刷吧
2014-07-30 08:38:12.958 HelloOC[483:303] 我是富二代,我爸有錢,我就刷!
2014-07-30 08:38:12.958 HelloOC[483:303] 我是富二代BILL`s son,我超喜歡飙車
多態簡單來說,就是不同對象響應同一訊息時產生不同行為。Objective-C 中動態類型 id 是實現多態的關鍵,id 可指向任意對象類型。
id richer = nil;
// 測試富人類
richer = [Richer richerWithName:@"Bill" AndAge:40 AndGender:@"Man"];
[richer poss];
// 測試富二代類
richer = [Richer2nd richer2ndWithName:@"BILL`s son" AndAge:16 AndGender:@"男" AndHobby:@"飙車"];
[richer poss];
[richer myHobby];
輸出結果與繼承範例一致,證明同一變數指向不同實例時,呼叫相同方法產生不同行為。
Animal *animal = nil;
// 實例化貓
animal = [Cat new];
[animal eat];
// 實例化狗
animal = [Dog new];
[animal eat];
父類別指標 animal 依序指向 Cat、Dog 實例,呼叫 eat 時分別執行子類別覆寫的方法,即為多態體現。
這兩大原則是設計健壯、可維護繼承體系的基石。