iOS Category
文章目录前言前言由几个问题引入Category吧Category的使用场合是什么Category的实现原理Category和Extension的区别是什么Category中有load方法吗? load方法是什么时候调用的? load方法能继承吗?load、initialize方法的区别是什么? 它们在Category中调用的顺序? 以及出现继承时它们之间的调用过程Category能否添加成员变量?
文章目录
前言
由几个问题引入Category吧
- Category的使用场合是什么
- Category的实现原理
- Category和Extension的区别是什么
- Category中有load方法吗? load方法是什么时候调用的? load方法能继承吗?
- load、initialize方法的区别是什么? 它们在Category中调用的顺序? 以及出现继承时它们之间的调用过程
- Category能否添加成员变量? 如果可以 如何添加
Category的底层原理
结论
先说结论
Category编译之后的底层结构是struct_category_t
里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候 runtime会将Category的数据 合并到类信息中(类对象、元类对象中)
分析
这里有三个类 下面两个是WXPerson
的分类
分类的方法最终会归到类里面
也就是WXPerson+Test
和WXPerson+Eat
里的方法最终会加入WXPerson
的方法列表中 不管是实例方法还是类方法 当然类方法会加入WXPerson
元类的方法列表中
通过runtime动态将分类的实例方法合并到类中方法列表中
将类方法合并到元类的方法列表中
通过命令行将其中一个分类转为cpp代码
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; //属性
};
底层结构我们找到是这样的
当程序编译的时候 分类中所有的数据都变成了这样的结构体对象
暂时分类和类的数据还是分开的
我还在cpp代码里找到了这样一个函数
很明显是用于上面_category_t
赋值的
static struct _category_t _OBJC_$_CATEGORY_WXPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"WXPerson",
0, // &OBJC_CLASS_$_WXPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_WXPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_WXPerson_$_Test,
0,
0,
};
这两个参数很明显是实例方法的方法列表跟类方法的方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_WXPerson_$_Test,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_WXPerson_$_Test,
在cpp文件中往上一点就会看到
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_WXPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_WXPerson_Test_test}}
};
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_WXPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_WXPerson_Test_test2}}
};
这样的代码段
很清晰
把WXPerson+Test
分类中的test
实例方法跟test2
类方法加入了方法列表
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_WXPerson_$_Test1 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCoding,
&_OBJC_PROTOCOL_NSCopying
};
这个是协议列表 我实现了NSCoding协议跟NSCopying协议
还有一个问题
当我们实现了协议方法的时候 这时候传入的method_list_t
结构体会有两个
会分开类方法实例方法 跟 协议方法
Category的load和initialize方法
+load方法会在runtime加载类、分类时调用
不管有没有用到这几个类 都会调用+load方法
我们知道调用方法的顺序是根据编译时候的顺序
谁在后面编译 调用类的不管是实例方法或者是类方法的时候
都会先调用它的
所以一般来说调用方法的时候 都会先调用到分类的方法
可是load方法可以看到 是根据先调用类 再调用分类的顺序进行的
这是为什么呢
load源码分析
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
// 准备Class list和Category list
prepare_load_methods((const headerType *)mh);
}
// 调用已经准备好的Class list和Category list
call_load_methods();
}
这里可以看到 最后调用的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();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
这里看的清清楚楚
先调用了类的load方法 之后调用分类的load方法
调用类的load方法的源码
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_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);
}
直接找到load方法的方法地址 直接调用
每个类、分类的+load方法 在程序运行过程中只调用一次
并且load方法可以继承 但是一般情况下不会主动去调用load方法 都是让系统自己调用
initialize
+initialize方法会在类第一次接受到消息的时候调用
它也是通过消息机制来发送的 根据我们之前的知识知道
如果类和分类方法同名 优先调用分类的方法 而且是后编译的先调用
并且如果出现继承的时候 会先调用父类的initialize方法 再调用子类的
而且先初始化父类 再初始化子类 每个类只会被初始化一次
这里Student是Person的子类 在Student中也写了initialize方法
直接写[WXStudent alloc];
如果子类没有实现initialize方法 就会调用父类的initialize方法 所以父类的方法有可能被调用多次
如果分类实现了initialize方法 就会覆盖类本身的initialize调用
load跟initialize方法的区别是什么
load是根据函数地址直接调用 initialize是通过objc_msgSend调用
load是在runtime加载类 分类的时候调用 所以只会调用一次
而initialize是在第一次接受到消息的时候调用 每个类只会initialize一次 但是父类的initialize可能被执行多次
分类能添加属性吗
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;
//属性列表
};
category中也可以添加属性 只不过@property只会生成setter和getter的声明 不会生成setter和getter的实现以及成员变量 点语法是可以写 只不过在运行时调用到这个方法时候会报方法找不到的错误
Category为什么不能添加成员变量
Objective-C类是由Class类型来表示的 它实际上是一个指向objc_class结构体的指针
typedef struct objc_class *Class;
定义如下
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class // 父类
const char *name // 类名
long version // 类的版本信息,默认为0
long info // 类信息,供运行期使用的一些位标识
long instance_size // 该类的实例变量大小
struct objc_ivar_list *ivars // 该类的成员变量链表
struct objc_method_list **methodLists // 方法定义的链表
struct objc_cache *cache // 方法缓存
struct objc_protocol_list *protocols // 协议链表
#endif
};
objc_class结构体的大小是固定的 不可能往这个结构体添加数据 只能修改
所以ivars指向的是一个固定区域 只能修改成员变量值 不能增加成员变量个数
Category和Extension的区别是什么
Extension在编译的时候 它的数据就已经包含在类信息中
Category是在运行时 才会将数据合并到类信息中
Extension可以添加实例变量 但是Category不可以
Extension和Category都可以添加属性 但是Category的属性不能生成成员变量和setter方法getter方法的实现
更多推荐
所有评论(0)