IOS 底层原理 类的本质--(2)

栏目: IOS · 发布时间: 5年前

内容简介:复习一下经过

复习一下 IOS 底层原理 对象的本质--(1) ,可以看出来实例对象实际是上结构体,那么这个结构体是有类指针和成员变量组成的。

//Person
@interface Person : NSObject
{
	@public
	int _age;//4bytes
	int _level;//4bytes
	int _code;//4bytes
}
@end
@implementation Person
@end
复制代码

经过 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main64.cpp 编译之后其实 Person 对象是:

struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	int _level;
	int _code;
};
复制代码

NSObject_IMPL 结构体:

struct NSObject_IMPL {
	Class isa;
};
复制代码

那么 NSObject 在内存中包括

isa
IOS 底层原理 类的本质--(2)
isa 地址就是 instance 的地址,其他成员变量排在后边,也就是 instance 的地址就是 isa

的地址。

那么这个 isa 指向的到底是什么呢? 请往下继续看: 先看下这段代码:

NSObject *ob1=[[NSObject alloc]init];
NSObject *ob2=[[NSObject alloc]init];

Class cl1 = object_getClass([ob1 class]);
Class cl2 = object_getClass([ob2 class]);

Class cl3 = ob1.class;
Class cl4 = ob2.class;

Class cl5 = NSObject.class;

NSLog(@" %p %p %p %p %p",cl1,cl2,cl3,cl4,cl5);
//0x7fff8e3ba0f0 0x7fff8e3ba0f0 
//0x7fff8e3ba140 0x7fff8e3ba140 0x7fff8e3ba140
复制代码

这代码是输出了几个 NSObject 的对象的类和 NSObject 的类对象的地址,可以看到 cl1==cl2cl3==cl4==cl5

Class的本质

我们知道不管是类对象还是元类对象,类型都是Class,class和mete-class的底层都是objc_class结构体的指针,内存中就是结构体。

Class objectClass = [NSObject class];        
Class objectMetaClass = object_getClass([NSObject class]);

复制代码

点击class来到内部,可以发现

typedef struct objc_class *Class;
复制代码

class 对象其实是指向objc_class的结构体,因此我们可以说类对象或元类对象在内存中其实就是objc_class结构体。

来到 objc_class 内部,在源码中经常看到这段源码

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;//isa

#if !__OBJC2__
    Class _Nullable super_class                    //父类          OBJC2_UNAVAILABLE;
    const char * _Nonnull name                              //obj名字 OBJC2_UNAVAILABLE;
    long version                                            //版本 OBJC2_UNAVAILABLE;
    long info                                               //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;
/* Use `Class` instead of `struct objc_class *` */
复制代码

这段代码明显是 已经 OBJC2_UNAVAILABLE ,说明代码已经不在使用了。那么 objc_class 结构体内部结构到底是什么呢?通过objc搜寻 runtime 的内容可以看到 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();
    }
    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);
    }
    //**后边省略
复制代码

我们发现这个结构体继承 objc_object 并且结构体内有一些函数,因为这是 c++ 结构体,在c上做了扩展,因此结构体中可以包含函数。我们来到 objc_object 内,截取部分代码

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
复制代码

那么我们之前了解到的,类中存储的类的成员变量信息,方法列表,协议列表,截取 class_rw_t 内部实现代码

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;//协议列表

    Class firstSubclass;
    Class nextSiblingClass;
**
//后边省略
}
复制代码

class_rw_t 是通过 bits.data() 获取的,截取 bits.data() 查看内部实现,而仅仅是 bits&FAST_DATA_MASK

class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
复制代码

而成员变量则是存储在 class_ro_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;
    }
};
复制代码

最后通过一张图总结一下:

IOS 底层原理 类的本质--(2)

那么我们来证明一下: 我们可以自定义一下一个和系统一样的结构体,那么我们当我们强制转化的时候,他们赋值会一一对应,此时我们就可以拿到结构体的内部的值。 下边代码是我们自定义的值:

//
//  Header.h
//  day02-类的本质1
//
//  Created by Charlie on 2019/7/2.
//  Copyright © 2019 www.fgyong.cn. All rights reserved.
//

#ifndef Header_h
#define Header_h

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
	cache_key_t _key;
	IMP _imp;
};

struct cache_t {
	bucket_t *_buckets;
	mask_t _mask;
	mask_t _occupied;
};

struct entsize_list_tt {
	uint32_t entsizeAndFlags;
	uint32_t count;
};

struct method_t {
	SEL name;
	const char *types;
	IMP imp;
};

