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阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容