iOS-类的结构初探

1,047 阅读6分钟

前言

在类的实例对象创建过程中,通过alloc的分析,我们知道实例对象通过isa(unoin)存储了对应的class的地址,从而将实例对象和class关联起来。那么,class到底是什么呢?我们来探索下。

预备知识

oc语言是c/c++的超集,为了便于研究底层结构,我们把oc源码转换成c++代码,这就需要使用LLVM的oc语言的前端编译器clang。

1.clang基本用法

clang -rewrite-objc -main.m -o main.cpp

该命令把目标文件编译成c++文件,方便我们研究底层代码。

2.当目标文件中引用了UIKit等framework时,需要指定平台、系统版本、sdk等信息

clang -rewrite-objc -fobjc-arc -fobjc-runtinme=ios-13.2.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk main.m -o main.cpp

如上指令指定了arc、系统版本、模拟器sdk等

3.xcrun clang

xcode安装了xcrun命令,对clang进行了一些封装,更好用写。建议使用。 真机:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm_64.cpp

模拟器:

xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m -o main_x86_64.cpp

一、isa的存在性

1.探索源码

@interface LGPerson : NSObject

@property(nonatomic, strong) NSString *name;

@end

@implementation LGPerson

@end

定义一个继承自NSObject的LGPerson类,为了便于研究底层结构,使用clang转成c++代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LGPerson.m -o LGPerson_arm64.cpp

针对LGPerson_arm64.cpp进行分析, 搜索LGPerson,找到如下代码块:

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};

主要由两部分组成:

  1. 定义LGPerson
typedef struct objc_object LGPerson;
  1. 定义结构体LGPerson_IMPL:
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};

1. 实例对象底层: isa指向objc_class

第二部分代码:

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString * _Nonnull _name;
};

是LGPerson实例对象的底层结构:一个包含数据成员NSObject_IVARS,_name的结构体。

  • _name就是属性对应的实例变量,用于存储属性数据。
  • NSObject_IVARS是一个struct,找到其定义:
struct NSObject_IMPL {
	Class isa;
};

可知,这是NSObject的实例对象的结构。

typedef struct objc_class *Class;
  • isa是一个指向objc_class的结构体指针
  • 由此可知:所有的实例对象内部,有一个isa,指向objc_class。
  • 通过alloc的源码分析可知:在64位架构下,isa通常情况下对应的不再是一个纯指针,而是一个优化后的nonpointer。

2. objc_class:继承objc_object

struct objc_class : objc_object {
	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() const {
        return bits.data();
    }
  ...
 }

3. objc_object: 只有isa

这样回到了我们已经熟悉的objc_object:

struct objc_object {
	private:
    	isa_t isa;
    ...
}

4. 总结

  1. objc_object是所有的基石,是一个结构体,具有isa_t类型的isa
  2. 类的底层是objc_class结构体,继承自objc_object, 同样具有isa
  3. 实例对象底层是一个结构体,根据继承规则,最终源头来自于NSObject对应的结构体:只有一个成员:Class isa,指向objc_class,所以所有的实例对象仍然具有一个isa
  4. 以objc_object为模板,oc中万物都是对象

二.isa的指向性

源码:

LGPerson *person = [[LGPerson alloc] init];
person.name = @"zxdix";

1. 实例对象isa指向类 Class

利用LLDB指令,断点调试,取出实例对象person的isa指向:

2.类的isa指向:元类 metaClass

继续查看LGPerson的内存: 再次查找isa指向,发现仍是一个LGPerson,但是内存地址不同,称之为元类metaClass

3.元类的isa指向:根元类 rootMetaClass, 根元类的isa指向自己

继续探索isa: 发现,元类的isa指向一个NSObject,这个NSObject的isa指向自己。然后机会进入无限循环,不会再出现新的东西了。
那么这个NSObject是什么呢?是我们平时说的根类NSObject(NSObject.class)吗? 由上图可知此处的NSObject并不是根类(NSObject.class),反而是根类(NSObject.class)的isa,称之为根元类

4. isa走向总结:实例对象->类对象->元类对象->根元类对象

