欢迎阅读iOS探索系列(按序阅读食用效果更加)
- iOS探索 alloc流程
- iOS探索 内存对齐&malloc源码
- iOS探索 isa初始化&指向分析
- iOS探索 类的结构分析
- iOS探索 cache_t分析
- iOS探索 方法的本质和方法查找流程
- iOS探索 动态方法解析和消息转发机制
- iOS探索 浅尝辄止dyld加载流程
- iOS探索 类的加载过程
- iOS探索 分类、类拓展的加载过程
- iOS探索 isa面试题分析
- iOS探索 runtime面试题分析
- iOS探索 KVC原理及自定义
- iOS探索 KVO原理及自定义
- iOS探索 多线程原理
- iOS探索 多线程之GCD应用
- iOS探索 多线程之GCD底层分析
- iOS探索 多线程之NSOperation
- iOS探索 多线程面试题分析
- iOS探索 细数iOS中的那些锁
- iOS探索 全方位解读Block
写在前面
iOS探索系列前面几篇讲到了对象的初始化、对象的内存分布、对象的isa初始化以及指向分析,本文就来讲讲实例出实例对象的类对象——类
一、类的本质
1.类的本质
objc源码下准备代码
#import <objc/runtime.h>
@interface FXPerson : NSObject
@end
@implementation FXPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
FXPerson *p = [FXPerson alloc];
Class cls = object_getClass(p);
}
return 0;
}
利用clang将OC文件输出cpp文件(仿佛打开了新世界的大门)
Class接收
然后开始大海捞针(开天眼模式)找到了Class的定义
typedef struct objc_class *Class;
想要找到objc_class就搜不到了,但是总觉得它似曾相似,或许能在objc源码找到灵感
在源码中搜索代码的经验 "objc_class :"、 "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继承于objc_object
struct objc_object {
private:
isa_t isa;
public:
...
}
结论:类的本质是objc_class类型的结构体,objc_class继承于objc_object,所以满足万物皆对象
以后不要再在面试的时候回答一句 万物皆对象 就完事了
2.objc_object和NSObject的关系
等等,为什么继承objc_object就满足万物皆对象了???
看过NSObject的定义就知道了
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
仔细比较的话就能看出NSObject和objc_object有着说不清道不明的关系
- 其实
NSObject是objc_object的仿写,和objc_object的定义是一样的,在底层会编译成objc_object - 同理
NSObject类是OC版本的objc_class
3.Class isa??
isa明明是isa_t类型的,为什么注释了一句Class ISA?
- 万物皆对象,用继承于objc_object的Class接收是没问题的
- 强转,方便isa走位时返回类的类型
二、类的结构
从objc_class的定义可以得出,类有4个属性:isa、superclass、cache、bits
1.Class ISA
不但实例对象中有isa指针,类对象中也有isa指针关联着元类
Class本身就是一个指针,占用8字节
2.Class superclass
顾名思义就是类的父类(一般为NSObject)superclass是Class类型,所以占用8字节
3.cache_t cache
虽然对这个属性比较陌生(详见iOS探索 cache_t分析),但是cache在英文中的意思是缓存
cache_t是一个结构体,内存长度有所有元素决定:_buckets是一个指针,占用8字节;mask_t是个int类型,_mask占用4字节;_occupied占用4字节
=>cache_t占用16字节
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
public:
...
};
4.class_data_bits_t bits
又是一个陌生的属性,但是苹果工程师还是蛮友好的,这一看就是存数据的地方
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit)
{
return bits & bit;
}
...
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
};
那么问题来了,类的属性方法都去哪儿了?是在
cache还是在bits? 其实前文中有提到一丢丢——objc_class中有个class_rw_t *data()方法
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;
...
}
三、类的属性方法
为FxPerson添加nickname成员属性、nationality属性变量、walk类方法、fly实例方法
@interface FXPerson : NSObject {
NSString *nickname;
}
@property (nonatomic, copy) NSString *nationality;
+ (void)walk;
- (void)fly;
@end
@implementation FXPerson
+ (void)walk {}
- (void)fly {}
@end
1.类的属性
x/4gx cls打印当前类结构
bits刚好是类的内存首地址+isa、superclass、cache的内存长度
=> 0x100001560+32字节 = 0x100001580
po打印不出来,那就类型强转打印输出bits的内存地址
根据class_rw_t *data() { return bits.data(); }打印bits.data()
再往里面探索进去(这比西天取经还难)
天呐!怎么只有一个nationality了?难道我FXPerson类只配拥有“国籍”不配拥有“姓名”?让我静静... 既然此路不通,那么换条路走走(内心很不情愿)
在一顿猛如虎的操作(开天眼)之后,发现了class_rw_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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
清空控制台输出ro,跟class_ro_t的结构类型一摸一样
打印ro的baseProperties
天呐?这个成员变量nickname是迷路了吗?等等!可能是自己迷路了!
尝试了一下打印ivars找到了nickname,但是为什么count = 2呢?
ivar_list_t继承entsize_list_tt,而后者又有个get(uint32_t i)方法
struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
bool containsIvar(Ivar ivar) const {
return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
}
};
struct entsize_list_tt {
...
Element& get(uint32_t i) const {
assert(i < count);
return getOrEnd(i);
}
...
}
如愿拿到了nickname和nationality,但是这个nationality长得有点不一样
仔细想想这不就是编译器会在底层自动将属性变量生成一个成员变量_nationality(_前缀+属性变量)
2.类的方法
打印ro的baseMethodList
系统在底层添加了一个c++的.cxx_destruct方法,同时编译器还在底层帮属性变量生成了一个setter方法和getter方法
但是FXPerson的walk类方法又被吃了...
无奈之下只能开大招(开天眼)在元类的data的ro的baseMethodList找到了
其实也很好理解:类方法可以理解成元类对象的实例方法,因此存在元类中
3.结论
- 成员变量存放在
ivar - 属性存放在
property,同时也会存一份在ivar,并生成setter、getter方法 - 对象方法存放在
类里面 - 类方法存放在
元类里面
4.API验证
利用底层开放的API可以验证以上结论
void testObjc_copyIvar_copyProperies(Class cls) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (unsigned int i=0; i < count; i++) {
Ivar const ivar = ivars[i];
//获取实例变量名
const char*cName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:cName];
NSLog(@"class_copyIvarList:%@",ivarName);
}
free(ivars);
unsigned int pCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &pCount);
for (unsigned int i=0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//获取属性值
NSLog(@"class_copyProperiesList:%@",propertyName);
}
free(properties);
}
void testObjc_copyMethodList(Class cls) {
unsigned int count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method, name: %@", key);
}
free(methods);
}
void testInstanceMethod_classToMetaclass(Class cls) {
const char *className = class_getName(cls);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(cls, @selector(walk));
Method method2 = class_getInstanceMethod(metaClass, @selector(fly));
Method method3 = class_getInstanceMethod(cls, @selector(walk));
Method method4 = class_getInstanceMethod(metaClass, @selector(fly));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
void testClassMethod_classToMetaclass(Class cls) {
const char *className = class_getName(cls);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(cls, @selector(walk));
Method method2 = class_getClassMethod(metaClass, @selector(fly));
Method method3 = class_getClassMethod(cls, @selector(walk));
Method method4 = class_getClassMethod(metaClass, @selector(fly));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
void testIMP_classToMetaclass(Class cls) {
const char *className = class_getName(cls);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(cls, @selector(walk));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(fly));
IMP imp3 = class_getMethodImplementation(cls, @selector(walk));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(fly));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
5.clang编译验证
其实直接用clang编译也能看出蹊跷
关于v@:这个玩意,在苹果开发者文档上也有介绍
四、写在后面
虽然一直碰壁,但是本文最后还是找到了想要的答案,有的时候你离成功只差一步之遥,也有的时候是你选错了研究方向。学习是如此,人生亦是如此,道阻且长,且行且珍惜。幸运的是有前人替我们踩坑,我们可以吸取前人的教训少走弯路