元类
- 实例对象是由类生产出来的,例如YYPerson这是一个类,通过YYPerson类我们可以调用其alloc方法,创建出一个实例对象person,
YYPerson *person = [[YYPerson alloc]init]
,可以说类就像一个工厂一样, 工厂可以生产出成千上万的产品,这些产品的属性和行为都是一样,这些产品在编程的世界里就是所谓的实例对象,可以这么说生产实例对象的工厂我们称之为类
,实例对象的isa指针指向生产实例对象的类
; - 正所谓在面向对象的编程世界里,万物皆对象,所以
生产实例对象的类,其本质也是一个对象
,那么生产类对象的工厂我们称之为元类
,类对象的isa指针指向生产类对象的类,也就是所谓的元类
; - 元类是系统生成的,其定义和创建都是由编译器完成;
- 元类 是描述 类对象 的类,每个类都有一个独一无二的
元类用来 存储类方法的相关信息
; - 元类本身是没有名称的,由于与类相关联,所以使用了和类名一样的名称;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//实例对象
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
NSLog(@"实例对象 -- %p -- %p",obj1,obj2);
//类对象
Class objClass1 = [obj1 class];
Class objClass2 = [obj2 class];
Class objClass3 = object_getClass(obj1);
Class objClass4 = object_getClass(obj2);
Class objClass5 = [NSObject class];
NSLog(@"类对象 -- %p -- %p -- %p -- %p -- %p",objClass1,objClass2,objClass3,objClass4,objClass5);
//元类对象
Class metaClass = object_getClass(objClass1);
NSLog(@"元类对象 -- %p",metaClass);
//判断类 是否是元类对象
bool isMetaClass = class_isMetaClass(metaClass);
NSLog(@"isMetaClass = %d",isMetaClass);
}
return 0;
}
- 调试结果如下:
- 可以看出,
不同的实例对象占据不同的内存空间
,类对象在内存中只占用一份内存空间
,元类对象在内存中也只占用一份内存空间
; -
object_getClass()
函数,参数传入实例对象,返回的是类对象; -
object_getClass()
函数,参数传入类对象,返回的是元类对象; -
class_isMetaClass()
函数,判断类 是否是元类对象; - 下面我们通过代码来验证一下上面所阐述的内容:
- YYPerson继承自NSObject;
- YYStudent继承自YYPerson;
- 当代码执行到断点处停下,进行LLDB命令调试,结果如下所示:
-
p/x person
读取实例对象person在内存中首地址; -
p/x 0x001d800100002375 & 0x00007ffffffffff8ULL
将实例对象person的isa的值与isa的掩码0x00007ffffffffff8ULL做位与运算得到YYPerson类的地址值0x0000000100002370
,其本质就是YYPerson类对象; -
x/4gx 0x0000000100002370
获取YYPerson类对象在内存中信息数据;其中0x0000000100002348
就是YYPerson类对象的isa指针的值; -
p/x 0x0000000100002348 & 0x00007ffffffffff8ULL
将YYPerson类对象的isa与isa的掩码0x00007ffffffffff8ULL做位与运算得到YYPerson元类的地址值,即0x0000000100002348
,其本质还是YYPerson,这就证实了上面所阐述的元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称;
如何在LLDB调试控制台获取实例对象person的类对象YYPerson的内存地址?
- 第一种方式:p/x [person class] 以16进制打印YYPerson类对象的内存地址;
- 第二种方式:p/x [YYPerson class] 以16进制打印YYPerson类对象的内存地址;
- 第三种方式:p/x object_getClass(person) 以16进制打印YYPerson类对象的内存地址;
- 第四种方式:x/4gx person 首先读取实例对象person在内存中数据,前8个字节是isa指针的值,然后 p/x isa的值 & isa掩码值,得到的就是YYPerson类对象的内存地址;
- p/x aaa 获取的是aaa的内存地址;
- x/4gx aaa 获取的是aaa内存地址中的数据内容;
isa指针的指向
由上面的内容我们知道实例对象的isa指向类对象
,类对象的isa指向元类对象
,那元类对象的isa指向哪里?会这样没有终点的一直指向下去么?
- 通过上面的LLDB调试分析可以得出下面的结论:
- 实例对象 的 isa 指向 类对象;
- 类对象 的 isa 指向 其元类对象;
- 元类对象 的 isa 指向 根元类,即NSObject;
- 根元类 的 isa 指向 它自己本身;
通过类Class所创建的实例对象在内存中可以成千上万,那么类对象在内存中占几份?
#import <Foundation/Foundation.h>
#import "YYPerson.h"
#import "YYStudent.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
Class class1 = [YYPerson class];
Class class2 = [YYPerson alloc].class;
Class class3 = object_getClass(person);
NSLog(@"\nclass1 = %p\nclass2 = %p\nclass3 = %p", class1, class2, class3);
}
return 0;
}
- 控制台的打印结果如下:
- 可以看出
类对象在内存中只占有一份
.
引用官方一张 关于 isa走向 与 类继承关系图
Snip20210210_143.pngisa走向(虚线部分)
- 实例对象(Instance)的isa指向类对象class;
- 类对象的isa指向元类对象meta class;
- 元类对象的isa指向根元类 root meta class(NSObject);
- 根元类对象的isa指向自己本身(NSObject);
类的继承superClass的走向(实线部分)
- 类对象之间的继承关系:
- 子类的SuperClass指向其父类Superclass;
- 父类的SuperClass指向根类RootClass,这里的根类就是NSOject;
- 根类的SuperClass指向nil,可以理解成无中生有;
- 元类对象之间也存在继承关系:
- 子类的元类的SuperClass指向父类的元类Superclass(meta);
- 父类的元类的SuperClass指向根元类RootClass(metal);
- 根元类的SuperClass指向根类RootClass 也就是NSObject;
- 根类的SuperClass指向nil,可以理解成无中生有;
- 实例对象之间没有继承关系,类与元类之间才有继承关系;
- 通过例子来实际阐述上面的关系图,YYStudent与YYPerson:
- isa的走向链:
- student的走向链:student子类实例对象 --> YYStudent子类 --> YYStudent子类的元类 --> NSObject根元类 --> NSObject根元类自身
- Person的走向链:person子类实例对象 --> YYPerson子类 --> YYPerson子类的元类 --> NSObject根元类 --> NSObject根元类自身
- 类的继承关系链:
- YYStudent子类 --> YYPerson父类 --> NSObject根类 --> nil
- YYStudent子类的元类 --> YYPerson父类的元类 --> NSObject根元类 --> NSObject根类 --> nil
- isa指针与superClass指针在方法调用中,起到至关重要的作用;
类的结构
- 在 iOS底层系列02-- objc4-781源码中的objc_class与objc_object中我们知道objc_class与objc_object这两个结构体且objc_class继承自objc_object;
-
Class类
是以objc_class为模版进行创建的
; -
OC任意对象id
是以objc_object为模版进行创建的
; - 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() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
......
}
-
isa指针
:继承自objc_object,占8个字节; -
superclass指针
:属于Class类型,是一个指针,占8个字节; -
cache成员
:是一个cache_t结构体
,其内存大小需要根据其内部的成员来确定,详细计算见下面; -
bits成员
:是一个class_data_bits_t
结构体,将Class的首地址进行偏移,偏移量为面3个成员的内存大小总和,才能获取到bits成员的首地址,bits成员存储了类的相关信息数据;
计算cache成员的内存大小
- 剔除不会占用类空间的const、void、static和函数,结构体如下所示:
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
- _buckets是结构体指针类型占8个字节;
- mask_t是unsigned int 占4个字节;
- uintptr_t是unsigned long 占8个字节;
- uint16_t是unsigned short 占2个字节;
- 所以cache_t结构体不论哪种CACHE_MASK_STORAGE,其
内存大小都会占8+4+2+2 = 16个字节
;
探索bits成员
- 根据上面关于cache内存大小的计算结果,然后isa指针与superclass指针分别占8个字节,再根据
内存偏移
,我们需要将class类的首地址进行32字节的偏移
,方可得到bits成员的首地址; - YYPerson.h文件内容:
@interface YYPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;
- (void)walk;
- (void)eat;
+ (void)sing;
@end
- LLDB调试结果如下所示:
- p/x YYPerson.class 获取
YYPerson类的首地址
- YYPerson类的首地址为0x100002360,那么
内存偏移32个字节
即为0x100002380,是bits成员的首地址,注意是16进制的换算
- p (class_data_bits_t *) 0x100002360 地址
强转为class_data_bits_t类型
- p $1->data(),bits调用函数data(),
获取class_rw_t结构体
进入class_rw_t结构体
- 在class_rw_t结构体定义中看到三个函数分别是获取
方法列表
,属性列表
与协议列表
的; -
method_array_t
,property_array_t
,protocol_array_t
均是一个二维数组; -
method_array_t
中存储的是method_list_t
一维数组,method_list_t
中存储的是method_t
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
- LLDB调试属性列表properties()结果如下:
- p $3.properties() 调用class_rw_t结构体中的properties(),获取属性列表数组property_list_t;
- 可通过p $6.get(0) 获取属性name;
- 可通过p $6.get(1) 获取属性weight;
- LLDB调试实例方法列表methods()结果如下:
- p $3.methods() 调用class_rw_t结构体中的methods(),获取方法列表数组method_list_t,注意此方法列表是
实例方法列表
; - 可通过p $12.get(i)获取对应的方法;
- LLDB调试实例变量的存储:
- 首先在class_rw_t结构体中存在下面这么一个函数ro(),其返回值为
class_ro_t结构体
;
const class_ro_t * ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
- 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;
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
......
};
- 有一个
ivars
成员,此成员是用来存储实例变量的; - ro中的方法列表,属性列表与成员变量列表均是一维数组;
- p $5.ro() 获取class_ro_t结构体;
- p $7.ivars 获取实例变量列表;
- p $9.get(0) 获取实例变量 _name;
- p $9.get(1) 获取实例变量 _weight;
- 总结:
- 类的属性列表存储在类的bits属性中,可通过
bits --> data() --> properties()
获取属性列表; - 类的实例变量列表存储在类的bits属性中,可通过
bits --> data() -->ro() --> ivars
获取实例变量列表; - 类的实例方法列表存储在类的bits属性中,可通过
bits --> data() --> methods()
获取实例方法列表;
- 类的属性列表存储在类的bits属性中,可通过
探索类方法的存储位置
- 类的bits成员,可通过data() --> methods()获取的是类的实例方法,并没有看到类方法,猜测类方法应该存储在元类的bits成员中,下面通过LLDB来验证一下:
- p/x YYPerson.class 获取YYPerson类的首地址;
- x/4gx 0x00000001000023f0 读取YYPerson类 前32个字节的内存数据,其中0x00000001000023c8是isa的值,然后其与isa mask做位与运算得到元类的首地址0x00000001000023c8;
- 元类首地址偏移32个字节,得到元类的bits成员,
- 接下来的获取类方法的步骤与获取类的实例方法步骤相似;
- 总结:
- 类的实例方法是存储在类的bits成员中,
类 --> bits --> data() --> methods()
; - 类的类方法是存储在元类的bits成员中,
元类 --> bits --> data() --> methods()
;
- 类的实例方法是存储在类的bits成员中,
通过MJClassInfo.h查看类对象的数据结构
- 上面是通过LLDB控制台调试分析类对象的数据结构,下面再提供一种更直观的方式,查看类对象的数据结构,引用MJ大神写的MJClassInfo.h文件,代码实现如下:
#import <Foundation/Foundation.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 */
- 工程测试代码如下:
#import <Foundation/Foundation.h>
#import "YYPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "MJClassInfo.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
mj_objc_class *personClass = (__bridge mj_objc_class *)([YYPerson class]);
class_rw_t *personClassData = personClass->data();
class_rw_t *personMetaClassData = personClass->metaClass()->data();
}
return 0;
}
- 调试类对象YYPerson的结果如下:
- 可以看到实例变量与方法都是存储在
class_ro_t
中,class_rw_t
不存储实例变量与方法,只提供访问实例变量与方法的函数,具体见class_rw_t的结构体定义; - 调试元类对象的结构如下:
常见API
- 首先准备测试代码YYPerson.h文件:
@interface YYPerson : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger weight;
- (void)walk;
- (void)eat;
+ (void)sing;
@end
class_getInstanceMethod(Class cls, SEL sel)
-
class_getInstanceMethod(Class cls, SEL sel)
获取类的实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
;
void YYClass_getInstanceMethod(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(walk));
Method method2 = class_getInstanceMethod(metaClass, @selector(walk));
Method method3 = class_getInstanceMethod(pClass, @selector(sing));
Method method4 = class_getInstanceMethod(metaClass, @selector(sing));
NSLog(@"%s - %p - %p - %p - %p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
Class cls = object_getClass(person);
YYClass_getInstanceMethod(cls);
}
return 0;
}
- 测试代码的结果分析:
1> method1 --> 0x100003270 有值 YYPerson类中有实例方法walk;
2> method2 --> 0x0 无值 YYPerson元类中没有实例方法walk,其查找顺序为: YYPerson元类 --> 根元类 --> 根类 --> nil,在元类的继承链上查找
;
3> method3 --> 0x0 无值 YYPerson类中没有实例方法sing,其查找顺序为YYPerson类 --> 根类 --> nil,在类的继承链上查找
;
4> method4 --> 0x100003208 有值 YYPerson元类中有实例方法sing;
5>元类中查找实例方法就是查找类方法
class_getClassMethod(Class cls, SEL sel)
-
class_getClassMethod(Class cls, SEL sel)
获取类的类方法(元类的实例方法),如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
;
其底层实现为:
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
-
cls->getMeta()
获取元类 表明类方法的查找是在元类中; -
若传进来的class为元类,就直接返回元类;若传进来的class为非元类,则会返回class的isa,即class的元类
; - 测试代码:
void YYclass_getClassMethod(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(walk));
Method method2 = class_getClassMethod(metaClass, @selector(walk));
Method method3 = class_getClassMethod(pClass, @selector(sing));
Method method4 = class_getClassMethod(metaClass, @selector(sing));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
Class cls = object_getClass(person);
YYclass_getClassMethod(cls);
}
return 0;
}
- 测试代码的结果分析:
1> method1 --> 0x0 无值 传参YYPerson为非元类,则获取YYPerson的isa即YYPerson的元类,元类中没有walk方法,且在元类的继承链上查找,都没有找到
;
2> method2 --> 0x0 无值 传参YYPerson为元类,而元类中没有walk方法,且在元类的继承链上查找,都没有找到
;
3> method3 --> 0x1000031e0 有值 传参YYPerson为非元类,则获取YYPerson的isa即YYPerson的元类,元类中有sing方法;
4> method4 --> 0x1000031e0 有值 传参YYPerson为元类,元类中有sing方法;
class_getMethodImplementation(Class cls, SEL sel)
-
class_getMethodImplementation(Class cls, SEL sel)
获取类中某个方法的是实现;
其底层实现为:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
- 若方法实现imp不存在,会进入消息的转发,也会返回一个函数指针;
- 测试代码如下:
void YYClass_getMethodImplementation(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(walk));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(walk));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sing));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sing));
NSLog(@"%p - %p - %p - %p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
Class cls = object_getClass(person);
YYClass_getMethodImplementation(cls);
}
return 0;
}
- 测试代码的结果分析:
1> imp1 --> 0x100001c20 有值 YYPerson类有walk实例方法实现,则返回walk实现的函数指针;
2> imp2 --> 0x10037eac0 有值 YYPerson元类中没有walk实例方法(元类的实例方法也就是类方法),且在元类的继承链上查找,都没有找到
;进入消息转发,返回消息转发的函数指针;
3> imp3 --> 0x10037eac0 有值 YYPerson类没有sing实例方法;在类的继承链上查找,都没有找到
,进入消息转发,返回消息转发的函数指针;
4> imp4 --> 0x100001bb0 有值 YYPerson元类中有sing实例方法(即类方法),则返回sing实现的函数指针;
- (BOOL)isKindOfClass:(Class)cls与+ (BOOL)isKindOfClass:(Class)cls
-
- (BOOL)isKindOfClass:(Class)cls
判断实例对象是否属于指定参数类,会在实例对象的类的继承链上判断,其底层实现为:
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
-
可以看到首先获取对象的类与传参类进行比较,如果相等直接返回YES,
如果不相等会在类的继承链上 父类 --> 根类 --> nil 循环与传参类进行比较
; -
+ (BOOL)isKindOfClass:(Class)cls
判断类是否属于指定参数类,会在类的元类的继承链上判断;
其底层实现为:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
-
可以看到首先获取类的元类与传参类进行比较,如果相等直接返回YES,
如果不相等会在元类的继承链上 父类的元类 --> 根元类 --> 根类 --> nil 循环与传参类进行比较
; -
测试代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
BOOL re1 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[YYPerson alloc] isKindOfClass:[YYPerson class]];
BOOL re3 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re4 = [(id)[YYPerson class] isKindOfClass:[YYPerson class]];
}
return 0;
}
- 发现一个问题isKindOfClass函数不论实例方法还是类方法都不会走上面的底层实现,分析其汇编代码如下:
- isKindOfClass函数其实例方法与类方法,底层调用的都是
objc_opt_isKindOfClass函数
,其代码实现为:
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
- 若obj是实例对象,obj->getIsa()获取的是类,若与传参类一直不等,会在类的继承链上依次进行比较;
- 若obj是类,obj->getIsa()获取的是元类,若与传参类一直不等,会在元类的继承链上依次进行比较;
- 底层这么调用,主要是因为在llvm中编译时对其进行了优化处理。
- 上面测试代码的结果分析:
- re1为YES,[NSObject alloc]实例对象的类为NSObject(根类)与传参类NSObject(根类)相等;
- re2为YES,[YYPerson alloc]实例对象的类为YYPerson与传参类YYPerson相等;
- re3为YES,[NSObject class]类的元类(根元类)与传参类NSObject(根类)不相等,然后在元类的继承链上,父类的元类 --> 根元类 --> 根类 --> nil,依次比较,根元类的父类是根类NSObject与传参类NSObject(根类)相等;
- re4为NO,[YYPerson class]类的元类与传参类YYPerson(类)不相等,然后在元类的继承链上,父类的元类 --> 根元类 --> 根类 --> nil,依次比较,都不相等;
- (BOOL)isMemberOfClass:(Class)cls与+ (BOOL)isMemberOfClass:(Class)cls
-
- (BOOL)isMemberOfClass:(Class)cls
判断实例对象是否属于指定参数类,不涉及类的继承链,其底层实现为:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- 可以看到仅仅只获取实例对象的类与传参类进行比较;
-
+ (BOOL)isMemberOfClass:(Class)cls
判断类是否属于指定参数类,不涉及元类的继承链;
其底层实现为:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- 可以看到仅仅只获取类的元类与传参类进行比较;
- 测试代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
YYPerson *person = [[YYPerson alloc]init];
person.name = @"liyanyan";
person.weight = 130;
BOOL re5 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re6 = [(id)[YYPerson alloc] isMemberOfClass:[YYPerson class]];
BOOL re7 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re8 = [(id)[YYPerson class] isMemberOfClass:[YYPerson class]];
}
return 0;
}
- isMemberOfClass函数调用的是上面的底层实现;与isKindOfClass函数底层调用不同;
- re5为YES,[NSObject alloc]的类为NSObject,与传参类NSObject相等;
- re6为YES,[YYPerson alloc]的类为YYPerson,与传参类YYPerson相等;
- re7为NO,[NSObject class]的元类即根元类与传参类NSObject(根类)不相等;
- re8为NO,[YYPerson class]的元类与传参类YYPerson(类)不相等;
如何获取Class对象
- 首先Class对象包含两种分别为:类对象和元类对象;
-
- (Class)class 与 + (Class)class
:获取的是类对象; -
objc_getClass("类字符串")
:获取的是类对象; -
object_getClass(id obj)
:参数传入实例对象,返回类对象
,参数传入类对象,返回元类对象
; - 代码实现如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//实例对象
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
NSLog(@"实例对象 -- %p -- %p",obj1,obj2);
//类对象
Class objClass1 = [obj1 class];
Class objClass2 = [obj2 class];
Class objClass3 = [NSObject class];
Class objClass4 = object_getClass(obj1);
Class objClass5 = objc_getClass("NSObject");
Class objClass6 = object_getClass(objClass1);
NSLog(@"class对象 -- %p -- %p -- %p -- %p -- %p -- %p",objClass1,objClass2,objClass3,objClass4,objClass5,objClass6);
NSLog(@"class对象 -- %p",objClass6);
}
return 0;
}
- 调试结果:
网友评论