struct method_list_t : entsize_list_tt {
	method_t first;
};

struct ivar_t {
	int32_t *offset;
	const char *name;
	const char *type;
	uint32_t alignment_raw;
	uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
	ivar_t first;
};

struct property_t {
	const char *name;
	const char *attributes;
};

struct property_list_t : entsize_list_tt {
	property_t first;
};

struct chained_property_list {
	chained_property_list *next;
	uint32_t count;
	property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
	uintptr_t count;
	protocol_ref_t list[0];
};

struct class_ro_t {
	uint32_t flags;
	uint32_t instanceStart;
	uint32_t instanceSize;  // instance对象占用的内存空间
#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;
};

struct class_rw_t {
	uint32_t flags;
	uint32_t version;
	const class_ro_t *ro;
	method_list_t * methods;    // 方法列表
	property_list_t *properties;    // 属性列表
	const protocol_list_t * protocols;  // 协议列表
	Class firstSubclass;
	Class nextSiblingClass;
	char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
	uintptr_t bits;
public:
	class_rw_t* data() { // 提供data()方法进行 & FAST_DATA_MASK 操作
		return (class_rw_t *)(bits & FAST_DATA_MASK);
	}
};

/* OC对象 */
struct xx_objc_object {
	void *isa;
};

/* 类对象 */
struct fy_objc_class : xx_objc_object {
	Class superclass;
	cache_t cache;
	class_data_bits_t bits;
public:
	class_rw_t* data() {
		return bits.data();
	}
	
	fy_objc_class* metaClass() { // 提供metaClass函数,获取元类对象
		// 上一篇我们讲解过,isa指针需要经过一次 & ISA_MASK操作之后才得到真正的地址
		return (fy_objc_class *)((long long)isa & ISA_MASK);
	}
};
#endif /* Header_h */

复制代码

这段代码亲测可用,直接复制自己新建 .h 文件导入'main.m'即可,将 main.m 改成 main.mm 或者将其他某一个 .m 改成 .mm 运行就可以运行了。

那么我们再拿出来经典的那张图挨着分析 isasuperclass 的指向

IOS 底层原理 类的本质--(2)

instance 对象验证

使用 p/x 输出 obj 16进制的地址,然后 isa指针需要经过一次 & ISA_MASK操作之后才得到真正的地址 。实施之后:

//object

Printing description of student:
<Student: 0x1021729c0>
(lldb) p/x object->isa //查看isa指针地址
(Class) $0 = 0x001dffff8e3ba141 NSObject 
(lldb) p/x objectClass//输出 objectClass的地址
(fy_objc_class *) $1 = 0x00007fff8e3ba140
(lldb) p/x 0x001dffff8e3ba141&0x00007ffffffffff8//计算得出object->isa真正的地址
(long) $2 = 0x00007fff8e3ba140 //0x00007fff8e3ba140是 objectClass地址和object->isa地址一样


//person

Printing description of person: <Person: 0x102175300>
(lldb) p/x person->isa
(Class) $3 = 0x001d800100002469 Person
(lldb) p/x 0x001d800100002469&0x00007ffffffffff8
(long) $4 = 0x0000000100002468
(lldb) p/x personClass
(fy_objc_class *) $5 = 0x0000000100002468//isa 和personclass地址都是0x0000000100002468

//student

(lldb) p/x student->isa
(Class) $6 = 0x001d8001000024b9 Student
(lldb) p/x 0x001d8001000024b9&0x00007ffffffffff8
(long) $7 = 0x00000001000024b8
(lldb) p/x studentClass
(fy_objc_class *) $8 = 0x00000001000024b8//studentclass 和isa地址都是0x00000001000024b8
(lldb) 
复制代码

从面的输出结果中我们可以发现instance对象中确实存储了isa指针和其成员变量,同时将instance对象的isa指针经过&运算之后计算出的地址确实是其相应类对象的内存地址。由此我们证明isa,superclass指向图中的1,2,3号线。

class 对象验证

接着我们来看 class 对象,同样通过上一篇文章,我们明确 class 对象中存储着 isa 指针, superclass 指针,以及类的属性信息,类的成员变量信息,类的对象方法,和类的协议信息,而通过上面对 object 源码的分析,我们知道这些信息存储在 class 对象的 class_rw_t 中,我们通过强制转化来窥探其中的内容

//objectClass and objectMetaClass

(lldb) p/x objectClass->isa
(__NSAtom *) $6 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1&0x00007ffffffffff8
(long) $7 = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass
(fy_objc_class *) $8 = 0x00007fff8e3ba0f0

