iOS-對象、isa和SuperClass

前言:本文簡述OC對象、isa和SuperClass,如有錯誤請留言指正。

Q:OC中對象分類

A:總共為三類:實例對象、類對象、元類對象

  • instance對象(實例對象)
  • class對象(類對象)
  • meta-class對象(元類對象)

實例對象(instance)

  • instance對象就是通過類alloc出來的對象,每次調用alloc都會產生新的instance對象
Student *p1 = [[Student alloc] init];
Student *p2 = [[Student alloc] init];
  • p1、p2是Student的instance對象(實例對象),它們是不同的兩個對象,分別占據著兩塊不同的內存


    p1、p2內存地址

instance對象在內存中存儲的信息包括

  • isa指針
  • 其他成員變量的值
  • 實例對象不包含實現方法

類對象(Class)

NSObject *obj1 = [[NSObject alloc] init];
Class objClass1 = [obj1 class];
Class objClass2 = [NSObject class];
//class 方法返回的一直是class對象,類對象,而不是元類對象
Class objClass3 = [[NSObject class] class];
Class objClass4 = object_getClass(obj1);
NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);
  • 上述都是同一個類對象,打印出的地址相同,每個類在內存中有且只有一個class對象

class對象在內存中存儲的信息包括

  • isa指針
  • superclass指針
  • 類的屬性信息(@property)
  • 類的實例方法信息(instance method)
  • 類的協議信息(protocol)
  • 類的成員變量信息(ivar)(成員變量類型、名稱等等,不是成員變量的值)
  • ......

元類對象(meta-class)

Class metaClass = object_getClass([NSObject class]);
  • 每個類在內存中有且只有一個meta-class對象
  • meta-class對象和class對象的內存結構是一樣的,但是用途不一樣

在內存中存儲的信息主要包括

  • isa指針
  • superclass指針
  • 類的類方法信息(class method)
  • ......
方法說明

1.Class objc_getClass(const char *aClassName)

  • 1> 傳入字符串類名
  • 2> 返回對應的類對象

2.Class object_getClass(id obj)

  • 1> 傳入的obj可能是instance對象、class對象、meta-class對象
  • 2> 返回值
    a) 如果是instance對象,返回class對象
    b) 如果是class對象,返回meta-class對象
    c) 如果是meta-class對象,返回NSObject(基類)的meta-class對象

Q:實例對象、類對象isa指向

A:instance的isa指向class,class的isa指向meta-class

  • 當調用實例方法時,通過instance的isa找到class,最后找到實例方法的實現進行調用
  • 當調用類方法時,通過class的isa找到meta-class,最后找到類方法的實現進行調用


    isa指向

Q:對象的isa指針指向哪里?

A:根據不同類型的對象來回答。

  • instance對象的isa指向class對象
  • class對象的isa指向meta-class對象
  • meta-class對象的isa指向基類的meta-class對象

Q:OC的類信息存放在哪里?

A:類的內容分別作答。

  • 實例方法、屬性、成員變量、協議信息,存放在class對象中
  • 類方法,存放在meta-class對象中
  • 成員變量的具體值,存放在instance對象

Q問題描述:Student類繼承自Person;Person繼承自NSObject

示例代碼:

@interface Person : NSObject<NSCopying>
{
    int _age;
    int _name;
    int _bro;
}
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation Person
- (void)personInstanceMethod{
    
}
+ (void)personClassMethod{
    
}
- (id)copyWithZone:(NSZone *)zone{
    return nil;
}
@end

@interface Student : Person<NSCopying>
{
    int _dog;
    int _cat;
    int _book;
    
    int _phone;
    int _bicycle;
    
    int _hat;
    int _clothes;
    int _pants;
}
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation Student
- (void)studentInstanceMethod{}
+ (void)studentClassMethod{}
@end

q1:上述代碼各類對象superclass指向?
a1:各類的superclass指向父類對象

類對象的superclass指針指向父類對象


各類superclass指向
q2:Student實例對象調用Student實例方法的調用流程?

示例代碼:

Student *stu = [[Student alloc] init];
[stu studentInstanceMethod];

a2:stu實例對象isa找到Student的類對象,調用實例方法

實例對象調用實例方法流程
q3:Student實例對象調用Person實例方法的調用流程?

示例代碼:

Student *stu = [[Student alloc] init];
[stu personInstanceMethod];

