[OC学习笔记]类对象的结构
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[OC学习笔记]类对象的结构相关的知识,希望对你有一定的参考价值。
对象的分类
Objective-C中所有对象可以分为3类:实例对象,类对象,元类对象。其中我们开发者常用的继承自NSObject
都属于实例对象,实例对象通过isa
指针指向的是类对象。类对象通过isa
指向的是元类对象。类对象和元类对象拥有同样的结构,都是来自objc_class
。关于这部分可以先学习一下我前面的博客:[OC学习笔记]浅学元类。
objc_class结构
因为所有的类都是继承于objct_class
,在源码中查找objc_class
可以找到其结构:
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
...
其中isa
指针是继承自objc_object
的。isa
在之前的内容里面已经讲过,主要是指向类对象或者元类对象的。类结构里面默认一个Class ISA
同时包含Class superclass
,cache_t cache
,class_data_bits_t bits
。superclass
是体现出继承关系的。cache
是方法缓存。bits
是类的其他信息,例如成员变量,方法列表,协议,属性。
注意:元类对象结构也是如此,但元类对象里面没有成员变量,协议,属性这些内容。
isa指向
放出这张经典图片:
对isa
指向做一个简单的总结:
实例对象的isa指针指向对应的类对象,类对象的isa指针指向对应的元类对象,元类对象的isa指向基类的元类对象(根元类)。
可以用lldb
调试来验看下。
- (void)test
NSObject *objc = [[NSObject alloc] init];
// isa指向的就是类对象
Class objcClass1 = [NSObject class];
Class objcMetaClass = object_getClass([NSObject class]);
根据上一篇内容,我们先获取objc的isa
指针的值,然后用这个值与运算ISA_MASK
,得到的值,正好是类对象的地址。然后用同样的方法获取类对象的值,和ISA_MASK
与运算得到元类对象的地址。
可以继续这样再操作一轮,得到的还是元类对象的地址。这是因为该类是NSObject
,NSObject
元类对象的isa
是指向自己的。
define ISA_MASK 0x007ffffffffffff8ULL
0x00007ffffffffff8
转二进制为
0001 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000
其实就是,000…000…中间44个1…000。其他位抹零,只保留中间44位,即取到shiftcls
类信息。
可看到NSObject
的isa
走势为: NSObject →根元类 →根元类自身
。
superClass指向
再看上面那张图:superClass
的指向也在上面那个经典的图中。这里要注意superclass
是objc_class
才有的,所以实例对象是没有的。类对象的superclass
指向父类对象,元类对象的superclass
指向父类的元类对象,根元类对象的superclass
指针指向根类对象。根类对象的superclass
是nil
。
bits
类的结构中还有一个bits
,里面也存了很多信息。我们之前知道,已知首地址,可以通过平移方法,得到我们64位下结构体指针类型占8字节,即isa
占8字节,superclass
同理也是结构体指针类型占8字节,即superclass
占8字节。
接下来我们看下cache
的大小:
因为cache
为cache_t
类型, 最简单的方法lldb
命令 po
读一下cache_t
。
可以看到,大小为16字节。
这一小节,我们主要来看bits
的数据结构 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;
// Atomically set the bits in `set` and clear the bits in `clear`.
// set and clear must not overlap.
void setAndClearBits(uintptr_t set, uintptr_t clear)
ASSERT((set & clear) == 0);
uintptr_t newBits, oldBits = LoadExclusive(&bits);
do
newBits = (oldBits | set) & ~clear;
while (slowpath(!StoreReleaseExclusive(&bits, &oldBits, newBits)));
void setBits(uintptr_t set)
__c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
void clearBits(uintptr_t clear)
__c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
public:
class_rw_t* data() const
return (class_rw_t *)(bits & FAST_DATA_MASK);
void setData(class_rw_t *newData)
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() const
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED)
// maybe_rw is rw
return maybe_rw->ro();
else
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
#if SUPPORT_INDEXED_ISA
void setClassArrayIndex(unsigned Idx)
// 0 is unused as then we can rely on zero-initialisation from calloc.
ASSERT(Idx > 0);
data()->index = Idx;
#else
void setClassArrayIndex(__unused unsigned Idx)
#endif
unsigned classArrayIndex()
#if SUPPORT_INDEXED_ISA
return data()->index;
#else
return 0;
#endif
bool isAnySwift()
return isSwiftStable() || isSwiftLegacy();
bool isSwiftStable()
return getBit(FAST_IS_SWIFT_STABLE);
void setIsSwiftStable()
setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY);
bool isSwiftLegacy()
return getBit(FAST_IS_SWIFT_LEGACY);
void setIsSwiftLegacy()
setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE);
// fixme remove this once the Swift runtime uses the stable bits
bool isSwiftStable_ButAllowLegacyForNow()
return isAnySwift();
_objc_swiftMetadataInitializer swiftMetadataInitializer()
// This function is called on un-realized classes without
// holding any locks.
// Beware of races with other realizers.
return safe_ro()->swiftMetadataInitializer();
;
其实最最重要的是其中的2个方法。data
和 safe_ro
,两个方法分别返回class_rw_t
和class_ro_t
。
class_rw_t* data() const
return (class_rw_t *)(bits & FAST_DATA_MASK);
const class_ro_t *safe_ro() const
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED)
// maybe_rw is rw
return maybe_rw->ro();
else
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
仔细观察获取class_ro_t
的方法,里面也用到了data()
方法,也可以理解为class_ro_t
也在class_rw_t
之中。
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 witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
const ro_or_rw_ext_t get_ro_or_rwe() const ...
void set_ro_or_rwe(const class_ro_t *ro) ...
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) ...
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
void setFlags(uint32_t set)
...
void clearFlags(uint32_t clear)
...
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
...
class_rw_ext_t *ext() const ...
class_rw_ext_t *extAllocIfNeeded() ...
class_rw_ext_t *deepCopy(const class_ro_t *ro) ...
const class_ro_t *ro() const ...
void set_ro(const class_ro_t *ro) ...
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_tv.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_tv.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_tv.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols;
;
可以看到有三个主要的方法:通过这三个方法分别能获取到类的方法、属性、协议。
属性列表打印
已知首地址以及 ISA
占 8字节,superclass
占8字节,cache
占16字节,所以bits
前面总共8+8+16 = 32
字节, 可通过首地址平移32字节获取bits
信息。
@interface MyPerson : NSObject
NSString *myHobby;
NSString *myCars;
@property (nonatomic, strong) NSString *myName;
@property (nonatomic, assign) int age;
- (void)sayHello;
- (void)sayBye;
+ (void)sayFuckOff;
@end
在lldb
调试区调试:
bits
数据信息在前面已经讲过了,我们从读bits
数据信息$2之后开始
p $3.properties()
获得的属性列表的list结构, 其中list中的ptr就是属性数组的参数指针地址。(p $3.properties()
命令中的properties
方法是由class_rw_t
提供的, 方法中返回的实际类型为property_array_t
)p *$4.list.ptr
读一下指针地址指向内容, 可看到获得属性list信息, count = 2, 也符合我们建的2个属性p $5.get(0)
可获取到myName
对应属性(property_t) $6 = (name = "myName", attributes = "T@\\"NSString\\",&,N,V_myName")
p $5.get(1)
可获取到age
属性(property_t) $7 = (name = "age", attributes = "Ti,N,V_age")
p $5.get(2)
数组越界, 因为我们只建立了2个属性
方法列表打印
先仔细看一下上面的那三个方法,可以看到一个叫class_rw_ext_t
的东西,先了解一些东西:
struct class_rw_ext_t
DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
class_ro_t_authed_ptr<const class_ro_t> ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
;
struct property_t
const char *name;
const char *attributes;
;
struct method_t
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big
SEL name;
const char *types;
MethodListIMP imp;
;
private:
...
;
想必与属性列表不同,方法列表的相关内容name
、types
、imp
储存在struct big
里面(818新改动),所以获取方法列表里面的信息也要稍微变一下。看别人是这样取的:
人家使用p $5.get(0).big()
可以取到,但我就不行,反而能用small()
取到。通过观察,我只好换用getDescription()
获取:
只获取了实例方法,没法获取类方法。或者我们直接上代码获取,哎,这里真的没太理解啊。代码获取:
Class metaClass = objc_getClass("MyPerson");
unsigned int count = 0;
Method *methods = class_copyMethodList(metaClass, &count);
NSLog(@"%u", count);
for (unsigned int i = 0; i < count; i++)
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method, name: %@", key);
metaClass = objc_getMetaClass("MyPerson");
count = 0;
methods = class_copyMethodList(metaClass, &count);
NSLog(@"%u", 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);
输出:
可以看出来,本类有6个方法(实例方法),元类有一个方法(类方法)。
class_ro_t
其实,我们刚刚也可以发现,成员变量以及类方法并没有在属性列表、方法列表里面。回过头我们再看struct class_rw_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_or_rw_ext)->ro;
return v.get<const class_ro_t *>(&ro_or_rw_ext);
看下ro
底层:
struct class_ro_t
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union
const uint8_t * ivarLayout;
Class nonMetaclass;
;
explicit_atomic<const char *> name;
WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const ...
const char *getName() const ...
class_ro_t *duplicate() const ...
Class getNonMetaclass() const ...
const uint8_t *getIvarLayout() const ...
;
可以看到有方法、属性、协议和成员变量。但方法、属性、协议的命名都是base
开头的。
探索成员变量
可看到const ivar_list_t * ivars;
,有一个ivars
属性(ivars
: 实例变量),我们仿照下上面也读一下ro
。
p $3.ro()
获得的ro
的里面的信息p $5.ivars
获得的ivars_list_t
即成员变量的列表
接下来我们仿照属性列表去读取,发现实例变量储存在ivars_list_t
里面,同时也会发现还有属性的成员变量。
小结:
- 通过
XXXX
定义的成员变量,会存储在类的bits
属性中,通过bits --> data() -->ro() --> ivars
获取成员变量列表,除了包括成员变量,还包括属性的成员变量。 - 通过
@property
定义的属性,存储在bits
属性中,通过bits --> data() --> properties() --> list
获取属性列表,其中只包含property
属性。
探索类方法
所谓的对象/实例方法
、类方法
其实是OC上层或者说苹果官方人为加入的概念, 其底层是都是函数,不区分+
、-
。但实例方法与类方法还是有必要区分的,则苹果将实例方法存在类里面,而类方法存在元类里面。一方面避免对象存储太大会发生混乱,一方面也是为了有个调用区分。
所以说类方法要在元类中查找。
ojective-C学习笔记关于面向对象编程