//personClass and personMetaClass

(lldb) p/x personClass->isa
(__NSAtom *) $9 = 0x001d800100002441
(lldb) p/x personMetaClass
(fy_objc_class *) $10 = 0x0000000100002440
(lldb) p/x 0x001d800100002441&0x00007ffffffffff8
(long) $11 = 0x0000000100002440

//sutdentClass and studentMetaClass

(lldb) p/x studentClass->isa
(__NSAtom *) $12 = 0x001d800100002491
(lldb) p/x 0x001d800100002491&0x00007ffffffffff8
(long) $13 = 0x0000000100002490
(lldb) p/x studentMetaClass
(fy_objc_class *) $14 = 0x0000000100002490

复制代码

有此结果得知 objectMetaClass==objectClass->isa==0x00007fff8e3ba0f0 , personClass->isa==personMetaClass==0x0000000100002440 , studentClass->isa==studentMetaClass==0x0000000100002490 。 由此我们证明isa,superclass指向图中,isa指针的4,5,6号线,以及superclass指针的7,8,9号线。

meta-class对象验证

最后我们来看 meta-class 元类对象,上文提到 meta-class 中存储着 isa 指针, superclass 指针,以及类的类方法信息。同时我们知道 meta-class 元类对象与 class 类对象,具有相同的结构,只不过存储的信息不同,并且元类对象的 isa 指针指向基类的元类对象,基类的元类对象的 isa 指针指向自己。元类对象的 superclass 指针指向其父类的元类对象,基类的元类对象的 superclass 指针指向其类对象。 与 class 对象相同,我们同样通过模拟对 person 元类对象调用 .data 函数,即对 bits 进行 &FAST_DATA_MASK(0x00007ffffffffff8UL) 运算,并转化为 class_rw_t

// objectMetaClass->superclass = 0x00007fff8e3ba140  NSObject
//objectMetaClass->isa =   0x00007fff8e3ba0f0
//objectMetaClass = 0x00007fff8e3ba0f0

(lldb) p/x objectMetaClass->superclass
(Class) $20 = 0x00007fff8e3ba140 NSObject
(lldb) p/x objectMetaClass->isa
(__NSAtom *) $21 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1&0x00007ffffffffff8
(long) $22 = 0x00007fff8e3ba0f0
(lldb) p/x objectMetaClass
(fy_objc_class *) $23 = 0x00007fff8e3ba0f0

// personMetaClass->superclas=0x00007fff8e3ba0f0
//personMetaClass->isa=0x00007fff8e3ba0f0
//personMetaClass = 0x0000000100002440

(lldb) p/x personMetaClass->superclass
(Class) $25 = 0x00007fff8e3ba0f0
(lldb) p/x personMetaClass->isa
(__NSAtom *) $26 = 0x001dffff8e3ba0f1
(lldb) p/x personMetaClass
(fy_objc_class *) $30 = 0x0000000100002440

// studentMetaClass->superclas=0x0000000100002440
//studentMetaClass->isa=0x00007fff8e3ba0f0


(lldb) p/x studentMetaClass->superclass
(Class) $27 = 0x0000000100002440
(lldb) p/x studentMetaClass->isa
(__NSAtom *) $28 = 0x001dffff8e3ba0f1
(lldb) p/x 0x001dffff8e3ba0f1 & 0x00007ffffffffff8
(long) $29 = 0x00007fff8e3ba0f0
复制代码

有上面可以看出, studentMetaClass->isa , personMetaClass->isa , objectMetaClass->isa 结果 mask 之后都是 0x00007fff8e3ba0f0 ,与 p/x objectMetaClass 结果一致,则验证了13,14,15号线, studentMetaClass->superclass =0x0000000100002440 , personMetaClass = 0x0000000100002440 验证12号线, personMetaClass->isa=0x00007fff8e3ba0f0objectMetaClass = 0x00007fff8e3ba0f0 验证了11号线, objectMetaClass->superclass = 0x00007fff8e3ba140 NSObject 验证10号线。

最怕一生碌碌无为,还安慰自己平凡可贵。

本文章图片比较少,还是每个人都跟着代码敲一遍,印象比较深刻。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构算法与应用

数据结构算法与应用

塞尼 / 机械工业出版社 / 1999-3 / 49.00元

数据结构、算法与应用—C++语言描述(英文版),ISBN:9787111070177,作者:(美)塞尼 著一起来看看 《数据结构算法与应用》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具