a3:stu實例對象isa找到Student的類對象,通過Student的superclass找到Person的類對象,最后調用Person的實例方法實現。

stu調用Person實例方法流程
q4:Student實例對象調用init方法的調用流程?

示例代碼:

Student *stu = [[Student alloc] init];
[stu init];

a3:調用流程

  • 1.stu實例對象isa找到Student的類對象
  • 2.通過Student的superclass找到Person的類對象
  • 3.通過Person的superclass找到NSObject的類對象
  • 4.最后調用NSObject的init方法實現。

Q:Student類繼承自Person;Person繼承自NSObject,各類元類的superclass指針指向?

A:Student元類superclass指向Person的元類,Person元類superclass指向NSObject的元類

各類元類的superclass指針指向

q1:參考上述示例,[Student studentClassMethod] 調用流程
a1:Student類isa指針找到 Student元類,然后調用類方法 studentClassMethod

q2:參考上述示例,[Student personClassMethod] 調用流程
a3:Student類isa指針找到 Student元類,Student元類的superclass找到Person元類,再調用personClassMethod

q2:參考上述示例,[Student load] 調用流程
a3:Student類isa指針找到 Student元類,Student元類的superclass找到Person元類,Person元類的superclass找到NSObject元類,再調用load

isa、superclass 小結

  • instance的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找父類
isa、superclass指向

上述圖片備注:isa指向為虛線、superclass指向為實線,Root class元類的superclass指向Root class的class(轉彎處實線)。可以按照Student、Person、NSObject來理解。

  • 如果在基類中找不到方法實現,會報錯unrecognized selector sent to class 0x1000011**

Q:如果調用類方法沒有實現,是否會調用同名的實例方法。

A:會。

調用方法實質是runtime的消息轉發機制,runtime只會根據方法名尋找而不會在意是類方法還是實例方法。
代碼驗證:

#Person文件
@interface Person : NSObject
+ (void)test;
@end

@implementation Person
+ (void)test{
    NSLog(@"+[Person test] %p",self);
}
@end

#NSObject+Test文件
@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
+ (void)test{
    NSLog(@"+[NSObject test] %p",self);
}
@end

#調用方式:

NSLog(@"Person:%p",[Person class]);
NSLog(@"NSObject:%p",[NSObject class]);
[Person test];
[NSObject test];
輸出結果1:

Person:0x10e72e090
NSObject:0x10f6d8ea8
+[Person test] 0x10e72e090
+[NSObject test] 0x10f6d8ea8

輸出結果2:(如果將Person的類方法注釋掉,其余代碼不變)
#Person 文件
@interface Person : NSObject
+ (void)test;
@end

@implementation Person
//+ (void)test{
//    NSLog(@"+[Person test] %p",self);
//}
@end

輸出結果2:
Person:0x10477b050
NSObject:0x105725ea8
+[NSObject test] 0x10477b050
+[NSObject test] 0x105725ea8

因為Person里找不到test實現方法,即找父類的實現方法

輸出結果3:(如果將Person和NSObject的類方法注釋掉,其余代碼不變)
#Person 文件
@interface Person : NSObject
+ (void)test;
@end

@implementation Person
//+ (void)test{
//    NSLog(@"+[Person test] %p",self);
//}
@end

#NSObject+Test文件
@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
//+ (void)test{
//    NSLog(@"+[NSObject test] %p",self);
//}
@end

輸出結果3:會報錯
Person:0x10766efd0
NSObject:0x108618ea8
+[Person test]: unrecognized selector sent to class 0x10766efd0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[Person test]: unrecognized selector sent to class 0x10766efd0'

輸出結果4:(如果將Person和NSObject的類方法注釋掉,NSObject添加對象同名方法,其余代碼不變)
#Person 文件
@interface Person : NSObject
+ (void)test;
@end

@implementation Person
//+ (void)test{
//    NSLog(@"+[Person test] %p",self);
//}
@end

#NSObject+Test文件
@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
- (void)test{
    NSLog(@"-[NSObject test] %p",self);
}

//+ (void)test{
//    NSLog(@"+[NSObject test] %p",self);
//}
@end

輸出結果4:
Person:0x101656010
NSObject:0x102600ea8
-[NSObject test] 0x101656010
-[NSObject test] 0x102600ea8

由此可見,當類方法無實現時,會調用同名的實例方法,上述辯證方法驗證了isa、superclass指向圖中的meta-Root class的superclass指向Root class(上圖實線轉彎處)。

