Category的使用场合是什么?
为某个类拓展方法,分模块。
Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

源码解读顺序
-
objc-os.mm
- _objc_init
- map_images
- map_images_nolock
-
objc-runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- attachLists
- realloc、memmove、 memcpy
- 分类数据合并(实现源码)
// 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, category_list *cats, bool flush_caches)
{
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
//方法二维数组
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
//属性二维数组
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
//协议二维数组
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) { //取出某个分类
auto& entry = cats->list[I];
//取出分类方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//最后面取出的分类放在前面
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//得到类对象里的数据
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//将所有分类的的对象方法附加到类对象的方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//将所有分类的的属性 附加到类对象的属性列表中
rw->properties.attachLists(proplists, propcount);
free(proplists);
//将所有分类的的协议 附加到类对象的协议列表中
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
- 方法合并(实现源码)
//addedCount 分类的个数
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;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// array()->lists原来的方法列表
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// addedLists 所有分类的方法列表
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}

Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
有load方法
load方法在runtime加载类、分类的时候调用
load方法是通过函数地址直接调用,所以是不是分类调用会,其他方法是一般通过消息机制调用也就是通过isa指针层层寻找方法
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
objc4源码解读
-
objc-os.mm
- _objc_init
- load_images
-
prepare_load_methods
- schedule_class_load
- add_class_to_loadable_list
- add_category_to_loadable_list
-
call_load_methods
-
call_class_loads
-
call_category_loads
(*load_method)(cls, SEL_load) -
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
/*********************************************************************** 底层源码解读
* call_load_methods
**********************************************************************/
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();//先调用类的load方法
}
// 2. Call category +loads ONCE
more_categories = call_category_loads(); //再调用分类的load方法
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
** call_class_loads**
/*********************************************************************** 底层源码解读
* call_class_loads
**********************************************************************/
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
//取出load 方法
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load); //取到函数地址直接调用
}
// Destroy the detached list.
if (classes) free(classes);
}
** call_category_loads**
/*********************************************************************** 底层源码解读
* call_category_loads
**********************************************************************/
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);//获取方法地址直接调用
cats[i].cat = nil;
}
}
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[I];
}
return new_categories_added;
}
类的加载顺序
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// 递归调用
schedule_class_load(cls->superclass);
//cls 添加到 loadable_classes数组的后面
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
** prepare_load_methods**
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
//按照编译顺序去加载类
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
//分类只认编译顺序
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
memmove、memcpy的区别
相同
作用是拷贝一定长度的内存的内容
不同
当内存发生局部重叠的时候,memmove保证数据的完整性,memcpy不保证拷贝的结果的正确
void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t count);
load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
调用时刻
- load是runtime加载类、分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
+ load
先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
再调用分类的load
- 先编译的分类,优先调用load
initialize
- 先初始化父类
- 再初始化子类(可能最终调用的是父类的initialize方法)
objc4源码解读
-
-msg-arm64.s
- objc_msgSend
-
objc-runtime-new.mm
- class_getInstanceMethod
- lookUpImpOrNil
- lookUpImpOrForward
- _class_initialize
- callInitialize
- objc_msgSend(cls, SEL_initialize)
int main(int argc, const char * argv[]) {
@autoreleasepool {
[TGStudent alloc];
BOOL sutdentInitialized = NO;
BOOL personInitialized = NO;
if (!sutdentInitialized) {//自己有没有初始化
if (!personInitialized) {//父类有没有初始化
objc_msgSend([TGPerson class], @selector(initialize));
personInitialized = YES;
}
objc_msgSend([TGStudent class], @selector(initialize));
sutdentInitialized = YES;
}
}
return 0;
}
Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
网友评论