根元类也是元类,所以指向自己
用一个经典的图说明如下:(ps:太懒了,偷了一个嘿嘿)

三、superclass的指向

在objc_class定义中, 有一个数据成员superclass:

struct objc_class : objc_object {
	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() const {
        return bits.data();
    }
  ...
 }

顾名思义,应该是指向父类对象,我们通过查看内存验证下(还是研究isa时的使用的代码,懒就一个字): superclass确实指向父类。 验证一遍类的superclass,同样验证一遍元类metaClass的superclass,可以得到如下的继承图: 有两点需要注意:

  1. NSObject(class)的superclass是nil
  2. NSObject(metaClass, 根元类)的superclass是NSObject(class, 根类),也就是说无论是类还是元类,沿着继承树追追溯,最终到nil结束。

结合isa,可以得到一个经典的走位图:(ps:偷图会上瘾的)

四、类的结构初探

objc_class的源码如下:

struct objc_class : objc_object {
	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() const {
        return bits.data();
    }
  ...
 }

分析类对象的内存结构(arm64架构下):粗看,具有4个成员

  1. 继承自objc_object, 所以第一个变量是isa,8个字节
  2. superclass, 指针, 8字节
  3. cache, 陌生的cache_t结构,未知
  4. bits,未知

1. cache_t分析

源码初看很复杂,有很定未知的定义,但是看结构,分成3个部分:

  1. static变量不会存在结构体中,忽略
  2. 第1部分,#if...#elif...#elif..#else...#endif结构,其实就是两个变量:以第一个#if为例:_bucketsmaskexplicit_atomic是个泛型定义,本质存储的是struct bucket_t *指针和mask_t(uint32)。其他if分支也是一样的,长度一致。 3.第2部分,2字节:typedef unsigned short uint16_t(LP64是成立的:指的是long、pointer类型都是64位的); 4.第3部分,同样也是2字节; 所以cache_t的size是: 8 + 4 + 2 + 2 = 16字节

2. 类的内存结构

到此,我们知道objc_class的内存分布如下图: bits成员的地址偏移是32,则通过class的首地址偏移32字节,即为bits的地址。

3. 获取class_rw_t

利用地址偏移,可以获取bits的地址,从而读取class_rw_t:

4. methods、properties

在class_rw_t中有几个重要的方法:methods、properties。我们可以通过打印输出内容。

  1. methods:可知里面存放了实例方法的相关信息。
(lldb) p/x LGPerson.class
(Class) $1 = 0x0000000100002188 LGPerson
(lldb) p/x (class_data_bits_t*)(0x00000001000021a8)  // 偏移32字节
(class_data_bits_t *) $2 = 0x00000001000021a8
(lldb) p *$2
(class_data_bits_t) $3 = (bits = 4320411204)
(lldb) p $3.data()
(class_rw_t *) $4 = 0x0000000101843e40
(lldb) p *$4
(class_rw_t) $5 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975592
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $5.methods()
(const method_array_t) $6 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000020b0
      arrayAndFlag = 4294975664
    }
  }
}
(lldb) p $6.list
(method_list_t *const) $7 = 0x00000001000020b0
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 3
    first = {
      name = ".cxx_destruct"
      types = 0x0000000100000f8d "v16@0:8"
      imp = 0x0000000100000e60 (KCObjc`-[LGPerson .cxx_destruct])
    }
  }
}
(lldb) p $8.get(1)
(method_t) $10 = {
  name = "name"
  types = 0x0000000100000f95 "@16@0:8"
  imp = 0x0000000100000e90 (KCObjc`-[LGPerson name])
}
(lldb) p $8.get(2)
(method_t) $11 = {
  name = "setName:"
  types = 0x0000000100000f9d "v24@0:8@16"
  imp = 0x0000000100000eb0 (KCObjc`-[LGPerson setName:])
}
  1. properties: 存放了属性相关信息
(lldb) p $5.properties()
(const property_array_t) $12 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002128
      arrayAndFlag = 4294975784
    }
  }
}
(lldb) p $12.list
(property_list_t *const) $14 = 0x0000000100002128
(lldb) p *($14)
(property_list_t) $15 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}