(一)isa指针
我们在前面几章一直提到isa指针,isa指针是三种对象中都有的成员变量,那么三种对象的isa指针有没有什么区别?指向哪里呢?
我们首先看下面的代码:
MJPerson *personInstance = [[MJPerson alloc]init];
personInstance.no = 432423;
[personInstance personInstanceMethod];//instance对象 调用实例方法
[MJPerson personClassMethod];//class对象 调用类方法
对消息发送机制有一定了解的同学,一定知道对象调用方法的时候,实际上是给对象发送消息,或者通过转换为C++代码后可以看到:
MJPerson *personInstance = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)personInstance, sel_registerName("setNo:"), (NSInteger)432423);
((void (*)(id, SEL))(void *)objc_msgSend)((id)personInstance, sel_registerName("personInstanceMethod"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("personClassMethod"));
但是问题来了,实例对象中并没有存放对象方法,类对象中没有存放类方法,它们是如何调用成功的呢?
isa指针的作用就在于此!
(1)instance的isa
- instance的isa指向class
- 当调用对象方法时,通过instance的isa找到class对象,最后找到对象方法的实现进行调用
(2)class的isa
- class的isa指向meta-class
- 当调用类方法时,通过class的isa找到meta-class对象,最后找到类方法的实现进行调用
(二)superclass指针
class对象与meta-class都有super-class指针。那superclass指针又有什么作用呢?我们看看下面的代码:
@interface MJPerson : NSObject<NSCopying>
@property(nonatomic,assign) NSInteger no;
-(void)personInstanceMethod;
+(void)personClassMethod;
@end
@implementation MJPerson
-(void)personInstanceMethod{
}
+(void)personClassMethod{
}
-(id)copyWithZone:(NSZone *)zone{
return nil;
}
@end
@interface MJStudent : MJPerson<NSCoding>
@property(nonatomic,assign) int height;
-(void)studentInstanceMethod;
+(void)studentClassMethod;
@end
@implementation MJStudent
-(void)studentInstanceMethod{
}
+(void)studentClassMethod{
}
-(instancetype)initWithCoder:(NSCoder *)coder{
return nil;
}
-(void)encodeWithCoder:(NSCoder *)coder{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJStudent *student = [[MJStudent alloc]init];
[student studentInstanceMethod];//调用instance方法
[MJStudent studentClassMethod];//调用class方法
[student personInstanceMethod];//调用MJPerson的instance方法
[MJStudent personClassMethod];//调用MJPerson的class方法
[student init];//调用NSObject的instance方法
[MJStudent initialize];//调用NSObject的class方法
}
return 0;
}
同理main函数中的代码转为C++代码后:
//MJStudent *student = [[MJStudent alloc]init];
MJStudent *student = ((MJStudent *(*)(id, SEL))(void *)objc_msgSend)((id)((MJStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJStudent"), sel_registerName("alloc")), sel_registerName("init"));
//[student studentInstanceMethod];
((void (*)(id, SEL))(void *)objc_msgSend)((id)student, sel_registerName("studentInstanceMethod"));
//[MJStudent studentClassMethod];
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJStudent"), sel_registerName("studentClassMethod"));
//[student personInstanceMethod];
((void (*)(id, SEL))(void *)objc_msgSend)((id)student, sel_registerName("personInstanceMethod"));
//[MJStudent personClassMethod];
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJStudent"), sel_registerName("personClassMethod"));
//[student init];
((MJStudent *(*)(id, SEL))(void *)objc_msgSend)((id)student, sel_registerName("init"));
//[MJStudent initialize];
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJStudent"), sel_registerName("initialize"));
可以看出并没有什么区别,同样是给instance对象和class对象发送信息,那么是如何找到父类的对象方法以及类方法并调用成功的呢?
superclass指针的作用就在于此!
(1)instance对象的superclass
- 当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class对象,然后通过superclass找到Person的class对象,最后找到对象方法的实现进行调用
(2)class对象的superclass
- 当Student的class对象要调用Person的类方法时,会先通过isa找到Student的meta-class对象,然后通过superclass找到Person的meta-class对象,最后找到类方法的实现进行调用
(三)isa、superclass总结
下面对isa、superclass进行总结:
- instance对象的isa指向class对象
- class对象的isa指向meta-class对象
- 注意:meta-class对象的isa指向基类的meta-class对象,基类的meta-class对象的isa指向自身
- class对象的superclass指向父类的class对象;注意:如果没有父类,superclass指针为nil
- meta-class对象的superclass指向父类的meta-class对象;注意:基类的meta-class的superclass指向基类的class对象
- instance调用对象方法的轨迹,isa找到class;方法不存在,就通过superclass找父类
- class调用类方法的轨迹,isa找meta-class;方法不存在,就通过superclass找父类
(1)疑问?
基类的class对象的superclass指针为nil,会造成什么结果?
答:如果instance对象调用intance方法,首先通过isa指针找到自己的class对象,如果没有找到对应的方法,则会通过superclass指针一层一层往上,最终找到基类的class对象依然找不到的话,会报常见的unrecognized selector sent to class
错误,crash。为什么基类的meta-class的superclass指向基类的class对象?这样不就造成 +(类方法)和 - (对象方法)方法混了吗?
答:①我们通过下面的转换C++后的代码可以看出,不管是instance对象还是class对象调用对应的方法,都是像自身发送消息,通过isa到对应的位置寻找方法(没有区分+ - )
②如果基类的class对象调用类方法,对应的meta-class对象没有对应的方法,则会通过superclass指针找到自身(class对象),调用自身的方法(instance方法)
从面向对象的角度来说,是不合理的,但是从本质上来说
//[student personInstanceMethod];
((void (*)(id, SEL))(void *)objc_msgSend)((id)student, sel_registerName("personInstanceMethod"));
//[MJStudent personClassMethod];
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJStudent"), sel_registerName("personClassMethod"));
①我们通过下面的代码可以证明,确实会这样:(将不同文件的代码放在一块)
//NSObject+Test.h
@interface NSObject (Test)
+(void)test;
@end
//NSObject+Test.m
@implementation NSObject (Test)
+(void)test{
NSLog(@"NSObject + Test %p", self);
}
@end
@interface MJPerson : NSObject
+(void)test;
@end
@implementation MJPerson
+(void)test{
NSLog(@"MJPerson + Test %p", self);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"MJPerson class %p",[MJPerson class]);
NSLog(@"NSObject class %p",[MJPerson class]);
[MJPerson test];
[NSObject test];
}
return 0;
}
打印结果:
MJPerson class 0x1000024c0
NSObject class 0x7fff91c77140
MJPerson + Test 0x1000024c0
NSObject + Test 0x7fff91c77140
这个结果应该没有什么疑问
②接下来我们将MJPerson的test方法去除,再看结果:
MJPerson class 0x100002480
NSObject class 0x7fff91c77140
NSObject + Test 0x100002480
NSObject + Test 0x7fff91c77140
即可证明,当自身的meta-class对象没有对应的方法,通过superclass指针寻找到NSObject的meta-class对象并调用
③我们继续将NSObject分类.m文件中的test方法注释掉,再看结果:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[MJPerson test]: unrecognized selector sent to class 0x100001440'
常见的报错提示,找不到对应的test方法
④我们继续将test方法改为实例方法,又会是什么结果呢?
MJPerson class 0x100002480
NSObject class 0x7fff91c77140
NSObject - Test 0x100002480
NSObject - Test 0x7fff91c77140
是不是很震惊!!结果就是class对象调用了实例方法,即可证明上面的结论
(2)其他疑问?
问: 如果给NSObject的class对象发送test消息,可能实例方法与类方法有存在且同名,那会如何调用呢?
答:给NSObject的class对象发送一条test消息,首先通过isa找到meta-class对象,有则直接调用,此时类方法调用;如果找不到在通过superclass回到自身寻找是否有同名的实例方法,有则实例方法调用,没有则报错
(四)isa细节
我们上面已经知道instance对象通过isa可以找到class对象,class对象可以通过isa找到meta-class对象,那么isa存放的真的是对应的地址吗?
我们可以打印一下,发现isa并不是直接存放的对应的地址,那么isa又是存放的是什么呢?又是如何找到对应的对象呢?
其实,在以前的系统中,isa确实存放的是地址值。但是,从64位之后,isa需要进行一个位运算,才能计算出真实地址
#######ISA_MASK
在runtime源码中有它的定义(其余不相关定义已省略)
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
我们通过位运算后的值是否和我们所想的一样呢?
(五)class和meta-class的结构
我们上面讲到class存放isa、superclass、其他成员变量,属性,对象方法等;meta-class存放isa、superclass、类方法等;class与meta-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;
/* Use `Class` instead of `struct objc_class *` */
虽然我们看到确实好像是这样的,有isa、成员变量、属性、实例方法列表、协议等等。但是很遗憾这个在_OBJC2__
时已经不可用了,我们无法通过一个过时的定义来证明,我们只能看runtime的源码来证明。
我们在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_data_bits_t这个类型我们继续跟进
class_rw_t *data() const {//这个方法很重要,我们进去继续查看
return bits.data();
}
...//还有其他我们前面已经看到过很熟悉的方法比如instanceSize等,不过并不在本次讨论点。略
}
我们发现,
objc_class
是一个结构体,继承自objc_object
,还可以定义方法,这是C++的语法,不要惊讶😺
重要参数:
- Class ISA 继承自
objc_object
- superclass
- cache
- bits class_data_bits_t类型
- data方法 返回
class_rw_t
类型的结构体指针
②class_rw_t
结构
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t version;
uint16_t witness;
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;//对象占用的内存空间(实际占用,未对齐的)
#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;
...//省略
}
④class_data_bits_t
结构:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
...
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);//bits通过与FAST_DATA_MASK位运算后得到class_rw_t类型的结构体指针
}
}
我们进行整理一下:
代码证明底层内存结构
我们从内存结构可以侧面证实,我们现在通过代码证明(自己仿写底层结构进行强制类型转换):
//MJClassInfo.h文件
#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() {
return (mj_objc_class *)((long long)isa & ISA_MASK);
}
};
#endif /* MJClassInfo_h */
main.mm的main函数中调用:
通过断点调试可以直接看到对应的成员变量、对象方法、类方法、属性、协议均可以在结构中可以找到。如下图: