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

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

内容简介:复习一下经过

复习一下 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号线。

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

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


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

查看所有标签

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

Automate This

Automate This

Christopher Steiner / Portfolio / 2013-8-9 / USD 25.95

"The rousing story of the last gasp of human agency and how today's best and brightest minds are endeavoring to put an end to it." It used to be that to diagnose an illness, interpret legal docume......一起来看看 《Automate This》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

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

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具