Q:討論isa指針值

從64bit開始,isa需要進行一次位運算,才能計算出真實地址
示例代碼:

struct xbt_objc_class {
    Class isa ;
};

Person *p = [[Person alloc] init];
Class pClass = [Person class];
Class personMetaClass = object_getClass(pClass);
struct xbt_objc_class *pClass1 = (__bridge struct xbt_objc_class *)pClass;
NSLog(@"%p-%p-%p",p,pClass,personMetaClass);

LLDB 命令行查看isa值

(lldb) p p->isa//獲取不到真正的isa指針
(Class) $0 = Person
(lldb) p/x (long)p->isa
(long) $1 = 0x000001a100105311//實例對象isa指針
(lldb) p/x pClass
(Class) $2 = 0x0000000100105310 Person//類對象地址
(lldb) p/x 0x000001a100105311 & 0x0000000ffffffff8//進行換算
(long) $3 = 0x0000000100105310

(lldb) p/x pClass1->isa
(Class) $0 = 0x000001a10402d2e9//類對象isa指針值
(lldb) p/x personMetaClass
(Class) $1 = 0x000000010402d2e8//元類對象isa指針值
(lldb) p/x 0x000001a10402d2e9 & 0x0000000ffffffff8//進行換算
(long) $2 = 0x000000010402d2e8
0x1c0036780-0x102a41310-0x102a412e8

結果可見:
1.實例對象isa值為0x000001a100105311
2.類對象地址為0x0000000100105310
3.換算結果:實例對象isa指針值 &0x0000000ffffffff8才與類對象地址值相同
4.類對象isa指針為0x000001a10402d2e9。(類并沒有暴露出isa指針值,定義了一個和類相似的結構體xbt_objc_class,進行強制轉換后,才獲取到類對象的isa指針)
5.元類對象地址為0x000000010402d2e8
6.換算結果:類對象isa指針值 &0x0000000ffffffff8才與元類對象地址值相同

總結

  • 從64bit開始,isa需要進行一次位運算,才能計算出真實地址
  • isa指針需要 & ISA_MASK 值才是最終結果
  • runtime中ISA_MASK值:
# if __arm64__//iPhone
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__//mac
#   define ISA_MASK        0x00007ffffffffff8ULL

Q:討論superclass指針值,是否會想isa指針值一樣?

A:superclass指針值與指向對象地址相同,和isa指針值不一樣

代碼證明
前提:student繼承自Person,寫結構體來代替類

struct xht_objc_class {
    Class isa ;
    Class superclass ;
};
struct xht_objc_class *pClass = (__bridge struct xht_objc_class *)[Person class];
struct xht_objc_class *stuClass = (__bridge struct xht_objc_class *)[Student class];
NSLog(@"%p-%p",pClass,stuClass);

LLDB調試

(lldb) p/x stuClass->superclass
(Class) $0 = 0x0000000102299340 Person//Student類的superClass值
(lldb) p/x pClass
(xht_objc_class *) $1 = 0x0000000102299340//Person類地址

綜上所述,Student類的superClass值 = Person類地址

Q:類的本質結構

類的本質是結構體
附上runtime的結構體代碼

#objc_class:objc_object
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    //方法緩存
    cache_t cache;             // formerly cache pointer and vtable
    //用于獲取具體類信息
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    // 諸多方法
}

#class_rw_t
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;//只讀類表

    method_array_t methods;//方法列表
    property_array_t properties;//屬性列表
    protocol_array_t protocols;//協議列表
     // 諸多方法
}

#class_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//instance對象占用的內存空間
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;//類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成員變量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

#objc_object
struct objc_object {
private:
    isa_t isa;//類的isa指針是私有的

public:
      // 諸多方法
}
結構體結構

想LLDB命令查看相關類結構,可以采取仿寫類結構進行打印查看

奉上大神的杰作

#import <Foundation/Foundation.h>

#ifndef xbtClassInfo_h
#define xbtClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance對象占用的內存空間
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成員變量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 屬性列表
    const protocol_list_t * protocols;  // 協議列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC對象 */
struct xbt_objc_object {
    void *isa;
};

/* 類對象 */
struct xbt_objc_class : xbt_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    xbt_objc_class* metaClass() {
        return (xbt_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* xbtClassInfo_h */
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容