https://opensource.apple.com/tarballs/objc4/
图解
对象的isa指针指向哪里?
实例对象的isa指针指向类对象,类对象isa指针指向元类对象;如果有继承关系,所有子类以及基类的元类对象的isa指针都指向基类的元类对象。
假设//Person类里有对象方法和类方法
@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end
------开始讨论类对象的isa指向调用-----
//Person *p1 = [[Person alloc] init];
//[p1 personMethod];
instance的isa指向class
当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
//[Person personClassMethod];
class的isa指向meta-class
当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
对象的superclass指针指向哪里?
如果有继承关系,子类类对象的superclass指向父类类对象,父类类对象的superclass指向基类类对象,基类类对象的superclass指向nil;子类元类对象的superclass指向父类元类对象,父类元类对象的superclass指向基类元类对象,基类元类对象的superclass指向基类类对象(特殊点)。
------开始讨论类对象的superclass指向调用-----
比如继承关系如下
/* Person */
@interface Person : NSObject
{
@public
int _age;
}
@property (nonatomic, assign) int height;
- (void)personMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end
/* Student */
@interface Student : Person
{
@public
int _no;
}
@property (nonatomic, assign) int score;
- (void)studentMethod;
+ (void)studentClassMethod;
@end
@implementation Student
- (void)studentMethod {}
+ (void)studentClassMethod {}
@end
上述是继承关系,如果我的调用代码如下,调用执行逻辑见备注
Student *stu = [[Student alloc] init];
[stu studentMethod]; //stu实例对象的isa指针会指向Student类对象,从而调用Student对象方法。
[stu personMethod]; //stu实例对象的isa指针会指向Student类对象,Student类对象的superclass指针指向Person类对象,从而调用Person对象方法。
[stu init]; //stu实例对象的isa指针会指向Student类对象,Student类对象的superclass指针指向Person类对象,Person类对象的superclass指针指向NSObject类对象,从而调用NSObject对象方法。(因为init这个方法存在NSobject里。)
------结束论证类对象的superclass-----
------开始论证元类对象的superclass-----
比如继承关系如下
/* Person */
@interface Person : NSObject
{
@public
int _age;
}
@property (nonatomic, assign) int height;
- (void)personMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end
/* Student */
@interface Student : Person
{
@public
int _no;
}
@property (nonatomic, assign) int score;
- (void)studentMethod;
+ (void)studentClassMethod;
@end
@implementation Student
- (void)studentMethod {}
+ (void)studentClassMethod {}
@end
上述是继承关系,如果我的调用代码如下,调用执行逻辑见备注
Student *stu = [[Student alloc] init];
[Student studentClassMethod]; //Student类对象的isa指针会指向Student元类对象,从而调用Student元类类方法。
[Student personClassMethod]; //Student类对象的isa指针会指向Student元类对象,Student元类对象的superclass指针指向Person元类对象,从而调用Person元类类方法。
[Student load]; //Student类对象的isa指针会指向Student元类对象,Student元类对象的superclass指针指向Person元类对象,Person元类对象的superclass指针指向NSObject元类对象,从而调用NSObject元类类方法。
------结束论证元类对象的superclass-----
如何事实论证isa指针指向问题呢
----先论证实例对象ISA指向类对象-----
//先创建实例对象
NSObject *object = [[NSObject alloc] init];
//创建类对象
Class objectClass = [NSObject class];
//然后断点运行,通过日志栏左侧可以看到object里有isa指针,在打印栏输入以下代码,可以打印出object的指针值,存的也就是objectClass的地址,然而实际输入打印objectClass地址时发现并不太相同,是因为从64bit开始,isa需要进行一次位运算,才能计算出真实地址 p/x是按16位输出
p/x (long)object->isa
位运算规则如下 (源码可以查看,源码库里搜索objc_object { ,点击objc-private.h文件,点击isa_t,可以找到ISA_MASK定义)
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL //真机64位处理器需要arm64架构
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL //模拟器64位处理器测试需要x86_64架构
# endif
//通过isa值和ISA_MASK位运算后可以看到值和objectClass地址相同
p/x (long)object->isa & 0x00007ffffffffff8
上面进行地址的&运算后,可以看到object的isa指向地址和objectClass的地址相同,所以证明了实例对象的isa指向了类对象
----现在论证类对象isa指向元类对象-----
//因为类的isa不像实例对象的isa直接暴露出来。我们在点击Class objectClass = [NSObject class];这句代码的Class可以看到被定义为这句话typedef struct objc_class *Class;这是一个结构体。再点击objc_class看到确实是一个isa指针的结构体 如右struct objc_class {
// Class _Nonnull isa OBJC_ISA_AVAILABILITY;
// }
/* 我们看到了类对象里的结构,但是因为类的isa不像实例对象的isa直接暴露出来,所以我们为了让isa“暴露”出来,我们定义一个跟系统Class结构一样的结构体,然后通过桥接,将objectClass转成我们自己的结构体,于是乎可以打印出类对象的isa指针。 */
//我们自定一个跟系统同样功能的objc_class结构体
struct mj_objc_object {
Class isa;
};
//创建类对象,所以下面这句话可以强转
Class objectClass = [NSObject class];
//创建元类对象
Class mateClass = object_getClass(obClass1);
//桥接(__bridge),然后强转(struct mj_objc_class *),通过这样强转后,就可以看到左侧日志栏,已经出现的objectClass2的isa。
struct mj_objc_class *objectClass2 = (__ bridge struct mj_objc_class *)objectClass;
//通过isa值和ISA_MASK位运算后可以看到值和mateClass地址相同
p/x (long)objectClass2->isa & 0x00007ffffffffff8
上面进行地址的&运算后,可以看到objectClass2的isa指向地址和mateClass的地址相同,所以证明了类对象的isa指向了元类对象
----论证类对象内部结构以及superclass -----
在xcode里直接点击Class可以查看结构是typedef struct objc_class *Class结构体,然后点击objc_class这个结构体,定义如下
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
但是,这个OBJC2_UNAVAILABLE代表已经在oc2.0(2006 年,苹果发布了全新的 Objective-C 2.0)被舍弃了,那么objc_class最新的长什么样子呢?
打开源码,搜索objc_class然后在objc-runtime-new.h里可以看到objc_class定义如下
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();
}
//还有很多代码,为了便于查看,删除了
}
可以看到最新的objc_class里居然没有isa,其实是有的,因为继承于objc_object,点击objc_object查看,可以看到这个结构体定义为
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
//还有很多代码,为了便于查看,删除了
}
objc_object里有isa,objc_class继承于objc_object,所以也是有isa的,只不过不会显式看到。
一步步查看源码,可以合并为下面的层次结构
struct objc_class {
Class ISA;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 用于获取类的具体信息
class_rw_t *data() {
return bits.data();
}
//还有很多代码,为了便于查看,删除了
}
而bits里有(点击class_data_bits_t)
struct class_data_bits_t {
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
//还有很多代码,为了便于查看,删除了
}
而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 firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
而class_ro_t里
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance对象占用的内存空间(不用质疑,就是alloc生成的那个实例对象所占的内存大小)
#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;
}
下面用xcode来验证上面结构
新建一个.h(MJClassInfo.h)文件,定义如下(模仿系统的结构体)
#import
#ifndef MJClassInfo_h
#define MJClassInfo_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 mj_objc_object {
void *isa;
};
/* 类对象 */
struct mj_objc_class : mj_objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
public:
class_rw_t* data() {
return bits.data();
}
mj_objc_class* metaClass() { //针对性新增,方便获取元类。原理就是拿到类对象isa然后位运算就能拿到元类对象的地址
return (mj_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* MJClassInfo_h */
然后调用,因为MJClassInfo.h有一些C++语法,所以为了调用,需要将main.m的文件改为main.mm,然后这个文件就兼容了C++语法, 此时书写结构体时就不用再加struct了。
NSObject *object = [[NSObject alloc] init];
Person *person = [[Person alloc] init];
Student *student = [[Student alloc] init];
mj_objc_class *objectClass = (__bridge mj_objc_class *)[object class];
mj_objc_class *personClass = (__bridge mj_objc_class *)[person class];
mj_objc_class *studentClass = (__bridge mj_objc_class *)[student class];
//获取元类。因为在上面代码里,新增了一个方法直接做了isa & ISA_MASK操作。
mj_objc_class *objectMetaClass = objectClass->metaClass();
mj_objc_class *personMetaClass = personClass->metaClass();
mj_objc_class *studentMetaClass = studentClass->metaClass();
class_rw_t *objectClassData = objectClass->data();
class_rw_t *personClassData = personClass->data();
class_rw_t *studentClassData = studentClass->data();
class_rw_t *objectMetaClassData = objectMetaClass->data();
class_rw_t *personMetaClassData = personMetaClass->data();
class_rw_t *studentMetaClassData = studentMetaClass->data();
// 0x00007ffffffffff8
NSLog(@"%p %p %p %p %p %p", objectClassData, personClassData, studentClassData,
objectMetaClassData, personMetaClassData, studentMetaClassData);
然后打断点,可以看到左边打印框里的信息或者看输出来看里面存的数据
由此,通过源码以及代码都验证了类对象内部结构
论证superclass,可以通过上述断点查看左侧结构里的superclass,通过打印superclass地址以及父类地址可以验证superclass指向父类,以及元类的superclass指向问题。
有一道题是,子类继承NSObject,NSObject写了一个分类,子类和NSObject分类都在.h里声明了test类方法,但是NSObject分类里面实现的是test对象方法,子类里面不实现任何方法。结果通过子类调用test类方法时,打印的是NSObject分类里面实现的test对象方法。由此论证了基类元类对象的superclass指向基类类对象。
原因是调用类方法时底层是objc_msgSend([NSObject class],@selector(test)) ,其中objc_msgSend调用的时候是不关心调用的是类方法还是对象方法,只会根据对象去找,元类对象找不到,根据基类元类对象的superclass指向基类类对象,所以成了调用类对象的对象方法了