类的加载(一)这里主要聊的是类的加载,这篇文章聊下分类的加载
问题先行
1、分类中为什么添加的属性不能自动生成set
,get
方法?
2、类和分类的加载可以分为几种情况?
资源准备
1、objc源码下载opensource.apple.com/
分类
分类的本质
通过底层看本质,我们需要把源代码编程成C++
代码来分析
@interface ASSon (ext)
@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)NSInteger age;
- (void)instanceMethod1;
+ (void)instanceMethod2;
@end
@implementation ASSon (ext)
- (void)instanceMethod1 {}
+ (void)instanceMethod2 {}
@end
兼容编译(代码少)clang -rewrite-objc -rewrite-objc main.m -o main-arm64.cpp
完成编译(不报错)xcrun -sdk iphonesimulator clang -rewrite-objc -rewrite-objc main.m -o main-arm64.cpp
C++分析
通过clang编译指令,会生成对应的cpp文件,我们从下向上分析app文件(可用信息主要在下方)
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_ASSon_$_ext,
};
可以看出分类是存储在MachO
文件的__DATA
段的__objc_catlist
中
static struct _category_t _OBJC_$_CATEGORY_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"ASSon",
0, // &OBJC_CLASS_$_ASSon,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_ASSon_$_ext,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_ASSon_$_ext,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_ASSon_$_ext,
};
static void OBJC_CATEGORY_SETUP_$_ASSon_$_ext(void ) {
_OBJC_$_CATEGORY_ASSon_$_ext.cls = &OBJC_CLASS_$_ASSon;
}
可以看出分类的结构体类型为_category_t
,进而搜索_category_t
可以看出其是个结构体如下
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;
};
- 这个结构体里面有类名字,cls,对象方法列表,类方法列表,协议列表,属性列表,但是没有变量列表,这也是分类属性没有
set
,get
方法的原因。 - 分类的实例方法和类方法是分开在两个
_method_list_t
中的,是因为分类是没有元分类的,分类的方法是在运行时通过attachToClass
插入到class
的。
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"instanceMethod1", "v16@0:8", (void *)_I_ASSon_ext_instanceMethod1}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"instanceMethod2", "v16@0:8", (void *)_C_ASSon_ext_instanceMethod2}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@\"NSString\",C,N"},
{"age","Tq,N"}}
};
有一个对象方法和一个类方法,格式为:sel+签名+地址
,和method_t
结构体一样。我们发现存在属性的变量名,但是没有相应的set
和get
方法。
总结
- 分类底层的结构体类型是
_category_t
- 分类可以添加方法和属性,不能添加成员变量
- 分类添加的属性没有
set
和get
方法实现 - 分类有两个方法列表,表明分类是没有元分类来进行存储方法的
分类的加载
加载时机分析
类的加载(一)中提及过分类的加载路径realizeClassWithoutSwift
->methodizeClass
,继续进行路径追踪methodizeClass
->attachToClass
->attachCategories
,发现attachCategories
才是分类加载的核心方法。
其中attachToClass
方法主要是将分类添加到主类中
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
/// 找到一个分类进来一次,即一个个加载分类
auto it = map.find(previously);
/// 这里会走进来,当主类没有实现load方法,分类开始加载,迫使主类开始加载
if (it != map.end()) {
category_list &list = it->second;
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);
}
map.erase(it);
}
}
attachCategories
方法为初始化rwe
,rwe
的初始化主要涉及:addMethod
、addProperty
、addprotocol
,即对原始类进行修改或者处理时,才会进行rwe
的初始化
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
/// 代码省略......
/// 分类的最大个数为64
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
/// rwe的初始化(为主类中添加属性,方法,协议等做准备)
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
/// 方法处理
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
/// 属性处理
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
/// 协议处理
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
attachLists
将分类数据添加到主类
中
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
总结:分类的加载主要分为3步
- 分类数据加载时机:根据类和分类是否实现
load
方法来区分不同的时机 attachCategories
准备分类数据attachLists
将分类数据添加到主类中
加载流程分析
通过全局搜索attachCategories
只有两个地方有调用,attachToClass
和load_categories_nolock
在全局搜索load_categories_nolock
只有两个地方调用loadAllCategories
和_read_images
,loadAllCategories
只有一个地方调用load_images
。
在全局搜索attachToClass
只有一个地方调用methodizeClass
。
通过上面分析,也知道类和分类的加载时机都和load
方法实现有关。因此可以进行类和分类的四种情况搭配分析。
非懒加载类与非懒加载分类
调用方法路径
- 类的加载
map_images
->map_images_nolock
->_read_images
->readClass
->_getObjc2NonlazyClassList
->realizeClassWithoutSwift
->methodizeClass
- 分类的加载
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
->attachLists
非懒加载类与懒加载分类
调用方法路径
- 类的加载
map_images
->map_images_nolock
->_read_images
->readClass
->_getObjc2NonlazyClassList
->realizeClassWithoutSwift
->methodizeClass
- 分类的加载
map_images
->map_images_nolock
->_read_images
->readClass
->_getObjc2NonlazyClassList
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
->attachLists
懒加载类与懒加载分类
调用方法路径
- 类的加载
lookUpImpOrForward
->realizeAndInitializeIfNeeded_locked
->realizeClassMaybeSwiftAndLeaveLocked
->realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
->methodizeClass
- 分类的加载
lookUpImpOrForward
->realizeAndInitializeIfNeeded_locked
->realizeClassMaybeSwiftAndLeaveLocked
->realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
->attachLists
懒加载类与非懒加载分类
调用方法路径
- 类的加载
map_images
->map_images_nolock
->_read_images
->readClass
->_getObjc2NonlazyClassList
->realizeClassWithoutSwift
->methodizeClass
- 分类的加载
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
->attachLists
通过以上条用路径分析可以得出如下结论:
- 非懒加载类+非懒加载分类:类的加载在
_read_images
处,分类的加载在load_images
方法中,首先对类进行加载,然后把分类的信息加入到类中 - 非懒加载类+懒加载分类:类的加载在
_read_images
处,分类的加载则在编译时 - 懒加载类+懒加载分类:类的加载在第一次消息发送的时候,分类的加载则在编译时
- 懒加载类+非懒加载分类:只要分类实现了
load
,会迫使主类提前加载,即和第一种情况一致
问题先行解答
1、分类中为什么添加的属性不能自动生成set
,get
方法?
通过_category_t
结构体可以发现,其结构体内并没有成员变量列表,这也就是为什么添加的属性不能自动生成set
,get
方法。
2、类和分类的加载可以分为几种情况?
见如上分类的加载->加载流程分析