小碼哥iOS學習筆記第三天: isa和superclass

weixin_34185364發表於2018-08-05
  • 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];
複製程式碼
  • 當前的程式碼如下圖:

  • 我們使用終端cdmain.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 指向 NSObjectclass類物件
    • NSObject類物件: isa 指向 NSObjectmeta-class元類物件
    • NSObjectmeta-class元類物件: isa指向 NSObjectmeta-class元類物件
  • Person物件為例

    • Person的例項物件: isa 指向 Personclass類物件
    • Person類物件: isa 指向 Personmeta-class元類物件
    • Personmeta-class元類物件: isa指向 NSObjectmeta-class元類物件
  • Student物件為例

    • Student的例項物件: isa 指向 Studentclass類物件
    • Student類物件: isa 指向 Studentmeta-class元類物件
    • Studentmeta-class元類物件: isa指向 NSObjectmeta-class元類物件
  • instanceisa指向class

    • 當呼叫物件方法時, 通過instanceisa找到class, 最後找到物件方法的實現進行呼叫
  • classisa指向meta-class

    • 當呼叫類方法時, 通過classisa找到meta-class, 最後找到類方法的實現進行呼叫
  • meta-classisa指向基類的meta-class

四、class物件的superclass指標

  • 類物件的superclass指標, 指向父類的類物件

那麼class中的superclass指標的作用是什麼呢?

  • 現有如下程式碼:
Student *student = [[Student alloc] init];
[student studentInstanceMethod];
[student personInstanceMethod];
[student init];
複製程式碼
  • student呼叫物件方法studentInstanceMethod時, 會有以下查詢方式
    • 通過instanceisa找到Studentclass類物件, 檢視是否有studentInstanceMethod
    • 發現Studentclass物件中有studentInstanceMethod方法, 停止繼續查詢, 通過訊息機制呼叫方法
  • student呼叫父類Person的物件方法personInstanceMethod, 會有以下查詢方式
    • 通過studentisa找到Studentclass類方法, 檢視是否有personInstanceMethod
    • 發現Studentclass物件中沒有personInstanceMethod, 於是通過superclass指標找到Personclass物件, 檢視是否有personInstanceMethod
    • Personclass物件中發現personInstanceMethod方法, 停止繼續查詢, 通過訊息機制呼叫方法
  • student呼叫基類NSObjectinit方法時, 會有以下查詢方式
    • 首先, 通過isa指標找到Studentclass物件, 檢視是否有init方法
    • Studentclass物件中沒有發現init方法, 於是通過superclass指標找到Personclass物件
    • Personclass中查詢init方法, 結果發現Personclass物件中也沒有init方法
    • 此時, 就會通過Personclass物件中的superclass指標, 找到NSObjectclass物件中, 查詢init方法
    • NSObjectclass物件中, 找到了init方法, 停止繼續查詢, 通過訊息機制呼叫方法

注意: classsuperclass會指向父類的class物件, 最後指向的是NSObjectclass物件, 而NSObjectclass物件中的superclass指標, 會指向nil

如果在發現NSObjectclass中也沒有找到要呼叫的方法時, 就會報錯unrecognized selector sent to instance

五、meta-class中的superclass指標

  • 與類物件的superclass指標類似, meta-class中的superclass指標指向父類的meta-class

  • 在類方法的呼叫上, 與例項方法呼叫類似

  • Studentclass要呼叫Person的類方法時,會先通過isa找到Studentmeta-class,然後通過superclass找到Personmeta-class,最後找到類方法的實現進行呼叫

注意: 基類NSObjectmeta-class物件的superclass最終指向的是NSObjectclass物件, 而不是指向nil

六、isa、superclass總結

  • isa、superclass的作用如下圖:

  • intanceisa指向class

  • classisa指向meta-class

  • meta-classisa指向基類的meta-class

  • classsuperclass指向父類的class

    • 如果沒有父類, superclass指標為nil
  • meta-classsuperclass指向父類的meta-class

    • 基類的meta-classsuperclass指向基類的class
  • instance呼叫物件方法的軌跡

    • isa找到class, 方法不存在, 就通過superclass找父類
  • class呼叫類方法的軌跡

    • isameta-class, 方法不存在, 就通過superclass找父類

七、驗證NSObject的Meta-class物件中的superclass指向自身的Class物件

  • 上面提到過: 基類的meta-classsuperclass指向基類的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方法, 傳送訊息, 呼叫方法
  • 接著我們移除掉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方法, 所以會直接呼叫

相關文章