runtime源码版本: objc4-818.2(opensource.apple.com/tarballs/ob…)
category作用:
- 为已有类扩展功能
- 拆分臃肿类
category的结构
struct category_t {
const char *name; // 分类名
classref_t cls; // 对应的主类
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods; // 实例方法
WrappedPtr<method_list_t, PtrauthStrip> classMethods; // 类方法
struct protocol_list_t *protocols; // 协议列表
struct property_list_t *instanceProperties; // 实例属性
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties; // 类属性
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
category原理:
在编译期, 每个分类都转换成一个category_t结构体, 在运行期,利用runtime的机制将分类中的方法 / 协议 / 属性附加到类对象和元类对象的方法 / 协议 / 属性列表中
与主类有同名方法时, 或者与其他分类同名时, 调用顺序是怎么样的
- 与主类有同名方法时,优先调用分类的方法
- 与其他分类有同名方法时, 越晚编译的越优先调用, 因为越晚编译的分类的方法会附加在方法列表中的越前边
与extension的区别
- extension在编译器决议,在编译的时候,将扩展中的内容与已有类组合成一个完整的类, category在运行期决议,在运行时,将自己的方法 / 协议 / 属性附加到类对象和元类对象的方法 / 协议 / 属性列表中
- 由于1的缘故,并且系统类都在动态库,不参与我们的编译,所以我们无法给系统类增加扩展,只能给系统类增加分类
分类是否能增加成员变量, 为什么
不能
一方面, 分类结构体中没有ivarList
另一方面, 类结构中, class_ro_t 中包含instanceSize, 也就是说, 在编译时, 每个对象的尺寸大小已经固定下来了, 不能再通过category或者runtime来动态添加成员变量了, 一旦可以动态增加成员变量, 那每个已经创建的对象的内存布局都会发生变化.这样太不安全
如何给 Category 添加属性?关联对象以什么形式进行存储?
category本身支持添加属性, 只是不会生成待下划线的成员变量以及对应的set/get方法
所以, 需要我们手动实现set/get方法, 利用关联来实现目的
关联对象本质是一个双重的HashMap
分类方法具体是怎么附加到类的
- 新建一个列表mlist,根据编译结果mach-o,取出分类,从后往前,将分类的方法列表依次加入到mlist中
- 将主类的方法列表容器class_rw_t->methods扩容, 将主类原有的方法列表, 往后挪动mlist长度的位置
- 然后将这个列表中的内容, 从前往后依次附加到主类的class_rw_t->methods中
- 苹果有个小优化,每次mlist长度最大为64,也就说每批处理64个,分批处理
具体代码实现:
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
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);
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();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
// 核心在这里
attachLists(mlists, mcount);
// 小技巧
1. cats_list的列表中的分类, 最前边的分类的方法会放到mlists的最后边.
2. 一次性最多attachLists 64个方法/协议/属性列表到类本身的方法/协议/属性列表
class list_array_tt {
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;
// 核心在这里
// 将原有的列表往后挪addedCount个位置, 倒序的挪是为了避免覆盖有用值
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();
}
}
}
源码路径
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
static void load_categories_nolock(header_info *hi)
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags)