前言
通过之前的文章我们对 isa 已经有了初步的了解和认知。如果还有不了解的同学可以通过 这篇文章 了解一下。接下来我们进入今天的正文。
一、类的分析 — ISA
首页我们创建下面这份代码,然后对 TPerson
进行分析。
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// 0x00007ffffffffff8
TPerson * tp = [TPerson alloc];
NSLog(@" -- %@ -- ", tp);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
我们通过控制台输出:
(lldb) p/x tp // 对 tp 进行探索
(TPerson *) $0 = 0x00006000029440a0 // 得到当前 tp 的指针地址
(lldb) x/4gx 0x00006000029440a0
0x6000029440a0: 0x0000000102ec4570 0x0000000000000000
0x6000029440b0: 0x0000006568636163 0x000000000000000d
// 这里就能得到当前对象的 isa :0x000000010aba3570
(lldb) p/x 0x0000000102ec4570 & 0x00007ffffffffff8 // 通过 isa 获取当前的类
(long) $1 = 0x0000000102ec4570
(lldb) po 0x0000000102ec4570
TPerson
// 输出 0x0000000102ec4570 就得到了 TPerson
// 我们继续探索 TPerson 类的地址,发现它也有和 TPerson 一样的内存结构
(lldb) x/4gx 0x0000000102ec4570
0x102ec4570: 0x0000000102ec4548 0x00007fff80030660
0x102ec4580: 0x00007fff20195cd0 0x0000801000000000
(lldb) p/x 0x0000000102ec4548 & 0x00007ffffffffff8
(long) $4 = 0x0000000102ec4548
(lldb) po 0x0000000102ec4548
TPerson
// 输出 0x0000000102ec4548 ,发现也得到了 TPerson
(lldb)
复制代码
通过上面这段代码和输出,发现 0x0000000102ec4570
和 0x0000000102ec4548
都输出了 TPerson
。难道 类
和 对象
一样,可以 无限开辟
在内存中 不止一个类
。
我们通过下面这段代码进行验证 类对象
Class class1 = [TPerson class];
Class class2 = [TPerson alloc].class;
Class class3 = object_getClass([TPerson alloc]);
Class class4 = [TPerson alloc].class;
NSLog(@"\n-- class1: %p\n-- class2: %p\n-- class3: %p\n-- clsss4: %p",class1,class2,class3,class4);
输出如下:
-- class1: 0x102ec4570
-- class2: 0x102ec4570
-- class3: 0x102ec4570
-- clsss4: 0x102ec4570
复制代码
根据打印结果可以得知,只有 0x0000000102ec4570
才是我们的类 TPerson
,0x0000000102ec4548
不是我们的类,那么它是什么呢?会不会是 NSObject
呢?通过控制台输出验证。
(lldb) p/x NSObject.class
(Class) $5 = 0x00007fff80030660 NSObject
复制代码
发现它也不是我们的 NSObject
类,那么它到底是什么呢?我们通过对程序的二进制文件进行分析,这里需要一个分析工具 烂苹果
。[下载地址](链接: pan.baidu.com/s/1nRvNHX-D… 密码: 8pes)
可以看到在整个编译的可以执行文件中多了有个 _OBJC_METCLASS_$_TPerson
叫做 元类
,是 系统
或者 编译器
帮我们生成好的。可以得出 对象 isa
-> 类 isa
-> 元类
。
二、isa 走位图和继承链
1. 走位图
上面我已经知道了 元类
。而且还得出了 对象 isa
-> 类 isa
-> 元类
那么我们可不可以类推一下呢?
对象 isa
-> 类 isa
-> 元类 isa
-> 根元类
根类 isa
-> 根元类 isa
-> 根元类
就得到了这张 isa 走位图
2. 继承链
那么什么是 继承链
呢?通过下面代码来一探究竟。
// NSObject 元类链
// 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 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
输出:
0x600000378060 实例对象
0x7fff80030660 类
0x7fff80030638 元类
0x7fff80030638 根元类
0x7fff80030638 根根元类
// TPerson元类
Class pMetaClass = object_getClass(TPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"\n%@ - %p",psuperClass,psuperClass);
输出:
NSObject - 0x7fff80030638
// TTeacher -> TPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(TTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
输出:
TPerson - 0x1005a35f0
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
输出:
(null) - 0x0
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
输出:
NSObject - 0x7fff80030660
复制代码
根据上述代码,我们可以得出下图:
三、类的结构
根据上文可以知道 类
也有 内存
,也能存储值。类
的本质是 objc_class
,接下来我们一起探索一下 objc
的源码。 苹果objc源码 、 objc源码编译
1. objc_class 声明
在源码中搜索 objc_class
,发现它有两个声明。
声明一
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
声明二
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
}
... 由于代码过多截取部分,如需了解移步源码中查看。
}
复制代码
发现 声明一
是 OBJC2_UNAVAILABLE
,无法使用的。所以我们就探索 声明二
。objc_class
是一个继承
于 objc_object
的 结构体
。类
的结构是 Class ISA
、 Class superclass
、 cache_t cache
、 class_data_bits_t bits
这些。我们现在已知 objc_class
这个类,但是我们要如何去获取 类
的 内存数据
(cache
、 bits
) 呢?
2. 如何获取 bits 的数据
我们先来探究如何获取 类
的 内存数据
bits
。
@interface TPerson : NSObject {
NSString * desc;
}
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * nickName;
- (void)formatPerson;
+ (void)personNickName;
@end
复制代码
#import "TPerson.h"
@implementation TPerson
- (instancetype)init {
self = [super init];
if (self) {
self.name = @"小名";
}
return self;
}
- (void)formatPerson {
}
+ (void)personNickName {
}
@end
复制代码
(lldb) x/4gx TPerson.class
0x100008600: 0x0000000100008628 0x000000010036a140
0x100008610: 0x00000001007519a0 0x0002802c00000003
(lldb) po 0x000000010036a140
NSObject
// 我们对 `TPerson.class` 进行探究,得到 '0x0000000100008628' -> isa , '0x000000010036a140' -> superclass ,即为 'NSObject'。
// 验证一下 superclass 是否就是 'NSObject' ?
(lldb) p/x NSObject.class
(Class) $2 = 0x000000010036a140 NSObject
(lldb)
复制代码
我们对 TPerson.class
进行探究得到 0x0000000100008628
: isa
、0x000000010036a140
: superclass
,以此类推 0x00000001007519a0
: cache
、0x0002802c00000003
: bits
。
3. cache_t 探索
接下来我们看 objc_class
的 声明二
源码。
从首地址平移到 bits
需要平移多少个字节呢?我们知道 isa
8
字节、superclass
8
字节,那么 cache
多少字节呢?那么我们接下来就一起探索 cache_t
的源码,看看 cache_t
占多少个字节。
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied; // 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
};
... 由于 【方法、函数、全局区】 都不在结构体存储区域得到了如上代码,如需了解移步源码中查看。
}
复制代码
得到 cache_t
的大小就等于 uintptr_t
+ union
的大小。uintptr_t
占 8
字节,union
占 8
字节,所以 cache_t
占 16
字节。
这里我们就得到了平移大小 : isa
(8
) + superclass
(8
) + cache_t
(16
)= 32
。
(lldb) x/4gx TPerson.class
0x100008600: 0x0000000100008628 0x000000010036a140
0x100008610: 0x00000001007519a0 0x0002802c00000003
(lldb) p/x 0x100008600+0x20 // 0x100008600 平移 32 位
(long) $9 = 0x0000000100008620
(lldb) p (class_data_bits_t *)0x0000000100008620 // 还原得到了 class_data_bits_t
(class_data_bits_t *) $10 = 0x0000000100008620
(lldb) p $10->data() // 调用 data() 就得到了 class_rw_t
(class_rw_t *) $11 = 0x0000000101525af0
(lldb) p *$11
(class_rw_t) $12 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000856
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb)
复制代码
接下来我们继续查看 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 {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
public:
void setFlags(uint32_t set)
{
__c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
}
void clearFlags(uint32_t clear)
{
__c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
}
// set and clear must not overlap
void changeFlags(uint32_t set, uint32_t clear)
{
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = flags;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
}
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *deepCopy(const class_ro_t *ro) {
return extAlloc(ro, true);
}
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);
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
} else {
set_ro_or_rwe(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_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};
}
}
}
复制代码
从 class_rw_t
源码中我们知道了分别存储 properties()
、 methods()
、 protocols()
等。
4. properties() 探索
我们先来探索 properties()
(class_rw_t *) $11 = 0x0000000101525af0
(lldb) p *$11
(class_rw_t) $12 = {
flags = 2148007936
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000856
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
}
(lldb) p $11->properties()
(const property_array_t) $13 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008478
}
arrayAndFlag = 4295001208
}
}
}
复制代码
这里我们就得到了 property_array_t
,我们继续查看 property_array_t
的源码。
class property_array_t :
public list_array_tt<property_t, property_list_t, RawPtr>
{
typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
};
复制代码
继续查看 list_array_tt
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize() {
return byteSize(count);
}
};
protected:
class iterator {
const Ptr<List> *lists;
const Ptr<List> *listsEnd;
typename List::iterator m, mEnd;
public:
iterator(const Ptr<List> *begin, const Ptr<List> *end)
: lists(begin), listsEnd(end)
{
if (begin != end) {
m = (*begin)->begin();
mEnd = (*begin)->end();
}
}
const Element& operator * () const {
return *m;
}
Element& operator * () {
return *m;
}
bool operator != (const iterator& rhs) const {
if (lists != rhs.lists) return true;
if (lists == listsEnd) return false; // m is undefined
if (m != rhs.m) return true;
return false;
}
const iterator& operator ++ () {
ASSERT(m != mEnd);
m++;
if (m == mEnd) {
ASSERT(lists != listsEnd);
lists++;
if (lists != listsEnd) {
m = (*lists)->begin();
mEnd = (*lists)->end();
}
}
return *this;
}
};
// ....
}
复制代码
发现这里有一个 iterator
迭代器方法
,所以的 property
都是存放在这里,并且可以取出响应的元素。
(lldb) p $11->properties()
(const property_array_t) $14 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
= {
list = {
ptr = 0x0000000100008478
}
arrayAndFlag = 4295001208
}
}
}
(lldb) p $14.list
(const RawPtr<property_list_t>) $15 = {
ptr = 0x0000000100008478
}
(lldb) p $15.ptr // 获取 ptr
(property_list_t *const) $16 = 0x0000000100008478
(lldb) p *$16 // 还原数据
(property_list_t) $17 = {
entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $17.get(0)
(property_t) $18 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $17.get(1)
(property_t) $19 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $17.get(2)
Assertion failed: (i < count), function get, file ***/objc4_debug-master/objc4-818.2/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
复制代码
通过还原的 property_list_t
$17
调用 get(0)
、get(1)
方法就得到了 TPerson
中定义的属性。
调用 get(2)
时,出现了如下错误,数组越界了!
Assertion failed: (i < count), function get, file ***/objc4_debug-master/objc4-818.2/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
复制代码
那么我们在 TPerson
中定义的方法怎么获取呢?我们已经知道了如果获取 property
,那么我们就按照获取 property
的方法,继续探索 methods()
5. methods() 探索
(lldb) p $11->methods()
(const method_array_t) $28 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008360
}
arrayAndFlag = 4295000928
}
}
}
(lldb) p $28.list
(const method_list_t_authed_ptr<method_list_t>) $29 = {
ptr = 0x0000000100008360
}
(lldb) p $29.ptr
(method_list_t *const) $30 = 0x0000000100008360
(lldb) p *$30
(method_list_t) $31 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $31.get(0)
(method_t) $32 = {}
(lldb)
复制代码
这里我们就得到了 method_list_t
,但是我们这里通过 get(0)
、get(1)
、get(2)
、get(3)
都无法取到值呢?难道 method
不能像 property
一样,通过 get()
取值么?我们一起来看一下 method_array_t
的源码。
class method_array_t :
public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
public:
method_array_t() : Super() { }
method_array_t(method_list_t *l) : Super(l) { }
const method_list_t_authed_ptr<method_list_t> *beginCategoryMethodLists() const {
return beginLists();
}
const method_list_t_authed_ptr<method_list_t> *endCategoryMethodLists(Class cls) const;
};
复制代码
6. methods() 与 properties() 的区别
通过对比 method_array_t
和 property_array_t
,发现它们的代码都是一样的。那么为什么不行呢?
通过对代码进行对比,发现 property_list_t
方法没有实现,而 method_list_t
方法有实现!
property_list_t
方法:
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
复制代码
method_list_t
方法:
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
bool isUniqued() const;
bool isFixedUp() const;
void setFixedUp();
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
ASSERT(i < count);
return i;
}
bool isSmallList() const {
return flags() & method_t::smallMethodListFlag;
}
bool isExpectedSize() const {
if (isSmallList())
return entsize() == method_t::smallSize;
else
return entsize() == method_t::bigSize;
}
method_list_t *duplicate() const {
method_list_t *dup;
if (isSmallList()) {
dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1);
dup->entsizeAndFlags = method_t::bigSize;
} else {
dup = (method_list_t *)calloc(this->byteSize(), 1);
dup->entsizeAndFlags = this->entsizeAndFlags;
}
dup->count = this->count;
std::copy(begin(), end(), dup->begin());
return dup;
}
}
复制代码
这里有一个点: 我们获取 property_list_t
的时候获取的是 property_t
结构体
直接输出的是 name
和 attributes
。
struct property_t {
const char *name;
const char *attributes;
};
复制代码
而再看 method_t
结构体
它里面没有成员变量
,所以输出的是空{}
。
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;
};
// ....
}
复制代码
7. methods()结构体的结构体中取值
这里我们发现在 method_t
中有一个 big
结构体
,它里面有 name
、 types
、 imp
,那么我们就可以先获取 method_t
再获取 big
。那么 结构体
-> 结构体
怎么获取呢?method_t
中提供了方法
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
复制代码
这样我们就能获取到 TPerson
中的 对象方法
。那么 TPerson
中的 类方法
存储在哪里呢?
(lldb) p $31.get(0).big()
(method_t::big) $33 = {
name = "formatPerson"
types = 0x0000000100003f84 "v16@0:8"
imp = 0x0000000100003c50 (KCObjcBuild`-[TPerson formatPerson])
}
(lldb) p $31.get(1).big()
(method_t::big) $34 = {
name = "init"
types = 0x0000000100003f5e "@16@0:8"
imp = 0x0000000100003bc0 (KCObjcBuild`-[TPerson init])
}
(lldb) p $31.get(2).big()
(method_t::big) $35 = {
name = "name"
types = 0x0000000100003f5e "@16@0:8"
imp = 0x0000000100003c60 (KCObjcBuild`-[TPerson name])
}
(lldb) p $31.get(3).big()
(method_t::big) $36 = {
name = ".cxx_destruct"
types = 0x0000000100003f84 "v16@0:8"
imp = 0x0000000100003d20 (KCObjcBuild`-[TPerson .cxx_destruct])
}
(lldb) p $31.get(4).big()
(method_t::big) $37 = {
name = "setName:"
types = 0x0000000100003f66 "v24@0:8@16"
imp = 0x0000000100003c90 (KCObjcBuild`-[TPerson setName:])
}
(lldb) p $31.get(5).big()
(method_t::big) $38 = {
name = "nickName"
types = 0x0000000100003f5e "@16@0:8"
imp = 0x0000000100003cc0 (KCObjcBuild`-[TPerson nickName])
}
(lldb) p $31.get(6).big()
(method_t::big) $39 = {
name = "setNickName:"
types = 0x0000000100003f66 "v24@0:8@16"
imp = 0x0000000100003cf0 (KCObjcBuild`-[TPerson setNickName:])
}
复制代码
这篇文章我们已经探索了 properties()
和 methods()
。
这里留了两个问题:
问题一:TPerson
中的 ivar
存储在哪里呢?
问题二:TPerson
中的 类方法
存储在哪里呢?
下篇文章我们继续补充。
补充: 内存偏移
我们通过下面这几段代码,对 内存偏移
进行了解。
// 普通指针
int a = 10;
int b = 10;
NSLog(@"-- %d -- %p", a, &a);
NSLog(@"-- %d -- %p", b, &b);
输出:
-- 10 -- 0x7ffeefbff430
-- 10 -- 0x7ffeefbff434
复制代码
在内存中某一个区域里,有指针a
和 指针b
,它们都同时指向一个数据 值 = 10
,意味着这个数据可以被任何一个指针所使用。按照这个理论就得出: 普通指针是值拷贝 。
// 对象指针
TPerson * tp1 = [TPerson alloc];
TPerson * tp2 = [TPerson alloc];
NSLog(@"-- %@ -- %p",tp1, &tp1);
NSLog(@"-- %@ -- %p",tp2, &tp2);
输出:
-- <TPerson: 0x100735110> -- 0x7ffeefbff420
-- <TPerson: 0x100736060> -- 0x7ffeefbff428
复制代码
tp1
和 tp2
指向的地址
不同,空间
也不同。&tp
-> tp
-> [TPerson alloc]
就得出一个结论:对象指针是指针拷贝
// 数组指针
int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"-- %p-- %p-- %p", &c, &c[0], &c[1]);
NSLog(@"-- %p-- %p-- %p", d, d+1, d+2);
输出:
-- 0x7ffeefbff420-- 0x7ffeefbff420-- 0x7ffeefbff424
-- 0x7ffeefbff420-- 0x7ffeefbff424-- 0x7ffeefbff428
复制代码
发现 &c
和 &c[0]
地址相同,是因为 数组的首地址就等于第一个元素的地址。0x7ffeefbff420
与 0x7ffeefbff424
相差 4
个字节( int
占用 4
个字节 )。d
就是当前 c
的指针,d+1
就是将当前 c
的指针 平移 1个
。
我们通过下面这段代码进行验证:
// 数组指针
int c[4] = {1, 2, 3, 4};
int *d = c;
for (int i = 0; i < 4; i++) {
int num = *(d+i);
NSLog(@"-- num : %d", num);
}
输出:
-- num : 1
-- num : 2
-- num : 3
-- num : 4
复制代码
得出一个结论:内存偏移可以根据 首地址
+ 偏移值
,得到相应变量的地址。
后续继续补充。。。
Tips: 如若有理解错误或者不对的地方欢迎一起探索和交流!!!