「OC」源码学习——对象的底层探索
「OC」源码学习——对象的底层探索
前言
上次我们说到了源码里面的调用顺序,现在我们继续了解我们上一篇文章没有讲完的关于对象的内容函数,完整了解对象的产生对于isa赋值以及内存申请的内容
函数内容
先把_objc_rootAllocWithZone
函数的内容先贴上来
static ALWAYS_INLINE id
_class_createInstance(Class cls, size_t extraBytes,int construct_flags = OBJECT_CONSTRUCT_NONE,bool cxxConstruct = true,size_t *outAllocatedSize = nil)
{ASSERT(cls->isRealized());//断言确保类已加载并完成内存布局(即 "realized" 状态)bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); //查看父类是否存在C++的析构函数bool hasCxxDtor = cls->hasCxxDtor();//查看是否存在C++的析构函数bool fast = cls->canAllocNonpointer();//是否支持指针优化size_t size;size = cls->instanceSize(extraBytes);//计算所需要的字节数if (outAllocatedSize) *outAllocatedSize = size;//返回计算的字节数id obj = objc::malloc_instance(size, cls);//根据类对象申请对应的内存if (slowpath(!obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {return _objc_callBadAllocHandler(cls);}return nil;}if (fast) {obj->initInstanceIsa(cls, hasCxxDtor);//如果是优化的isa指针先进入进行判断再进入initIsa} else {// Use raw pointer isa on the assumption that they might be// doing something weird with the zone or RR.obj->initIsa(cls);}if (fastpath(!hasCxxCtor)) {return obj;//没有析构函数就可以直接返回了}construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;//添加一个flagreturn object_cxxConstructFromClass(obj, cls, construct_flags);
}
我们来看看instanceSize
这个函数究竟实现了什么,按照instanceSize
->fastInstanceSize
->align16
,依次步入,我们可以看到一下内容
static inline size_t align16(size_t x) {return (x + size_t(15)) & ~size_t(15);
}
这个程序就是一个16字节对齐的算法,先抛开这个算法,若是由我们来设计这个算法我们的思路是什么呢?最简单的就是
(x + 15) / 16 * 16
当然很快我们就意识到了,使用乘法和除法运算的效率和位运算相比实在太低了,正好16是2的4次方,位运算一样的解决啊
(x + 15) >> 4 << 4
OK现在设计完了我们在来看看苹果设计的代码
(x + 15) & ~15
~
有按位取反的意思通过按位与操作,将低 4 位(二进制 1111
)清零,得到最近的 16 的倍数。用size(15)
是为了兼容32位和64机型,因为size_t
是无符号整数类型,其位数由平台决定(32 位系统为 4 字节,64 位系统为 8 字节)。当 x
是 size_t
类型时,size_t(15)
确保运算中的右操作数(15)与左操作数(x
)类型一致,避免隐式转换带来的风险
内存内容探究
GGObject *obj = [GGObject alloc];
NSLog(@"%lu", sizeof(obj));
NSLog(@"%lu", class_getInstanceSize(obj.class));
NSLog(@"%lu", malloc_size((__bridge const void*)(obj)));
根据这个内容我们可以看到自定义类对应的大小
当我们的类被编译了之后,底层会类编译成 isa + 成员变量
,所以在给类的实例分配内存的话这个内存块存储的就是 isa + 成员变量的值
。
内存对齐
学过C语言我们都知道,结构体是有内存对齐这个概念的,当然不同的排列所实现的内存对齐是不同的,例如
struct Mystruct1{char a; //1字节double b; //8字节int c; //4字节short d; //2字节
}Mystruct1;struct Mystruct2{double b; //8字节int c; //4字节short d; //2字节char a; //1字节
}Mystruct2;//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));// 打印:24 - 16
结构体安排的顺序会关系到结构体内存的大小,我们知道OC之中的对象其实就是由结构体构成的,那我们来探究一下不同的属性对对象的内存是否有影响呢
内存重排
对象的本质 = isa + 成员变量的值。
#import <Foundation/Foundation.h>
@interface MyPerson : NSObject
// isa 8字节
@property (nonatomic, copy) NSString *name; // 8字节
@property (nonatomic, copy) NSString *hobby; // 8字节
@property (nonatomic, assign) int age; // 4字节
@property (nonatomic, assign) double height; // 8字节
@property (nonatomic, assign) short number; // 2字节
@end
一共38字节,16字节内存对齐得到总的字节数为48字节,在探究内存问题之前,先学几个lldb命令
lldb指令:p 输出10进制p/x 输出16进制p/0 输出8进制p/t 输出2进制p/f 输出浮点数x 输出地址x/4gx 输出4个字节地址x/6gx 输出6个字节地址
现在我们用lldb进行调试
MyPerson *p = [GGObject alloc];p.name = @"bb";p.hobby = @"吃吃睡睡喝喝";p.height = 1.80;p.age = 26;p.number = 123;
其实尝试着将类之中的属性内容,打印出来可以看到,无论属性怎么排列,这个MyPerson
类age和number这两个属性永远会排在一起,这个就是编译器的内存优化,对属性进行重排
我们看到在类的内部,我们的属性会通过重新排列获得最小内存,接下来我们看看能能不能重排父类的属性和子类属性吧
还是可以看到子类属性并不会跟父类属性合并
@interface JCTestObject : NSObject
{@publicint count;// 若count在此处,为JCTestSubObject实例实际需要40字节,系统为实例分配48字节NSObject *obj1;NSObject *obj2;
// int count; // 若count在此处,为JCTestSubObject实例实际需要32字节,系统为实例分配32字节}
@end
@interface JCTestSubObject : JCTestObject
{@publicint count2;
}
isa走向
之前学习过关于isa指针的部分内容,
结论:
类的实例isa --> 类对象;
类对象isa --> 元类对象;
元类对象isa --> 根元类对象;
根元类对象isa --> 根元类对象自己。
objc_class
与 `objc_object
objc_object
是所有 Objective-C 对象的底层结构体,其定义如下
struct objc_object {isa_t isa; // 指向类对象或元类的指针
};
objc_class
是类的底层结构体,类对象(如 [LGPerson class]
)和元类(metaclass)均为此类型。其继承自 objc_object
,定义如下:
struct objc_class : objc_object {Class superclass; // 父类指针cache_t cache; // 方法缓存(哈希表)class_data_bits_t bits; // 指向类信息(如方法列表、属性等)
};
1
class_getClassMethod
和class_getInstanceMethod
先定义模版类
@interface JCObject : NSObject
{int _sum;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short number;
- (void)speak;
- (void)sayHello;
+ (void)walk;
@end@implementation JCObject- (void)speak {NSLog(@"%s", __func__);
}
+ (void)walk {NSLog(@"%s", __func__);
}-(void) sayHello {NSLog(@"%s", __func__);
}
在完成两个函数
void jcInstanceMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getInstanceMethod(pClass, @selector(speak));Method method2 = class_getInstanceMethod(metaClass, @selector(speak));Method method3 = class_getInstanceMethod(pClass, @selector(walk));Method method4 = class_getInstanceMethod(metaClass, @selector(walk));NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}void jcClassMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getClassMethod(pClass, @selector(speak));Method method2 = class_getClassMethod(metaClass, @selector(speak));Method method3 = class_getClassMethod(pClass, @selector(walk));Method method4 = class_getClassMethod(metaClass, @selector(walk));NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}//main函数
JCObject *person = [JCObject alloc];
Class pClass = object_getClass(person);
jcObjc_copyMethodList(pClass);
jcInstanceMethod_classToMetaclass(pClass);
jcClassMethod_classToMetaclass(pClass);
得到的结果
jcInstanceMethod_classToMetaclass
要了解jcInstanceMethod_classToMetaclass
之中的内容就要从理解class_getInstanceMethod
开始,顾名思义class_getInstanceMethod
这个方法,主要是用于获取实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL。从实例方法存储在类中,类方法存储在元类中
可以得知,JCObject的方法列表打印结果只有sayHello
方法
jcClassMethod_classToMetaclass
需要先了解class_getClassMethod
这个方法,看该方法的源码实现,可以得出class_getClassMethod
的实现是获取类的类方法
,其本质
就是获取元类的实例方法
,最终还是会走到class_getInstanceMethod
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{if (!cls || !sel) return nil;return class_getInstanceMethod(cls->getMeta(), sel);
}⬇️
//获取元类
//在getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
Class getMeta() {if (isMetaClass()) return (Class)this;else return this->ISA();
}
所以我们再来看看结果
对于speak方法存在于类对象之中,而walk方法存在于元类对象之中,所以使用class_getInstanceMethod
可以在类对象之中找到speak方法,而在元类对象之中找到walk的类方法
而对于class_getClassMethod
来说如果不是元类会先找到元类,然后再在元类之中查找类方法
iskindOfClass & isMemberOfClass
- iskindOfClass & isMemberOfClass 类方法调用
//-----使用 iskindOfClass & isMemberOfClass 类方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
- iskindOfClass & isMemberOfClass 实例方法调用
//------iskindOfClass & isMemberOfClass 实例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
isKindOfClass
检查对象是否属于指定类或其继承链中的任意父类。例如,若对象是Person
类的子类实例(如Student
),则[student isKindOfClass:[Person class]]
返回YES
//--isKindOfClass---类方法、对象方法
//+ isKindOfClass:第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {// 获取类的元类 vs 传入类// 根元类 vs 传入类// 根类 vs 传入类// 举例:LGPerson vs 元类 (根元类) (NSObject)for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}//- isKindOfClass:第一次是获取对象类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {
/*
获取对象的类 vs 传入的类
父类 vs 传入的类
根类 vs 传入的类
nil vs 传入的类
*/for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}
isMemberOfClass
严格检查对象是否直接属于指定类,不包含继承链。例如,Student
实例调用isMemberOfClass:[Person class]
会返回NO
,因为其直接类是Student
而非Person
//-----类方法
//+ isMemberOfClass : 获取类的元类,与 传入类对比
+ (BOOL)isMemberOfClass:(Class)cls {return self->ISA() == cls;
}
//-----实例方法
//- isMemberOfClass : 获取对象的类,与 传入类对比
- (BOOL)isMemberOfClass:(Class)cls {return [self class] == cls;
}
参考文章
iOS-底层原理 09:类 & isa 经典面试题分析