Clang Rewrite视角OC类结构分析与部分常见考点

知识小集 2020-11-22 23:01


作者 | 收納箱 
来源 | 掘金,点击阅读原文查看作者更多文章

0. 简介

本文利用clang -rewrite-objc探索了类、元类中存储的信息;类、元类、根源类的关系。

最后,通过 属性重排 展示了苹果对成员变量的布局优化,再次重申 《WWDC20 iOS14 Runtime优化》 中苹果反复提到的观点:

在开发中,请使用运行时API,而不是指针偏移、mask等更底层的方式获取信息。

因为苹果可能对这底层逻辑进行优化,如果直接访问会有不稳定的问题,但对外提供的API是稳定的,所以推荐使用运行时API来解决遇到的问题。

1.类、元类、根源类

1.1 源码

main.m

#import <Foundation/Foundation.h>

@interface YCFather : NSObject
@property (nonatomic, copy) NSString *name;
+ (void)clsSayHello1;
- (void)insSayHello1;
@end
@implementation YCFather
+ (void)clsSayHello1 {}
- (void)insSayHello1 {}
@end

@interface YCSon : YCFather
@property (nonatomic, copy) NSString *midName;
+ (void)clsSayHello2;
- (void)insSayHello2;
@end
@implementation YCSon
+ (void)clsSayHello2 {}
- (void)insSayHello2 {}
@end

int main(int argc, char * argv[]) {
return 0;
}

1.2 Clang -rewrite-objc

执行下面的代码,会输出一个 main.cpp 文件。

xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc -fobjc-arc -mios-version-min=8.0.0 -fobjc-runtime=ios-8.0.0 main.m

⚠️ 注意:此处使用 clang -rewrite-objc 得到的c++代码只能做参考,与真实的代码还是会有一定出入,但逻辑基本一致

1.2.1 类的基本结构

搜索 NSObject

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
__unsafe_unretained Class isa;
};

我们可以看到 NSObject 主要成员变量就是 Class isa ,下面我们再看看 YCFather 和 YCSon 。

#ifndef _REWRITER_typedef_YCFather
#define _REWRITER_typedef_YCFather
typedef struct objc_object YCFather;
typedef struct {} _objc_exc_YCFather;
#endif

extern "C" unsigned long OBJC_IVAR_$_YCFather$_name;
struct YCFather_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 继承 NSObject_IMPL 的成员变量 isa
NSString *__strong _name; // 追加自己的 _name 成员变量
};

// @property (nonatomic, copy) NSString *name;
// + (void)clsSayHello1;
// - (void)insSayHello1;
/* @end */

// @implementation YCFather

// clsSayHello1 IMP
static void _C_YCFather_clsSayHello1(Class self, SEL _cmd) {}

// insSayHello1 IMP
static void _I_YCFather_insSayHello1(YCFather * self, SEL _cmd) {}

// name 属性的 get 方法
static NSString * _I_YCFather_name(YCFather * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_YCFather$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

// name 属性的 set 方法
static void _I_YCFather_setName_(YCFather * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct YCFather, _name), (id)name, 0, 1); }
// @end


#ifndef _REWRITER_typedef_YCSon
#define _REWRITER_typedef_YCSon
typedef struct objc_object YCSon;
typedef struct {} _objc_exc_YCSon;
#endif

extern "C" unsigned long OBJC_IVAR_$_YCSon$_midName;
struct YCSon_IMPL {
struct YCFather_IMPL YCFather_IVARS; // 继承 YCFather_IMPL 的成员变量 isa 和 _name
NSString *__strong _midName; // 追加自己的 _midName 成员变量
};

// @property (nonatomic, copy) NSString *midName;
// + (void)clsSayHello2;
// - (void)insSayHello2;
/* @end */

// @implementation YCSon

// clsSayHello2 IMP
static void _C_YCSon_clsSayHello2(Class self, SEL _cmd) {}

