文章目錄
- 前言
- 什么是繼承鏈
- OC中的根類
- 關于NSProxy
- 關鍵作用
- 1.方法查找與動態綁定
- 2. 消息轉發
- 3. **類型判斷與多態**
- 繼承鏈的底層實現
- 元類的繼承鏈
- 總結
前言
??在objective-c中,繼承鏈是類與類之間通過父類(Superclass)關系形成的一層層繼承結構,在我們之前的學習中,我們發現無論是方法的動態查找、消息的傳遞還是代碼的復用,都需要用到繼承鏈,今天我們就來系統地了解一下繼承鏈。
什么是繼承鏈
??每個OC類(除根類外)都有一個直接父類,通過這種層級關系形成一條從子類到根類的單向鏈。當向一個對象發送消息(調用方法)時,OC運行時會沿著這條鏈自底向上查找對應的方法實現:
- 若當前類實現了該方法,直接調用;
- 若未實現,則跳轉到父類繼續查找;
- 直到根類仍無實現時,觸發「消息轉發」機制(否則程序崩潰)。
OC中的根類
??OC 中幾乎所有類的最終父類都是 NSObject(少數特殊類如 NSProxy可能作為獨立根類)。NSObject定義了OC對象的基礎行為(如內存管理、反射、消息傳遞等)。
eg:
// 自定義類繼承鏈示例
@interface MyBaseClass : NSObject
@end@interface MySubClass : MyBaseClass
@end// MySubClass 的繼承鏈:MySubClass → MyBaseClass → NSObject → nil(根類無父類)
關于NSProxy
??在 Objective-C(OC)中,NSProxy是一個特殊的抽象基類,與 NSObject并列作為 OC 類體系的兩大根類(但 NSObject是絕大多數類的最終父類,而 NSProxy更專注于消息轉發場景)。它的核心設計目標是==輕量級消息轉發,常用于實現代理模式(Proxy Pattern)、動態消息攔截或替代復雜的繼承結構。
NSProxy的底層源碼如下:
/* NSProxy.hCopyright (c) 1994-2019, Apple Inc. All rights reserved.
*/#import <Foundation/NSObject.h>@class NSMethodSignature, NSInvocation;NS_HEADER_AUDIT_BEGIN(nullability, sendability)NS_ROOT_CLASS
@interface NSProxy <NSObject> {__ptrauth_objc_isa_pointer Class isa;
}+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);// - (id)forwardingTargetForSelector:(SEL)aSelector;@endNS_HEADER_AUDIT_END(nullability, sendability)
通過上述代碼,我們可以發現NSProxy是一個實現了NSObject協議的根類。
由此我們知道:
-
NSProxy 是一個抽象類,跟 NSObject 一樣的基類,都遵守
NSObject
協議 -
NSProxy是一個抽象類,必須繼承實例化其子類才能使用。
-
NSProxy從類名來看是代理類專門負責代理對象轉發消息的。相比NSObject類來說NSProxy更輕量級,OC是單繼承的語言,但通過NSProxy可以幫助Objective-C間接的實現多重繼承的功能(“偽多繼承”)。
在NSProxy源碼中,運用消息轉發機制的核心方法有兩個:
通過methodSignatureForSelector:
方法獲取一個NSMethodSignature
類型的對象,調用forwardInvocation:
方法。該方法傳入一個封裝了NSMethodSignature
的NSInvocation
對象。然后該對象通過invakeWithTarget:
方法將消息轉發給其它對象。
順便回顧一下消息轉發:
OC的消息轉發流程:當向一個對象發送消息,而該對象沒有實現對應的方法時,運行時會觸發消息轉發機制。這個過程分為幾個步驟:動態方法解析、備用接收者轉發,最后是完整消息轉發。這兩個方法主要參與最后一步,即完整消息轉發階段。
methodSignatureForSelector:
的作用是為指定的選擇器生成方法簽名。方法簽名包含了方法的參數類型、返回類型等信息,是構造NSInvocation
對象所必需的。如果這個方法返回nil
,運行時會認為該消息無法處理,進而觸發doesNotRecognizeSelector:
導致崩潰。然后是
forwardInvocation:
,它的作用是處理那些無法被當前對象或其繼承鏈中其他類處理的方法調用。在這個方法里,我們可以自定義如何處理這些未被識別的消息,比如將消息轉發給其他對象,或者執行一些額外的邏輯。當消息轉發到
NSProxy
或自定義類時,首先會調用methodSignatureForSelector:
來獲取方法簽名,如果返回有效的簽名,才會調用forwardInvocation:
來處理消息。如果methodSignatureForSelector:
返回nil
,則不會調用forwardInvocation:
,直接崩潰。
關鍵作用
1.方法查找與動態綁定
OC 是動態語言,方法的調用(消息發送)發生在運行時。繼承鏈的存在使得對象可以「繼承」父類的方法,無需重復實現。例如:
MySubClass *obj = [[MySubClass alloc] init];
NSString *desc = [obj description]; // 實際調用 NSObject 的 description 方法
即使 MySubClass
未重寫 description
,運行時仍會沿繼承鏈找到 NSObject
的實現。
2. 消息轉發
若繼承鏈中所有類都未實現目標方法,OC 會嘗試通過消息轉發機制處理,避免直接崩潰。典型流程如下:
- 動態方法解析:調用
+resolveInstanceMethod:
(實例方法)或+resolveClassMethod:
(類方法)嘗試動態添加方法實現; - 備用接收者轉發:調用
-forwardingTargetForSelector:
將消息轉發給其他對象; - 完整消息轉發:調用
-methodSignatureForSelector:
生成方法簽名,再通過-forwardInvocation:
轉發(可自定義處理邏輯)。
3. 類型判斷與多態
通過繼承鏈可以實現多態(Polymorphism)。例如,isKindOfClass:
和 isMemberOfClass:
方法通過檢查對象繼承鏈判斷類型:
id obj = [[MySubClass alloc] init];
BOOL isMyBase = [obj isKindOfClass:[MyBaseClass class]]; // YES(繼承鏈包含 MyBaseClass)
BOOL isNSObject = [obj isKindOfClass:[NSObject class]]; // YES(繼承鏈最終到 NSObject)
繼承鏈的底層實現
OC 類的底層通過 objc_class
結構體表示,其中 superclass
字段指向父類。通過運行時函數可手動操作繼承鏈:
class_getSuperclass(Class cls)
:獲取類的直接父類;class_getClass(Class cls)
:獲取類對應的元類(Meta Class);object_getClass(id obj)
:獲取對象的類(等價于[obj class]
)。
元類的繼承鏈
OC 中類(Class)本身也是對象,其類型為元類(Meta Class)。元類的繼承鏈與普通類不同:
- 普通類的
isa
指針指向其元類; - 元類的
isa
指針指向根元類(通常是NSObject
元類的父類); - 根元類的
isa
指針指向自身(形成閉環)。
例如,NSObject
類的元類繼承鏈為:NSObject_Meta → Root_Meta(自身)
。
總結
- 在編程設計時,我們要避免繼承鏈過長,過深的繼承鏈會增加方法查找時間,降低性能。推薦優先使用組合(Composition)而非繼承。
- 在繼承中,根類(如
NSObject
)需自行實現部分基礎方法(如alloc
、init
),否則其子類無法正常使用。 - 類方法存儲在元類中,其繼承鏈為「元類 → 父元類 → … → 根元類」;實例方法的繼承鏈為「類 → 父類 → … → 根類」。
在面向對象編程(OOP)中,組合(Composition) 是一種通過「持有其他對象實例」來實現功能復用的設計模式,與「繼承(Inheritance)」共同構成代碼復用的兩大核心手段。它的核心思想是「整體-部分」(Whole-Part)關系,即一個對象(整體)由多個其他對象(部分)組成,通過調用這些「部分」對象的方法來實現自身功能。
在面向對象編程(OOP)中,組合(Composition) 是一種通過「持有其他對象實例」來實現功能復用的設計模式,與「繼承(Inheritance)」共同構成代碼復用的兩大核心手段。它的核心思想是「整體-部分」(Whole-Part)關系,即一個對象(整體)由多個其他對象(部分)組成,通過調用這些「部分」對象的方法來實現自身功能。
組合的本質是「has-a」關系(擁有關系),而非繼承的「is-a」關系(是一種關系)。例如:
一輛
Car
(整體)「擁有」一個Engine
(部分),因此Car
通過持有Engine
實例來調用start
、stop
等方法;一個
Computer
(整體)「擁有」一個CPU
和一個Memory
(部分),通過調用它們的計算和存儲方法完成功能。
與繼承相比,組合不要求整體類與部分類存在繼承層級,而是通過動態的消息傳遞(調用部分對象的方法)實現功能復用。
維度 | 組合(Composition) | 繼承(Inheritance) |
---|---|---|
關系類型 | 「has-a」(整體擁有部分) | 「is-a」(子類是一種父類) |
耦合度 | 低:整體與部分通過接口(協議)交互,解耦性強 | 高:子類依賴父類的實現細節(如私有方法、屬性) |
靈活性 | 高:運行時可動態替換部分對象(如依賴注入) | 低:繼承關系編譯時確定,無法動態修改 |
復用粒度 | 細粒度:僅復用需要的部分功能 | 粗粒度:必須繼承整個父類的功能(包括不需要的) |
設計復雜度 | 需定義清晰的接口(協議),規范部分對象的行為 | 需維護繼承鏈,可能導致「脆弱基類」問題 |
組合的具體使用:
在 Objective-C 中,組合通過「屬性持有其他對象實例」實現。
1.定義部分對象(Component)
首先定義需要被組合的功能模塊(部分),通常通過協議(Protocol)規范其行為,以降低耦合。
// 定義 Engine 協議(部分對象的行為規范)
@protocol Engine <NSObject>
- (void)start;
- (void)stop;
- (NSString *)engineInfo;
@end// 具體實現:GasEngine(汽油發動機)
@interface GasEngine : NSObject <Engine>
@end@implementation GasEngine
- (void)start { NSLog(@"汽油發動機啟動..."); }
- (void)stop { NSLog(@"汽油發動機停止..."); }
- (NSString *)engineInfo { return @"Gas Engine v1.0"; }
@end
2. 定義整體對象(Composite)
整體對象通過屬性持有部分對象的實例,并在需要時調用其方法。
// 整體對象:Car(汽車)
@interface Car : NSObject
@property (nonatomic, strong) id<Engine> engine; // 持有符合 Engine 協議的對象
- (instancetype)initWithEngine:(id<Engine>)engine;
- (void)startCar;
- (void)stopCar;
@end@implementation Car
- (instancetype)initWithEngine:(id<Engine>)engine {if (self = [super init]) {_engine = engine; // 注入部分對象(依賴注入)}return self;
}// 調用部分對象的方法實現自身功能
- (void)startCar {[self.engine start];NSLog(@"汽車啟動,使用發動機:%@", [self.engine engineInfo]);
}- (void)stopCar {[self.engine stop];NSLog(@"汽車停止");
}
@end
3. 使用組合
通過創建部分對象的實例,并注入到整體對象中,即可完成功能復用。
// 創建部分對象(GasEngine)
id<Engine> engine = [[GasEngine alloc] init];// 創建整體對象(Car)并注入部分對象
Car *car = [[Car alloc] initWithEngine:engine];// 調用整體對象的方法(內部調用部分對象的方法)
[car startCar];
// 輸出:
// 汽油發動機啟動...
// 汽車啟動,使用發動機:Gas Engine v1.0