-
Objective-C中的物件, 主要可以分為3種
- instance: 例項物件, 包含 isa和其他成員變數的值, ...
- class: 類物件, 包含, isa、superclass、屬性、物件方法、協議、成員變數的描述, ...
- meta-class: 元類物件, 包含 isa、superclass、類方法, ...
-
可以用下圖表示每種物件中包含的內容
一、準備程式碼
- 準備兩個類,
Person
類繼承自NSObject
,Student
類繼承自Person
, 具體如下:
@interface Person : NSObject <NSCopying> {
int _age;
}
@property (nonatomic, assign) double height;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod {}
+ (void)personClassMethod {}
- (id)copyWithZone:(nullable NSZone *)zone {
return nil;
}
@end
@interface Student : Person {
int _no;
}
@property (nonatomic, assign) double weight;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation Student
- (void)studentInstanceMethod {}
+ (void)studentClassMethod {}
@end
複製程式碼
-
Person
類中, 主要包含以下內容int
型別的成員變數_age
double
型別的屬性height
- 一個例項方法
- (void)personInstanceMethod
- 一個類方法
+ (void)personClassMethod
- 遵守
NSCopying
協議, 並實現- (id)copyWithZone:(nullable NSZone *)zone
方法
-
Student
類中, 主要包含以下內容int
型別的成員變數_no
double
型別的成員變數weight
- 一個物件方法
- (void)studentInstanceMethod
- 一個類方法
+ (void)studentClassMethod
二、驗證OC呼叫方法是傳送訊息機制
-
OC中方法的呼叫, 是通過傳送訊息機制實現的, 我們可以檢視底層程式碼來驗證
-
建立
Person
類的物件, 呼叫方法personInstanceMethod
方法
Person *person = [[Person alloc] init];
[person personInstanceMethod];
複製程式碼
- 當前的程式碼如下圖:
- 我們使用終端
cd
到main.m
的檔案中, 並執行命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
複製程式碼
- 將生成的
main-arm64.cpp
檔案拖到當前工程中:
- 可以在
main-arm64.cpp
檔案中, 找到如下程式碼:
- 可以看到有一句程式碼:
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personInstanceMethod"));
複製程式碼
- 去掉型別轉換後:
objc_msgSend(person, sel_registerName("personInstanceMethod"));
複製程式碼
- 即:
OC
中呼叫方法, 在底層就是傳送一條訊息
三、物件的isa指標是指向哪裡?
問: 物件的isa指標是指向哪裡?
答: instance的isa, 指向class類物件, 類物件的isa指向meta-class元類物件
-
以
NSObject
物件為例NSObject
的例項物件:isa
指向NSObject
的class類物件
NSObject
類物件:isa
指向NSObject
的meta-class
元類物件NSObject
的meta-class
元類物件:isa
指向NSObject
的meta-class
元類物件
-
以
Person
物件為例Person
的例項物件:isa
指向Person
的class類物件
Person
類物件:isa
指向Person
的meta-class
元類物件Person
的meta-class
元類物件:isa
指向NSObject
的meta-class
元類物件
-
以
Student
物件為例Student
的例項物件:isa
指向Student
的class類物件
Student
類物件:isa
指向Student
的meta-class
元類物件Student
的meta-class
元類物件:isa
指向NSObject
的meta-class
元類物件
-
instance
的isa
指向class
- 當呼叫物件方法時, 通過
instance
的isa
找到class
, 最後找到物件方法的實現進行呼叫
- 當呼叫物件方法時, 通過
-
class
的isa
指向meta-class
- 當呼叫類方法時, 通過
class
的isa
找到meta-class
, 最後找到類方法的實現進行呼叫
- 當呼叫類方法時, 通過
-
meta-class
的isa
指向基類的meta-class
四、class物件的superclass
指標
- 類物件的
superclass
指標, 指向父類的類物件
那麼
class
中的superclass
指標的作用是什麼呢?
- 現有如下程式碼:
Student *student = [[Student alloc] init];
[student studentInstanceMethod];
[student personInstanceMethod];
[student init];
複製程式碼
- 當
student
呼叫物件方法studentInstanceMethod
時, 會有以下查詢方式- 通過
instance
的isa
找到Student
的class
類物件, 檢視是否有studentInstanceMethod
- 發現
Student
的class
物件中有studentInstanceMethod
方法, 停止繼續查詢, 通過訊息機制呼叫方法
- 通過
- 當
student
呼叫父類Person
的物件方法personInstanceMethod
, 會有以下查詢方式- 通過
student
的isa
找到Student
的class
類方法, 檢視是否有personInstanceMethod
- 發現
Student
的class
物件中沒有personInstanceMethod
, 於是通過superclass
指標找到Person
的class
物件, 檢視是否有personInstanceMethod
- 在
Person
的class
物件中發現personInstanceMethod
方法, 停止繼續查詢, 通過訊息機制呼叫方法
- 通過
- 當
student
呼叫基類NSObject
的init
方法時, 會有以下查詢方式- 首先, 通過
isa
指標找到Student
的class
物件, 檢視是否有init
方法 - 在
Student
的class
物件中沒有發現init
方法, 於是通過superclass
指標找到Person
的class
物件 - 在
Person
的class
中查詢init
方法, 結果發現Person
的class
物件中也沒有init
方法 - 此時, 就會通過
Person
的class
物件中的superclass
指標, 找到NSObject
的class
物件中, 查詢init
方法 - 在
NSObject
的class
物件中, 找到了init
方法, 停止繼續查詢, 通過訊息機制呼叫方法
- 首先, 通過
注意:
class
的superclass
會指向父類的class
物件, 最後指向的是NSObject
的class
物件, 而NSObject
的class
物件中的superclass
指標, 會指向nil
如果在發現
NSObject
的class
中也沒有找到要呼叫的方法時, 就會報錯unrecognized selector sent to instance
五、meta-class
中的superclass
指標
- 與類物件的
superclass
指標類似,meta-class
中的superclass
指標指向父類的meta-class
-
在類方法的呼叫上, 與例項方法呼叫類似
-
當
Student
的class
要呼叫Person
的類方法時,會先通過isa
找到Student
的meta-class
,然後通過superclass
找到Person
的meta-class
,最後找到類方法的實現進行呼叫
注意: 基類
NSObject
的meta-class
物件的superclass
最終指向的是NSObject
的class
物件, 而不是指向nil
六、isa、superclass總結
- isa、superclass的作用如下圖:
-
intance
的isa
指向class
-
class
的isa
指向meta-class
-
meta-class
的isa
指向基類的meta-class
-
class
的superclass
指向父類的class
- 如果沒有父類,
superclass
指標為nil
- 如果沒有父類,
-
meta-class
的superclass
指向父類的meta-class
- 基類的
meta-class
的superclass
指向基類的class
- 基類的
-
instance
呼叫物件方法的軌跡isa
找到class
, 方法不存在, 就通過superclass
找父類
-
class
呼叫類方法的軌跡isa
找meta-class
, 方法不存在, 就通過superclass
找父類
七、驗證NSObject的Meta-class物件中的superclass指向自身的Class物件
- 上面提到過: 基類的
meta-class
的superclass
指向基類的class
- 下面通過程式碼來進行驗證
@interface Person: NSObject
+ (void)test;
@end
@implementation Person
+ (void)test {
NSLog(@"+ [Person text] - %p", self);
}
@end
複製程式碼
Person
類繼承自NSObject
, 有一個類對方+ (void)test
, 並給出了方法的實現
NSLog(@"[Person class] - %p", [Person class]);
[Person test];
// 控制檯列印:
[Person class] - 0x100001170
+ [Person text] - 0x100001170
複製程式碼
-
根據列印可以知道, 呼叫方法的正是
Person
類的Class
物件 -
現在刪除
test
方法的實現部分, 只保留宣告部分
@interface Person: NSObject
+ (void)test;
@end
@implementation Person
@end
複製程式碼
- 再次呼叫該方法, 會報出執行時錯誤,
test
方法不存在
[Person test];
// 報錯: '+[Person test]: unrecognized selector sent to class 0x100001130'
複製程式碼
- 我們在給
NSObject
新增一個分類, 實現+ (void)test
方法
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
+ (void)test
{
NSLog(@"+ [NSObject test] - %p", self);
}
@end
複製程式碼
- 再次使用
Person
的類物件呼叫test
方法
NSLog(@"[Person class] - %p", [Person class]);
[Person test];
// 控制檯列印:
[Person class] - 0x100001220
+ [NSObject test] - 0x100001220
複製程式碼
-
此時的呼叫順序是:
- 1、
Person
的類物件, 通過isa
找到了Person
的元類物件, 並查詢有沒有test
方法 - 2、由於
Person
的元類物件中沒有test
方法, 於是通過superclass
找到了父類NSObject
的元類物件 - 3、在
NSObject
的元類物件中, 發現了test
方法, 傳送訊息, 呼叫方法
- 1、
-
接著我們移除掉
NSObject
分類中的+ (void)test
方法的實現
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
@end
複製程式碼
- 此時再次使用
Person
呼叫+ (void)test
方法, 就會報執行時錯誤
[Person test];
reason: '+[Person test]: unrecognized selector sent to class 0x100001178'
複製程式碼
- 接著在
NSObject
的分類中, 給出一個物件方法- (void)test
的方法實現
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
- (void)test {
NSLog(@"- [NSObject test] - %p", self);
}
@end
複製程式碼
- 再次使用
Person
呼叫類方法+(void)test
NSLog(@"[Person class] - %p", [Person class]);
[Person test];
// 控制檯列印:
[Person class] - 0x1000011b8
- [NSObject test] - 0x1000011b8
複製程式碼
- 此時呼叫成功, 說明當
NSObject
的元類物件中沒有test
方法時, 就會通過superclass
指標找到NSObject
的類物件, 並查詢有沒有test
方法 - 由於在
NSObject
中找到了test
方法, 所以會直接呼叫