// insSayHello2 IMP
static void _I_YCSon_insSayHello2(YCSon * self, SEL _cmd) {}

// midName 属性的 get 方法
static NSString * _I_YCSon_midName(YCSon * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_YCSon$_midName)); }

// midName 属性的 set 方法
static void _I_YCSon_setMidName_(YCSon * self, SEL _cmd, NSString *midName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct YCSon, _midName), (id)midName, 0, 1); }
// @end

1.2.2 类中的信息

YCFather 和 YCSon 的分析类似,这里以 YCFather 为例进行分析。

  • 成员变量列表 OBJC$_INSTANCE_VARIABLES_YCFather

static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[1];
} _OBJC_$_INSTANCE_VARIABLES_YCFather __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
1, // 只有一个成员变量
{{(unsigned long int *)&OBJC_IVAR_$_YCFather$_name, "_name", "@\"NSString\"", 3, 8}} // 成员变量_name
};
  • 实例方法列表 OBJC$_INSTANCE_METHODS_YCFather

static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_YCFather __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
5, // 有5个实例方法,SEL、Encode、IMP
{{(struct objc_selector *)"insSayHello1", "v16@0:8", (void *)_I_YCFather_insSayHello1},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_YCFather_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YCFather_setName_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_YCFather_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YCFather_setName_}}

};
  • 类方法列表 OBJC$_CLASS_METHODS_YCFather

static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_YCFather __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1, // 有1个类方法,SEL、Encode、IMP
{{(struct objc_selector *)"clsSayHello1", "v16@0:8", (void *)_C_YCFather_clsSayHello1}}
};
  • 属性方法列表 OBJC$_PROP_LIST_YCFather

static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_YCFather __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1, //1个属性
{{"name","T@\"NSString\",C,N,V_name"}}
};
  • 元类对象 RO 的初始化 OBJCMETACLASS_RO_$_YCFather

⚠️ 常见考点1:类方法存储在元类 (注意static,这些信息只存在一份)

static struct _class_ro_t _OBJC_METACLASS_RO_$_YCFather __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1, sizeof(struct _class_t), sizeof(struct _class_t),
0,
"YCFather",
(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_YCFather, // YCFather 的类方法存储在元类
0,
0,
0,
0,
};
  • 类对象 RO 的初始化 OBJCCLASS_RO_$_YCFather

⚠️ 常见考点2:实例方法存储在类 (注意static,这些信息只存在一份)

static struct _class_ro_t _OBJC_CLASS_RO_$_YCFather __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct YCFather, _name), sizeof(struct YCFather_IMPL),
0,
"YCFather",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_YCFather, // 方法列表
0,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_YCFather, // 成员变量列表
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_YCFather, // 属性列表
};
  • 元类 OBJC_METACLASS__YCFather∗∗、类∗∗OBJC_CLASS__YCFather**、类 **OBJC_CLASS__YCFather∗∗、类∗∗OBJC_CLASS__YCFather 的初始化

extern "C" __declspec(dllimport) struct _class_t OBJC_METACLASS_$_NSObject;
// 元类的初始化
extern "C" __declspec(dllexport) struct _class_t OBJC_METACLASS_$_YCFather __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_NSObject,
0, // &OBJC_METACLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_YCFather,
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_NSObject;
// 类的初始化
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_YCFather __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_YCFather,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_YCFather,
};
  • 元类与类的关系绑定

⚠️ 常见考点3:类、元类、根源类的关系

static void OBJC_CLASS_SETUP_$_YCFather(void ) {
OBJC_METACLASS_$_YCFather.isa = &OBJC_METACLASS_$_NSObject; // YCFather元类的 isa 为 NSObject元类(根元类)
OBJC_METACLASS_$_YCFather.superclass = &OBJC_METACLASS_$_NSObject; // YCFather元类的 superclass 为 NSObject元类
OBJC_METACLASS_$_YCFather.cache = &_objc_empty_cache;
OBJC_CLASS_$_YCFather.isa = &OBJC_METACLASS_$_YCFather; // YCFather类的 isa 为 YCFather元类
OBJC_CLASS_$_YCFather.superclass = &OBJC_CLASS_$_NSObject; // YCFather类的 superclass 为 NSObject类
OBJC_CLASS_$_YCFather.cache = &_objc_empty_cache;
}

