和谐学习!不急不躁!!我是你们的老朋友小青龙~
前一篇文章: iOS底层分析之类的加载(上)
我们分析到_objc_init
里,
map_images->...->_read_images->readClass
,readClass内部:
- 类名写入非元类映射表(addNamedClass)
- 将类添加到所有类的那张表里(addClassTableEntry)
到目前为止,我们还没发现类的
ro
、rw
信息在哪里初始化。
纵观_read_images
函数内部几个操作,我们发现只有以下几个部分代码是跟类有关系: - ************ 修复类的混乱问题 ************(这个我们已经分析过)
- ************ 修复重映射没有被镜像文件加载进来的 类 ************
- ************ 类的加载处理 ************
- ************ 实现未来类解析 ************ 通过辅助代码,定位自定义Direction类:
/******* 辅助代码 START *********/
const char *mangledName = cls->nonlazyMangledName();
const char *comName = "Direction";
if (strcmp(comName, mangledName) == 0) {
printf("mangledName-->%s\n",mangledName);
}
/******* 辅助代码 END *********/
最后定位到类的加载处理
这块代码:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
...
// 8、************ 类的加载处理 ************
...
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
/******* 辅助代码 START *********/
const char *mangledName = cls->nonlazyMangledName();
const char *comName = "Direction";
if (strcmp(comName, mangledName) == 0) {
printf("mangledName-->%s\n",mangledName);
}
/******* 辅助代码 END *********/
...
realizeClassWithoutSwift(cls, nil);
...
}
...
}
realizeClassWithoutSwift
进入realizeClassWithoutSwift
:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
/**
* 对类cls执行首次初始化,
* 包括分配读写数据。
* 不执行任何Swift端初始化。
* 返回类的实际类结构。
* 锁定:runtimeLock必须由调用方进行写锁定
*/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
...
/// ************ ro、rw处理 ************
/// 这里的ro赋值请看后面的“通过地址指针强转类型并自动赋值”
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
...
}else{
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);// ro复制一份给rw
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
...
/// ************ 类的处理 ************
// 递归加载父类
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
// 递归加载元类
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
//关联父类与元类。也就是继承链与isa走位。
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
...
// 关联父类子类关系
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
...
// 方法名写入、方法排序
methodizeClass(cls, previously);
return cls;
}
执行完ro赋值,继续打印:
发现,已经成功打印出了run和sleep这两个实例方法。
控制台输入x/4gx cls
,并且进入下一次递归,定位到断点为止,控制台继续输入x/4gx cls
:
已经确定当前进入断点的是元类
,那就重复上述类的操作,查找ro
里是否存放着类方法
:
至此,类方法
也在元类
的ro里打印出来了。
methodizeClass
static void methodizeClass(Class cls, Class previously)
{
...
/******* 辅助代码 START *********/
const char *mangledName = cls->nonlazyMangledName();
const char *comName = "Direction";
if (strcmp(comName, mangledName) == 0) {
printf("类的加载处理|| mangledName-->%s\n",mangledName);
}
/******* 辅助代码 END *********/
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
...
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
...
}
为了保证研究的class是普通类Direction
,加上辅助代码,在printf哪一行打上断点
进行调试:
进入prepareMethodLists
:
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
...
/// 核心部分
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
...
}
for语句里打上断点:
说明for语句内部的mlist
就是前面ro的list
。
进入fixupMethodList
:
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
...
/// 设置SEL
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
...
/// 方法排序
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
...
}
查看方法排序,对fixupMethodList
增加一些打印代码
:
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
...
...
// 为了确保当前打印的是Direction类,
// 可以在methodizeClass方法,先断点卡住,然后清空控制台数据,再放开断点。
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
printf("%s ->%p\n",name,meth.name());
}
/// 方法排序
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
printf("排序后\n\n");
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
printf("%s ->%p\n",name,meth.name());
}
...
}
懒加载和非懒加载
一个工程随着时间的累积,类会越来越多,如果每一个类都在main启动之前就去读写ro、rw、methodlist等等,那无疑会大大增加
应用程序的启动速度
。所以苹果设计了懒加载类
和非懒加载类
模式。针对一些非懒加载类,只需要在类内部实现+(void)load
方法皆可。
回顾前面read_image方法:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {
...
// 8、************ 类的加载处理 ************
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// 实现了load方法才会进入for循环
...
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
...
realizeClassWithoutSwift(cls, nil);
...
}
...
}
注释里解释了,只有实现了load方法的非懒加载类,才会执行到for循环里,
继而执行realizeClassWithoutSwift
函数,对类进行一系列的初始化。
那么问题来了,那些没有实现load方法的懒加载类
的初始化是由哪里发起
的呢?
为了研究这个问题,我们新建了一个类DirectionChild
类,没有实现load方法。
接下来,我们在main
里实现DirectionChild:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"这里是main函数~");
DirectionChild *dir = [DirectionChild new];
NSLog(@"这里是main函数结束~");
}
return 0;
}
在realizeClassWithoutSwift
增加一段辅助代码,用于下断点定位DirectionChild
类:
/******* 辅助代码 START *********/
const char *mangledName = cls->nonlazyMangledName();
const char *comName = "DirectionChild";
if (strcmp(comName, mangledName) == 0) {
printf("类的加载处理 -> 我是DirectionChild");
}
/******* 辅助代码 END *********/
控制台打印的最后一句可以说明,执行到当前断点,已经走完了main之前的那些步骤,再看左边,可以发现懒加载类DirectionChild
的初始化realizeClassWithoutSwift
函数是由lookUpImpOrForward
发起的。
回到objc源码
,搜索lookUpImpOrForward
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
...
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
...
}
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
...
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
...
}
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
...
realizeClassWithoutSwift(cls, nil);
...
}
所以懒加载类
的大致流程走向:
- _objc_msgSend_uncached
- lookUpImpOrForward
- realizeAndInitializeIfNeeded_locked
- realizeClassMaybeSwiftAndLeaveLocked
- realizeClassMaybeSwiftMaybeRelock
- realizeClassWithoutSwift
具体是由哪个方法发起的呢?目前还不太确定,可能是alloc,也可能是类方法等。这边一一测试:
测试alloc
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"这里是main函数~ 测试alloc");
DirectionChild *dir = [DirectionChild new];
NSLog(@"这里是main函数结束~");
}
return 0;
}
测试类方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"这里是main函数~ 测试类方法");
[DirectionChild happy];
NSLog(@"这里是main函数结束~");
}
return 0;
}
测试结果:调用alloc或类方法都会对DirectionChild类进行初始化。
结论:对普通懒加载类发送一个消息,就会进入类初始化流程。
懒加载和非懒加载类 -> 加载流程
非懒加载类
(map_images的时候):
- _read_images
- _getObjc2ClassList
- readClass
- realizeClassWithoutSwift
- methodizeClass
懒加载类
(对类第一次发送消息的时候):
- _objc_msgSend_uncached
- lookUpImpOrForward
- realizeAndInitializeIfNeeded_locked
- realizeClassMaybeSwiftAndLeaveLocked
- realizeClassMaybeSwiftMaybeRelock
- realizeClassWithoutSwift
- methodizeClass
分类探究
分了结构
给添加分类
#import "Direction.h"
@interface Direction (category)
@property (nonatomic , strong) NSString *name_category;
@property (nonatomic , assign) NSInteger count;
- (void)eatSome;
+ (void)cleanSome;
@end
#import "Direction+category.h"
@implementation Direction (category)<NSObject>
- (void)eatSome{
NSLog(@"Direction+category ----eat something.");
}
+ (void)cleanSome{
NSLog(@"Direction+category ----cleanSome.");
}
@end
cd到分类所在目录,并执行命令$xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Direction+category.m
打开Direction+category.cpp
,全局搜索category
定位到分类结构:
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
其中:
name
:分类名cls
:指向的类instance_methods
:实例方法列表class_methods
:类方法列表protocols
:协议列表properties
:属性列表
注意:分类中的属性不会自动生成set、get方法。
代码拉到底,可以看到这样一段代码:
跟category结构体一一对应。
回到objc源码
,搜索category_t
:
再次验证了分类本质上也是一种结构体
。
分类的加载
category分类
的本质我们已经分析了,它和普通类一样,也有属性、协议、实例方法、类方法,那么它又是如何加载
到普通类里,以便我们直接通过类调用的呢?
回到objc源码,定位到methodizeClass
函数,看到一段跟category有关的代码:
static void methodizeClass(Class cls, Class previously){
...
auto rwe = rw->ext();
...
if (rwe) rwe->methods.attachLists(&list, 1);
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Attach categories.
// 附加到类别
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
...
}
我们发现,attachLists函数
需要rwe
有值才能执行,定位到rwe初始化代码,进入ext
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));
}
}
// 到此,我们知道ext返回get_ro_or_rwe,而get_ro_or_rwe的操作是在extAllocIfNeeded函数里
搜索extAllocIfNeeded
,发现有以下几个地方调用了:
- attachCategories函数(将类别的属性、协议、方法添加到类)
- demangledName函数
- class_setVersion函数(类的版本设置)
- addMethods_finish函数(添加方法)
- class_addProtocol函数(添加协议)
- _class_addProperty函数(添加属性)
- objc_duplicateClass函数
这几个初始化rwe
的函数,核心是在attachCategories
函数,全局搜一下它,看看哪里调用了它:
void attachToClass(Class cls, Class previously, int flags){
...
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
attachCategories(cls, list.array(), list.count(), flags);
}
...
}
static void load_categories_nolock(header_info *hi){
...
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
...
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
...
}
根据搜索结果来看,只有attachToClass
和load_categories_nolock
会调用attachCategories
函数,继而对rwe进行赋值
。
分类的探索
接下去的部分,会放在下篇文章继续《iOS底层分析之类的加载(下)》
通过地址指针强转类型并自动赋值
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
...
/// ************ ro、rw处理 ************
auto ro = (const class_ro_t *)cls->data();
...
}
进入data
:
class_rw_t *data() const {
return bits.data();
}
进入data
:
class_rw_t* data() const {
// 这个data函数通过bit地址与上FAST_DATA_MASK,返回一个地址指针
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
maim.m
里写一段代码:
/*
// Method本质上就是objc_method结构体指针
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
*/
// 按照objc_method格式自定义了一个结构体
struct ssj_objc_method {
SEL _Nonnull ssj_method_name OBJC2_UNAVAILABLE;
char * _Nullable ssj_method_types OBJC2_UNAVAILABLE;
IMP _Nonnull ssj_method_imp OBJC2_UNAVAILABLE;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
Method m = class_getInstanceMethod(Direction.class, @selector(eatSome));
struct ssj_objc_method * objc_m =(struct ssj_objc_method *) class_getInstanceMethod(Direction.class, @selector(eatSome));
NSLog(@"这里是main函数结束~");
}
return 0;
}
/**
* 分析这段代码,class_getInstanceMethod函数最终返回的是一个结构体指针,
* 即地址指针;理论上,只要类型匹配的上,结构体内部组织也一样,就可以强转
*/
为了验证分析的猜想,运行代码:
结果证明:地址指针除了可以访问到值以外,还可以用来做强转。
再回头来看这句代码auto ro = (const class_ro_t *)cls->data();
意思就是一旦从bits里读取到data数据,就会按照class_ro_t的格式,对里面的数据一一赋值,然后返回一个class_ro_t指针给ro。
代码
链接: pan.baidu.com/s/17bzAR9wp…
密码: jo8a
--来自百度网盘的分享
本文总结
-
研究的方向:ro、rw在哪里初始化?
-
研究的方式:
-
通过辅助代码,定位cls=Direction类,阅读_read_images函数内部操作
-
定位到realizeClassWithoutSwift
-
定位到methodizeClass(方法名写入、方法排序)
-
rwe初始化由 extAllocIfNeeded函数完成
-
懒加载类和非懒加载类(实现了+load方法) -> 加载流程
-
分类的本质
下篇预告:
iOS底层分析之类的加载(下)
----待更新
(分类的加载)