第5课-类的原理分析上-1
[TOC]
5.1 类的分析
5.1.1 元类
首先我们看一下下面实例,引出元类
ZBPerson继承NSObject
@interface ZBPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)say666;
@end
ZBTeacher继承ZBPerson
@interface ZBTeacher : ZBPerson
@end
接下来,我们进行lldb调试,如下图:
在上图中,我们有个疑问点? 为什么通过p/x 0x011d8001000082c5 & 0x0000000ffffffff8ULL
与通过p/x 0x0000000100008298 & 0x0000000ffffffff8ULL
获得的都是ZBPerson
对象呢?
- 0x011d8001000082c5是person对象isa首地址,所以
0x011d8001000082c5 & 0x0000000ffffffff8ULL
得到的即为person对象的isa指针关联的ZBPerson类 - 0x0000000100008298是上面第一步获取的ZBPerson类的isa指针地址,那么它指向的是ZBPerson类的类,我们这里称之为ZBPerson的元类。
- 所以上面之所以打印相同的值,是因为元类造成的。
接下来我们进一步探索元类。
首先我们来解释一下元类:
- 我们都知道对象的isa是指向类,类其实也是一个对象,可以称为类对象,其isa指针指向苹果定义的元类
- 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
- 元类是类对象的类,每个类都有一个独一无二的元类用来存储类方法的相关信息。
- 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
5.1.2 isa走位以及类的继承关系图
接下来我们探索一下isa的走位以及类的继承关系
通过上面的分析我们可以得出如下结论:
- 对象的isa走向(红色部分):
- 对象的isa指针->对象所对应类的地址
- 对象类的isa指针->元类对象的地址
- 元类对象的isa指针->NSObject根元类对象的地址
- NSObject根元类对象的isa指针依然指向NSObject元类对象自身。
- NSObject类对象的isa指针走向(蓝色部分):
- NSObject类对象的isa指针->NSObject根元类对象的地址
- NSObject根元类对象的isa指针依然指向NSObject元类对象自身
- 补充:
- 对的isa指向类信息
- 类的isa与元类地址相同
注意对象isa指针和NSObjcet类的isa指针最后都指向了NSObject根元类,而且二者指向的根元类地址相同,即NSObject(根元类)在内存中永远只存在一份
[面试题]:类存在几份? 由于类的信息在内存中永远只存在一份,所以 类对象只有一份
接下来我们再来看一下著名的isa走位以及继承关系图
isa走位关系以及类的继承关系如下: 子类isa走位
- 子类实例对象(Instance of Subclass)的isa指向子类(Subclass(class))
- 子类(Subclass(class))的isa指向子类的元类(Subclass(meta))
- 子类的元类(Subclass(meta))的isa指向根元类(Root class (meta))
- 根元类(Root class (meta))的isa指向根元类(Root class (meta))
代码验证:
// 继承关系:ZBTeacher -> ZBPerson -> NSObject
// ZBTeacher实例
ZBTeacher *tObject = [ZBTeacher alloc];
// ZBTeacher类
Class tClass = object_getClass(tObject);
// ZBTeacher元类
Class tMetaClass = object_getClass(tClass);
// ZBTeacher根元类
Class tRootClass = object_getClass(tMetaClass);
// ZBTeacher根元类的元类
Class tRootRootClass = object_getClass(tRootClass);
NSLog(@"\n%p ZBTeacher子类实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",tObject,tClass,tMetaClass,tRootClass,tRootRootClass);
// 打印
0x101206330 ZBTeacher子类实例对象
0x100008330 类
0x100008308 元类
0x7ff854c59d20 根元类
0x7ff854c59d20 根根元类
父类isa走位
- 父类实例对象(Instance of superclass)的isa指向父类(superclass(class))
- 父类(superclass(class))的isa指向父类的元类(superclass(meta))
- 父类的元类(superclass(meta))的isa指向根元类(Root class (meta))
- 根元类(Root class (meta))的isa指向根元类(Root class (meta))
代码验证
// ZBPerson实例
ZBPerson *pObject = [ZBPerson alloc];
// ZBPerson类
Class pClass = object_getClass(pObject);
// ZBPerson元类
Class pMetaClass = object_getClass(pClass);
// ZBPerson根元类
Class pRootClass = object_getClass(pMetaClass);
// ZBPerson根元类的元类
Class pRootRootClass = object_getClass(pRootClass);
NSLog(@"\n%p ZBPerson父类实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",pObject,pClass,pMetaClass,pRootClass,pRootRootClass);
// 打印
0x101604720 ZBPerson父类实例对象
0x1000082e0 类
0x1000082b8 元类
0x7ff854c59d20 根元类
0x7ff854c59d20 根根元类
根类(NSObject)走位
- 根类实例对象(Instance of Root class)的isa指向根类(Root class(class))
- 根类(Root class(class))的isa指向根元类(Root class(meta))
- 根元类(Root class (meta))的isa指向根元类(Root class (meta))
代码验证:
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p NSObject实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass, rootRootMetaClass);
// 结果打印
0x10070d2a0 NSObject实例对象
0x7ff854c59d48 类
0x7ff854c59d20 元类
0x7ff854c59d20 根元类
0x7ff854c59d20 根根元类
继承关系如下:
子类(Subclass(class))===>继承===> 父类(superclass(class))===>继承===> NSObject根类(Root class(class) 代码验证:
// ZBTeacher类的父类 Class tFatherClass = class_getSuperclass(tClass); // ZBTeacher类的的祖父 Class tGrandFatherClass = class_getSuperclass(tFatherClass); // ZBTeacher类的的曾祖 Class tGrandGrandFatherClass = class_getSuperclass(tGrandFatherClass); NSLog(@"\n%p ZBTeacher类的父类\n%p ZBTeacher类的的祖父\n%p ZBTeacher类的的曾祖",tFatherClass,tGrandFatherClass,tGrandGrandFatherClass); // 结果打印 0x1000082e0 ZBTeacher类的父类 0x7ff854c59d48 ZBTeacher类的的祖父 0x0 ZBTeacher类的的曾祖 (lldb) po 0x0 <nil>
子类的元类(Subclass(meta)) ===>继承===> 父类的元类(superclass(meta)) ===>继承===> 根元类(Root class (meta)) ===>继承===> NSObject根类(Root class(class) 代码验证:
// ZBTeacher元类的父类 Class tMetaFatherClass = class_getSuperclass(tMetaClass); // ZBTeacher元类的的祖父 Class tMetaGrandFatherClass = class_getSuperclass(tMetaFatherClass); // ZBTeacher元类的的曾祖 Class tMetaGrandGrandFatherClass = class_getSuperclass(tMetaGrandFatherClass); NSLog(@"\n%p ZBTeacher元类的父类\n%p ZBTeacher元类的的祖父\n%p ZBTeacher元类的的曾祖",tMetaFatherClass,tMetaGrandFatherClass,tMetaGrandGrandFatherClass); // 结果打印 0x1000082b8 ZBTeacher元类的父类 0x7ff854c59d20 ZBTeacher元类的的祖父 0x7ff854c59d48 ZBTeacher元类的的曾祖 (lldb) po 0x7ff854c59d48 NSObject
NSObject的父类为nil 代码验证:
// NSObject 根类特殊情况 // NSObject根类的父类 Class objectSuperClass = class_getSuperclass(NSObject.class); // 根元类的父类 Class rootMetaFatherClass = class_getSuperclass(rootMetaClass); NSLog(@"\n%p NSObject根类的父类\n%p 根元类的父类",objectSuperClass,rootMetaFatherClass); // 结果打印 0x0 NSObject根类的父类 0x7ff854c59d48 根元类的父类
注意:对象与对象之间没有继承关系,只有类与类才有继承关系
所以 NSObject 是最终的根类,是万物之祖。
5.2 类结构分析
5.2.1 objc_class与objc_object的关系
我们直接查看一下objc4的源码,可以查看objc_class的源码有两份,其中一份 在objc-runtime-old.h文件中,这里的定义是老版本的,已经不是最新的代码了
另一份最新的代码在objc-runtime-new.h文件中 源代码如下:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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 getSuperclass() const {
#if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
if (superclass == Nil)
return Nil;
#if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL
void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
if ((void *)superclass == stripped) {
void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
if ((void *)superclass != resigned)
return Nil;
}
#endif
void *result = ptrauth_auth_data((void *)superclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
return (Class)result;
# else
return (Class)ptrauth_strip((void *)superclass, ISA_SIGNING_KEY);
# endif
#else
return superclass;
#endif
}
void setSuperclass(Class newSuperclass) {
#if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
superclass = (Class)ptrauth_sign_unauthenticated((void *)newSuperclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS));
#else
superclass = newSuperclass;
#endif
}
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);
}
void clearInfo(uint32_t clear) {
ASSERT(isFuture() || isRealized());
data()->clearFlags(clear);
}
// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear) {
ASSERT(isFuture() || isRealized());
ASSERT((set & clear) == 0);
data()->changeFlags(set, clear);
}
#if FAST_HAS_DEFAULT_RR
bool hasCustomRR() const {
return !bits.getBit(FAST_HAS_DEFAULT_RR);
}
void setHasDefaultRR() {
bits.setBits(FAST_HAS_DEFAULT_RR);
}
void setHasCustomRR() {
bits.clearBits(FAST_HAS_DEFAULT_RR);
}
#else
bool hasCustomRR() const {
return !(bits.data()->flags & RW_HAS_DEFAULT_RR);
}
void setHasDefaultRR() {
bits.data()->setFlags(RW_HAS_DEFAULT_RR);
}
void setHasCustomRR() {
bits.data()->clearFlags(RW_HAS_DEFAULT_RR);
}
#endif
#if FAST_CACHE_HAS_DEFAULT_AWZ
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
#else
bool hasCustomAWZ() const {
return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
bits.data()->setFlags(RW_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ);
}
#endif
#if FAST_CACHE_HAS_DEFAULT_CORE
bool hasCustomCore() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
}
void setHasDefaultCore() {
return cache.setBit(FAST_CACHE_HAS_DEFAULT_CORE);
}
void setHasCustomCore() {
return cache.clearBit(FAST_CACHE_HAS_DEFAULT_CORE);
}
#else
bool hasCustomCore() const {
return !(bits.data()->flags & RW_HAS_DEFAULT_CORE);
}
void setHasDefaultCore() {
bits.data()->setFlags(RW_HAS_DEFAULT_CORE);
}
void setHasCustomCore() {
bits.data()->clearFlags(RW_HAS_DEFAULT_CORE);
}
#endif
#if FAST_CACHE_HAS_CXX_CTOR
bool hasCxxCtor() {
ASSERT(isRealized());
return cache.getBit(FAST_CACHE_HAS_CXX_CTOR);
}
void setHasCxxCtor() {
cache.setBit(FAST_CACHE_HAS_CXX_CTOR);
}
#else
bool hasCxxCtor() {
ASSERT(isRealized());
return bits.data()->flags & RW_HAS_CXX_CTOR;
}
void setHasCxxCtor() {
bits.data()->setFlags(RW_HAS_CXX_CTOR);
}
#endif
#if FAST_CACHE_HAS_CXX_DTOR
bool hasCxxDtor() {
ASSERT(isRealized());
return cache.getBit(FAST_CACHE_HAS_CXX_DTOR);
}
void setHasCxxDtor() {
cache.setBit(FAST_CACHE_HAS_CXX_DTOR);
}
#else
bool hasCxxDtor() {
ASSERT(isRealized());
return bits.data()->flags & RW_HAS_CXX_DTOR;
}
void setHasCxxDtor() {
bits.data()->setFlags(RW_HAS_CXX_DTOR);
}
#endif
#if FAST_CACHE_REQUIRES_RAW_ISA
bool instancesRequireRawIsa() {
return cache.getBit(FAST_CACHE_REQUIRES_RAW_ISA);
}
void setInstancesRequireRawIsa() {
cache.setBit(FAST_CACHE_REQUIRES_RAW_ISA);
}
#elif SUPPORT_NONPOINTER_ISA
bool instancesRequireRawIsa() {
return bits.data()->flags & RW_REQUIRES_RAW_ISA;
}
void setInstancesRequireRawIsa() {
bits.data()->setFlags(RW_REQUIRES_RAW_ISA);
}
#else
bool instancesRequireRawIsa() {
return true;
}
void setInstancesRequireRawIsa() {
// nothing
}
#endif
void setInstancesRequireRawIsaRecursively(bool inherited = false);
void printInstancesRequireRawIsa(bool inherited);
#if CONFIG_USE_PREOPT_CACHES
bool allowsPreoptCaches() const {
return !(bits.data()->flags & RW_NOPREOPT_CACHE);
}
bool allowsPreoptInlinedSels() const {
return !(bits.data()->flags & RW_NOPREOPT_SELS);
}
void setDisallowPreoptCaches() {
bits.data()->setFlags(RW_NOPREOPT_CACHE | RW_NOPREOPT_SELS);
}
void setDisallowPreoptInlinedSels() {
bits.data()->setFlags(RW_NOPREOPT_SELS);
}
void setDisallowPreoptCachesRecursively(const char *why);
void setDisallowPreoptInlinedSelsRecursively(const char *why);
#else
bool allowsPreoptCaches() const { return false; }
bool allowsPreoptInlinedSels() const { return false; }
void setDisallowPreoptCaches() { }
void setDisallowPreoptInlinedSels() { }
void setDisallowPreoptCachesRecursively(const char *why) { }
void setDisallowPreoptInlinedSelsRecursively(const char *why) { }
#endif
bool canAllocNonpointer() {
ASSERT(!isFuture());
return !instancesRequireRawIsa();
}
bool isSwiftStable() {
return bits.isSwiftStable();
}
bool isSwiftLegacy() {
return bits.isSwiftLegacy();
}
bool isAnySwift() {
return bits.isAnySwift();
}
bool isSwiftStable_ButAllowLegacyForNow() {
return bits.isSwiftStable_ButAllowLegacyForNow();
}
uint32_t swiftClassFlags() {
return *(uint32_t *)(&bits + 1);
}
bool usesSwiftRefcounting() {
if (!isSwiftStable()) return false;
return bool(swiftClassFlags() & 2); //ClassFlags::UsesSwiftRefcounting
}
bool canCallSwiftRR() {
// !hasCustomCore() is being used as a proxy for isInitialized(). All
// classes with Swift refcounting are !hasCustomCore() (unless there are
// category or swizzling shenanigans), but that bit is not set until a
// class is initialized. Checking isInitialized requires an extra
// indirection that we want to avoid on RR fast paths.
//
// In the unlikely event that someone causes a class with Swift
// refcounting to be hasCustomCore(), we'll fall back to sending -retain
// or -release, which is still correct.
return !hasCustomCore() && usesSwiftRefcounting();
}
bool isStubClass() const {
uintptr_t isa = (uintptr_t)isaBits();
return 1 <= isa && isa < 16;
}
// Swift stable ABI built for old deployment targets looks weird.
// The is-legacy bit is set for compatibility with old libobjc.
// We are on a "new" deployment target so we need to rewrite that bit.
// These stable-with-legacy-bit classes are distinguished from real
// legacy classes using another bit in the Swift data
// (ClassFlags::IsSwiftPreStableABI)
bool isUnfixedBackwardDeployingStableSwift() {
// Only classes marked as Swift legacy need apply.
if (!bits.isSwiftLegacy()) return false;
// Check the true legacy vs stable distinguisher.
// The low bit of Swift's ClassFlags is SET for true legacy
// and UNSET for stable pretending to be legacy.
bool isActuallySwiftLegacy = bool(swiftClassFlags() & 1);
return !isActuallySwiftLegacy;
}
void fixupBackwardDeployingStableSwift() {
if (isUnfixedBackwardDeployingStableSwift()) {
// Class really is stable Swift, pretending to be pre-stable.
// Fix its lie.
bits.setIsSwiftStable();
}
}
_objc_swiftMetadataInitializer swiftMetadataInitializer() {
return bits.swiftMetadataInitializer();
}
// Return YES if the class's ivars are managed by ARC,
// or the class is MRC but has ARC-style weak ivars.
bool hasAutomaticIvars() {
return data()->ro()->flags & (RO_IS_ARC | RO_HAS_WEAK_WITHOUT_ARC);
}
// Return YES if the class's ivars are managed by ARC.
bool isARC() {
return data()->ro()->flags & RO_IS_ARC;
}
bool forbidsAssociatedObjects() {
return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
}
#if SUPPORT_NONPOINTER_ISA
// Tracked in non-pointer isas; not tracked otherwise
#else
bool instancesHaveAssociatedObjects() {
// this may be an unrealized future class in the CF-bridged case
ASSERT(isFuture() || isRealized());
return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
}
void setInstancesHaveAssociatedObjects() {
// this may be an unrealized future class in the CF-bridged case
ASSERT(isFuture() || isRealized());
setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
}
#endif
bool shouldGrowCache() {
return true;
}
void setShouldGrowCache(bool) {
// fixme good or bad for memory use?
}
bool isInitializing() {
return getMeta()->data()->flags & RW_INITIALIZING;
}
void setInitializing() {
ASSERT(!isMetaClass());
ISA()->setInfo(RW_INITIALIZING);
}
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
void setInitialized();
bool isLoadable() {
ASSERT(isRealized());
return true; // any class registered for +load is definitely loadable
}
IMP getLoadMethod();
// Locking: To prevent concurrent realization, hold runtimeLock.
bool isRealized() const {
return !isStubClass() && (data()->flags & RW_REALIZED);
}
// Returns true if this is an unrealized future class.
// Locking: To prevent concurrent realization, hold runtimeLock.
bool isFuture() const {
if (isStubClass())
return false;
return data()->flags & RW_FUTURE;
}
bool isMetaClass() const {
ASSERT_THIS_NOT_NULL;
ASSERT(isRealized());
#if FAST_CACHE_META
return cache.getBit(FAST_CACHE_META);
#else
return data()->flags & RW_META;
#endif
}
// Like isMetaClass, but also valid on un-realized classes
bool isMetaClassMaybeUnrealized() {
static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias");
static_assert(RO_META == RW_META, "flags alias");
if (isStubClass())
return false;
return data()->flags & RW_META;
}
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClassMaybeUnrealized()) return (Class)this;
else return this->ISA();
}
bool isRootClass() {
return getSuperclass() == nil;
}
bool isRootMetaclass() {
return ISA() == (Class)this;
}
// If this class does not have a name already, we can ask Swift to construct one for us.
const char *installMangledNameForLazilyNamedClass();
// Get the class's mangled name, or NULL if the class has a lazy
// name that hasn't been created yet.
const char *nonlazyMangledName() const {
return bits.safe_ro()->getName();
}
const char *mangledName() {
// fixme can't assert locks here
ASSERT_THIS_NOT_NULL;
const char *result = nonlazyMangledName();
if (!result) {
// This class lazily instantiates its name. Emplace and
// return it.
result = installMangledNameForLazilyNamedClass();
}
return result;
}
const char *demangledName(bool needsLock);
const char *nameForLogging();
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceStart() const {
ASSERT(isRealized());
return data()->ro()->instanceStart;
}
// Class's instance start rounded up to a pointer-size boundary.
// This is used for ARC layout bitmaps.
uint32_t alignedInstanceStart() const {
return word_align(unalignedInstanceStart());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
void setInstanceSize(uint32_t newSize) {
ASSERT(isRealized());
ASSERT(data()->flags & RW_REALIZING);
auto ro = data()->ro();
if (newSize != ro->instanceSize) {
ASSERT(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&ro->instanceSize) = newSize;
}
cache.setFastInstanceSize(newSize);
}
void chooseClassArrayIndex();
void setClassArrayIndex(unsigned Idx) {
bits.setClassArrayIndex(Idx);
}
unsigned classArrayIndex() {
return bits.classArrayIndex();
}
};
通过分析源码,我们能够发现objc_class
- objc_class是一个结构体,继承自objc_object
- objc_class内部包含了4个成员变量和众多方法
- 从objc_object继承过过来Class isa
- Class superclass: 父类
- cache_t cache
- class_data_bits_t bits;
我们再来看一下objc_object的源码,我们发现objc_object也有两份 其中一份位于objc-private.h中
另一份位于objc.h中
还记得在探索OC对象原理的一篇中,我们通过编译器,将源码编译之后得到 其中NSObject就被编译成了结构体NSObject_IMPL,结构体NSObject_IMPL就与这里objc_object定义是相同的,所以苹果应该是使用的这里的objc_object。
【问题】NSObject、Class、objc_class 与 objc_object 有什么关系?
- NSObject对象(比如person,注意这里person是对象,NSObjcet是类,person是NSObject的实例)在底层的本质就是objc_object结构体 NSObject对象编译之后会变成
里面有一个Class类型的isa变量,而Class则是一个objc_class的指针类型,而objc_class又继承自objc_object。
- Class的本质在底层就是一个objc_class结构体指针,
typedef struct objc_class *Class;
- objc_class继承自objc_object,二者之间是继承关系
- objc_object 与 NSObject对象的关系是同一个事物的不同表现形式,在OC层面所有对象来自NSObject,而NSObject的底层就是一个objc_object结构体类型
通过上面的分析,我们可以总结如下:
- 所有的对象 + 类 + 元类 + 根元类 都有isa属性,即对象和类都有isa属性
- 在OC层面所有对象来自NSObject,而NSObject的底层就是一个objc_object结构体类型;而Class在底层其实就是一个objc_class的结构体指针
- 在OC底层通过结构体定义了模板,例如objc_class、objc_objec
- 所有以 objc_object为模板 创建的对象,都有isa属性
- 所有以objc_class为模板,创建的类,都有isa属性
objc_class、objc_object、isa、object、NSObject等的整体的关系,如下图所示
【百度面试题】objc_object 与 对象的关系
- objc_object 与 NSObject对象的关系是同一个事物的不同表现形式,在OC层面所有对象来自NSObject,而NSObject的底层就是一个objc_objectC/C++)结构体类型
- 所以可以说所有的对象在底层都是以objc_object为模板创建出来的
5.2.2 计算cache类的内存大小
在类原理基础分析一篇中,我们知道一个数组成员变量的地址是可以通过内存偏移计算,这里结构体同样使用。
通过上文的分析我们知道objc_object主要包含以下成员变量
struct objc_class : objc_object {
// Class ISA; //8字节
Class superclass; //Class 类型 8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略,未贴出
}
- isa指针: 指针类型,8字节
- Class superclass:Class也是结构体指针类型,8字节
- cache_t cache;: 我们无法直接看出cache_t的大小
- bits:要想获取bits的首地址,我们只需要将类地址平移(isa+superclass+cache所占的总字节数),即可以得到bits的首地址
目前我们已经知道了isa和superclass的大小,接下来我们计算一下cache的大小。cache是cache_t类型,首先我们看一下cache_t的源码:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// _bucketsAndMaybeMask is a buckets_t pointer
// _maybeMask is the buckets mask
static constexpr uintptr_t bucketsMask = ~0ul;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
static constexpr uintptr_t preoptBucketsMarker = 1ul;
static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
// _maybeMask is unused, the mask is stored in the top 16 bits.
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// Ensure we have enough bits for the buckets pointer.
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,
"Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
static constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)
// 63..60: hash_mask_shift
// 59..55: hash_shift
// 54.. 1: buckets ptr + auth
// 0: always 1
static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
uintptr_t value = (uintptr_t)cache->shift << 55;
// masks have 11 bits but can be 0, so we compute
// the right shift for 0x7fff rather than 0xffff
return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);
}
#else
// 63..53: hash_mask
// 52..48: hash_shift
// 47.. 1: buckets ptr
// 0: always 1
static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;
static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
return (uintptr_t)cache->hash_params << 48;
}
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
// _maybeMask is unused, the mask length is stored in the low 4 bits
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif
bool isConstantEmptyCache() const;
bool canBeFreed() const;
mask_t mask() const;
#if CONFIG_USE_PREOPT_CACHES
void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);
const preopt_cache_t *disguised_preopt_cache() const;
#endif
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);
static bucket_t *emptyBuckets();
static bucket_t *allocateBuckets(mask_t newCapacity);
static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));
public:
// The following four fields are public for objcdt's use only.
// objcdt reaches into fields while the process is suspended
// hence doesn't care for locks and pesky little details like this
// and can safely use these.
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
#if CONFIG_USE_PREOPT_CACHES
const preopt_cache_t *preopt_cache(bool authenticated = true) const;
#endif
mask_t occupied() const;
void initializeToEmpty();
#if CONFIG_USE_PREOPT_CACHES
bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;
bool shouldFlush(SEL sel, IMP imp) const;
bool isConstantOptimizedCacheWithInlinedSels() const;
Class preoptFallbackClass() const;
void maybeConvertToPreoptimized();
void initializeToEmptyOrPreoptimizedInDisguise();
#else
inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
inline bool shouldFlush(SEL sel, IMP imp) const {
return cache_getImp(cls(), sel) == imp;
}
inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }
inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endif
void insert(SEL sel, IMP imp, id receiver);
void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
void destroy();
void eraseNolock(const char *func);
static void init();
static void collectNolock(bool collectALot);
static size_t bytesForCapacity(uint32_t cap);
#if __LP64__
bool getBit(uint16_t flags) const {
return _flags & flags;
}
void setBit(uint16_t set) {
__c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);
}
void clearBit(uint16_t clear) {
__c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);
}
#endif
#if FAST_CACHE_ALLOC_MASK
bool hasFastInstanceSize(size_t extra) const
{
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
}
return _flags & FAST_CACHE_ALLOC_MASK;
}
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
void setFastInstanceSize(size_t newSize)
{
// Set during realization or construction only. No locking needed.
uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
uint16_t sizeBits;
// Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
// to yield the proper 16byte aligned allocation size with a single mask
sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
sizeBits &= FAST_CACHE_ALLOC_MASK;
if (newSize <= sizeBits) {
newBits |= sizeBits;
}
_flags = newBits;
}
#else
bool hasFastInstanceSize(size_t extra) const {
return false;
}
size_t fastInstanceSize(size_t extra) const {
abort();
}
void setFastInstanceSize(size_t extra) {
// nothing
}
#endif
};
上面的代码可以简化为:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
// 一系列static变量和方法
....
// 一系列方法
...
我们知道方法是不占用类的内存空间的,而且static静态变量也不是存储在类内存中的,而是存储到全局的静态存储区。也就是说cache_t的成员变量主要有2个:
explicit_atomic _bucketsAndMaybeMask ,占用8字节 其中explicit_atomic源码如下:
template <typename T> struct explicit_atomic : public std::atomic<T> { explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {} operator T() const = delete; T load(std::memory_order order) const noexcept { return std::atomic<T>::load(order); } void store(T desired, std::memory_order order) noexcept { std::atomic<T>::store(desired, order); } // Convert a normal pointer to an atomic pointer. This is a // somewhat dodgy thing to do, but if the atomic type is lock // free and the same size as the non-atomic type, we know the // representations are the same, and the compiler generates good // code. static explicit_atomic<T> *from_pointer(T *ptr) { static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *), "Size of atomic must match size of original"); explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr; ASSERT(atomic->is_lock_free()); return atomic; } };
它是c++中的泛型,字节占用的大小就是所传入类型的大小,这里传入的是uintptr_t,而uintptr_r是无符号长整型(
typedef unsigned long uintptr_t;
)占用8字节联合体union,占用8字节 联合体源码如下:
union { struct { explicit_atomic<mask_t> _maybeMask; #if __LP64__ uint16_t _flags; #endif uint16_t _occupied; }; explicit_atomic<preopt_cache_t *> _originalPreoptCache; };
其中:
- 结构体最大8字节
- _maybeMask:是mask_t(
typedef uint32_t mask_t;
,typedef unsigned int uint32_t;
)无符号整型的大小,占用4字节 - _flags:是uint16_t(
typedef unsigned short uint16_t;
)无符号short类型,占用2字节 - _occupied:是uint16_t(
typedef unsigned short uint16_t;
)无符号short类型,占用2字节
- _maybeMask:是mask_t(
- _originalPreoptCache:是
preopt_cache_t *
指针类型,占用8字节 - 因为联合体是公用一块内存,所以联合体总共占用8字节。
- 结构体最大8字节
结论:综上所述cache_t总结占用8+8=16字节大小
5.2.3 获取bits
通过上面的分析我们知道了bits前面的3个成员变量总计占用了32字节的大小。根据内存平移原则 bits地址=类首地址+32字节
class_data_bits_t bits;
bits是class_data_bits_t类型,class_data_bits_t的源码如下:
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
};
由此可见它是一个结构体类型,其中里面有一个data函数,该函数返回一个class_rw_t*指针
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
其中class_rw_t也是一个结构体,而且我们常见的方法列表methods,属性列表properties,协议列表protocols都存储在这个结构体中,其源码如下:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
/// 省略一部分函数
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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
};
所以我们可以通过以下方式获取类的方法、属性和协议:
首先通过类的首地址+32字节得到bits的首地址(class_data_bits_t类型)
然后通过调用bits.data()函数获取到class_rw_t指针
接着通过class_rw_t指针,调用methods函数获取类的方法列表;调用properties函数获取属性列表;调用protocols函数获取协议列表
5.2.4 属性列表 property_list在底层的存储
接下来我们通过一个实例获取property_list。代码如下:
@interface ZBBaseTeacher : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
- (void)sayHello;
+ (void)say666;
@end
@implementation ZBBaseTeacher
- (void)sayHello
{
}
+ (void)say666
{
}
@end
@interface ZBTeacher : ZBBaseTeacher
@end
@implementation ZBTeacher
@end
我们在main函数中调试ZBBaseTeacher对象,通过lldb调试如下:
- 第一步我们需要先获取ZBBaseTeacher类在内存中的信息,在类中:
- isa指针,占用8字节
- superclass指针,占用8字节
- cache结构体,占用16字节
- 第2步我们通过内存偏移32字节获取bits的首地址,我们可以有两种方式来计算:
- 通过类的首地址+0x20,例如上图中:
p/x 0x100008308+0x20
- 直接通过第一步中口算得到
0x100008328
地址,这就是bits的首地址
- 通过类的首地址+0x20,例如上图中:
- 我们在第2步获取的是bits的首地址指针,所以为了后续的操作方便,我们这里进行强转一下,将bits地址指针转换成class_data_bits_t *类型。
- 注意,在lldb环境进行强转时,需要在objc源码环境,如果不是源码环境,会报找不到class_data_bits_t类型的错误
- 注意,在lldb环境进行强转时,需要在objc源码环境,如果不是源码环境,会报找不到class_data_bits_t类型的错误
- 调用class_data_bits_t的data()函数获取数据
- data函数源码如下,返回值类型为class_rw_t*类型
- 另外这里需要注意一下,第3步我们获得的是class_data_bits_t指针,所以访问data函数,需要使用
p $2->data()
,如果此时的$2是结构体类型,那么可以直接通过p $2.data()
来访问
- data函数源码如下,返回值类型为class_rw_t*类型
- 打印class_rw_t指针指向的具体数据,也就是获取到class_rw_t结构体
- 通过调用class_rw_t结构体的properties方法获取具体属性
- properties源码如下
- 方法的返回值类型为property_array_t,源码如下:
可以发现这是一个数组中还包含一个list数组property_list_t,这才是我们属性真正的藏身之处。
- 再进一步点开property_list_t,发现是一个空结构体
- 再进一步点开property_t,可以发现它也是一个结构体,正是我们要找的属性,源码如下:
- properties源码如下
- 我们通过
p $5.list.ptr
获取到property_list_t列表的指针地址 - 通过
*$6
直接访问property_list_t列表的数据 - 直接调用get(index) 来访问具体的属性,如果index越界之后会报错。
注意: 1. 我们第一步获取的一定是一个类的内存,而不是对象,如果对对象进行操作会报错,而且也分析不到具体的数据
- 如果我们使用的M1芯片的电脑,那么由于架构不同的原因,那么最后一步需要调用get(index).getDescription
结论:对象的属性列表存储在类的bits字段中, 通过bits.data()->properties()---> list ---> ptr ---> get(index)获取
5.2.5 成员变量列表在底层的存储
通过上面的分析,我们发现还有一个问题。 问题:ZBBaseTeacher还有一个hobby的成员变量,为什么这里获取不到呢?
我们在上面的分析过程中,可以知道class_rw_t结构体中有methods、properties、protocols等方法,那么同样应该也有获取成员变量的方法,我们寻找一番会找到如下函数: 那么是不是通过此方法来获取成员变量呢,我们通过lldb验证一下
验证通过。其中需要注意以下几点:
- 调用class_rw_t的ro函数会得到一个class_ro_t的指针。
- class_ro_t也是一个结构体,源码如下
其中有一个ivars变量用来保存成员变量,它是一个ivar_list_t的指针类型
- ivar_list_t也是一个结构体,源码如下:
- 这里的ivar就是我们的成员变量,它是Ivar类型,而Ivar类型就是一个结构体指针类型
里面包含了名字name,类型type,以及所占用字节数size等关键信息
- class_rw_t与class_ro_t的区别参照:https://www.jianshu.com/p/823eaedb3697 苹果官方视频:https://developer.apple.com/videos/play/wwdc2020/10163/
- class_ro_t也是一个结构体,源码如下
- 打印class_ro_t的具体内容
- 第3-4步就是为了获取ivar_list_t的具体内容
- 第5步通过get方法获取成员变量的内容,通过打印结果我们可以发现
- 成员变量hobby,字符串类型,占8字节
- 成员变量_name,字符串类型,占8字节
- 成员变量_nickName,字符串类型,占8字节
结论:对象的成员变量列表存储在类的bits字段中, 通过bits.data()->ro()---> ivars ---> get(index)获取
5.2.6 对象方法列表 methods_list在底层的存储
通过上面的分析我们知道了方法列表位域methods方法中,我们直接验证
这里我们发现了一个问题,我们通过get(index) 函数打印的时候,结果发现全是空的,那么是怎么回事呢?
我们找到mehods函数源码
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 *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
}
}
它返回一个method_array_t类型 我们点开method_t的源码发现里面有个big结构体,里面存储的正是我们方法的信息
- name,SEL类型,方法签名
- types,const char *类型,方法类型
- imp,MethodListIMP(
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;
)类型,方法实现,这里就是一个函数指针
接下我们再验证一下: 验证没问题,上面打印了5个方法,唯独缺少一个类方法
say666
结论:对象方法列表存储在类的bits字段中, 通过bits.data()->methods()---> list ---> ptr ---> get(index).big()获取