// 这里特意补上 YCSon 的关系绑定
static void OBJC_CLASS_SETUP_$_YCSon(void ) {
OBJC_METACLASS_$_YCSon.isa = &OBJC_METACLASS_$_NSObject; // YCSon元类的 isa 为 NSObject元类(根元类)
OBJC_METACLASS_$_YCSon.superclass = &OBJC_METACLASS_$_YCFather; // YCSon元类的 superclass 为 YCSon元类的
OBJC_METACLASS_$_YCSon.cache = &_objc_empty_cache;
OBJC_CLASS_$_YCSon.isa = &OBJC_METACLASS_$_YCSon; // YCSon类的 isa 为 YCSon元类
OBJC_CLASS_$_YCSon.superclass = &OBJC_CLASS_$_YCFather; // YCSon类的 superclass 为 YCFather类
OBJC_CLASS_$_YCSon.cache = &_objc_empty_cache;
}

这里比较可惜的是,我们无法直观地看到 OBJC_METACLASS_$_NSObject 中的结构,但我们可以通过运行时或源码分析,已经得到了下面这张经典的图片了。根据上面的代码分析,我们也可以直观的验证下图所示的大部分关系。


2. 属性的成员变量重排

2.1 源码

main.m

#import <Foundation/Foundation.h>

@interface YCFather : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;

@end
@implementation YCFather

@end

int main(int argc, char * argv[]) {
return 0;
}

2.2 Clang -rewrite-objc

执行下面的代码,会输出一个 main.cpp 文件。

xcrun -sdk iphoneos clang -arch arm64 -w -rewrite-objc -fobjc-arc -mios-version-min=8.0.0 -fobjc-runtime=ios-8.0.0 main.m

⚠️ 注意:此处使用 clang -rewrite-objc 得到的c++代码只能做参考,与真实的代码还是会有一定出入,但逻辑基本一致

我们可以看到:成员变量的顺序和我们声明属性的顺序不同。

struct YCFather_IMPL {
struct NSObject_IMPL NSObject_IVARS;
char _c1;
char _c2;
int _age;
NSString *__strong _name;
NSString *__strong _nickName;
long _height;
};

这是因为苹果进行了内存优化,我们可以对比一下:

#import <objc/runtime.h>

struct NSObject_IMPL {
__unsafe_unretained Class isa;
};

struct YCFather {
struct NSObject_IMPL NSObject_IVARS;
NSString *__strong _name;
NSString *__strong _nickName;
int _age;
long _height;
char _c1;
char _c2;
};

struct YCFather_IMPL {
struct NSObject_IMPL NSObject_IVARS;
char _c1;
char _c2;
int _age;
NSString *__strong _name;
NSString *__strong _nickName;
long _height;
};

@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
printf("sizeof(struct YCFather): %lu\nsizeof(struct YCFather_IMPL): %lu", sizeof(struct YCFather), sizeof(struct YCFather_IMPL));
}
@end

// 输出
sizeof(struct YCFather): 48
sizeof(struct YCFather_IMPL): 40

可以看到经过重排之后的结构体大小比没有重排的结构体大小优化了48−40=848-40=848−40=8字节。

当然,我们同样可以通过调试输出的方式来验证真实的内存布局与我们的分析是一致的:




推荐阅读

☞  iOS 程序员摇身变成 macOS 程序员?可能并没有那么简单
☞  重磅开源,Swift Server Side 又添一利器
☞  NSBlockOperation面试与正确用法
☞  iOS 是如何获取夜间模式启动图缓存路径?


就差您点一下了 👇👇👇