Category的实现原理
- 在程序编译过程后的底层结构是struct _category_t,里面包含着分类的对象方法、类方法、属性和协议信息
- 在程序运行过程中,runtime会将分类的数据合并到类信息中(包括类对象、元类对象);
通过memmove,将原本的方法列表在往数组的后方移动;
通过memcpy,将分类的方法列表添加到数组前面;
那么:
- 分类的方法列表会被添加到原类方法列表之前, 故调取方法时会优先调用分类的方法
- 分类与分类的调用优先级是,最后编译的分类会被添加在前面,故后编译的分类方法会被优先调用 (编译顺序即为 Build Phases -> Compile sources中的顺序,可手动调整)
编译后的分类会被转换为_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;
};
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat,
};
Category的使用场合
- 将一个类进行拆分,或者扩展时使用;
- 在遵循开闭原则进行类的设计时使用;
Category和Extension的本质区别是什么?
Extension在编译的时候,它的数据就会被添加到类信息中;
Category则是在程序运行时,才会将它的信息合并到类信息中;
load方法什么时候调用?
- Category中有load方法;
- load方法会在runtime加载类、分类时调用;
- 每个类、分类的load方法,在程序运行过程中只调用一次;
load、initialize方法的区别是什么?它们在Category中的调用顺序?以及出现继承时,它们的调用顺序?
-
调用时机的区别:
load方法会在runtime加载类、分类时调用;
initialize方法会在类第一次接收到消息时调用; -
调用方式的区别
load方法通过内存地址进行调用
initialize方法通过消息发送机制进行调用 -
调用顺序的区别:
load方法调用顺序是:
1 先调用类的load方法
a)按照编译顺序调用,先编译先调用
b)调用子类的load之前会先调用父类的load方法
2 再调用分类的load方法
a)按照编译顺序调用,先编译先调用initialize方法的调用顺序是:
1 先调用父类的initialize,再调用子类的initialize
相关源码:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
lockdebug::assert_locked(&loadMethodLock);
// 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;
}
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, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
通过源码可知,加载类时会先调用类的load方法,再调用分类的load方法;
且调用是通过地址直接调用,没有触发msg_send(消息发送机制)的逻辑,故类的load方法不会被其分类覆盖;
load、initialize均为提供给开发者重写的,当需要在类加载到内存时处理一些事情则在load中做,当需要类第一次使用时处理一些事情则在initialize中调用
Category能否添加成员变量?
Category不可以直接添加成员变量,因为Category不允许声明实例变量,即使可以定义属性,但无法为属性添加对应的实例变量和getter、setter方法;本质上看也是因为分类的结构导致;
但是Category中可以间接添加成员变量,一般方式有使用runtime的关联对象,或使用全局属性进行保存
@interface Person (Test)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end
#import <objc/runtime.h>
@implementation Person (Test)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
// 隐式参数,每个oc方法都会有2个隐藏参数:(id)self 和 (SEL)_cmd
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(int)weight {
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight {
// _cmd == @selector(weight)
return [objc_getAssociatedObject(self, _cmd) intValue];
}
@end
objc_setAssociatedObject方法中的key要求传入值是const void * _Nonnull key,
也就是传入指针即可;
那么可以传入:
static const void *NameKey = &NameKey;
或者 int *p、int p;的&p;
或者 static const char NameKey;的&NameKey
或者 "name"
或者 @selector(name)
objc_setAssociatedObject
objc_setAssociatedObject不会影响原本class的结构、也不会影响instance在内存中的结构;
实现关联对象技术的核心对象有:
class AssociationsManager {
static AssociationsHashMap *_map
}
class AssociationsHashMap: public unordered_map<disguised_ptr_t, ObjectAssociationMap>
class ObjectAssociationMap: public std::map<void *, ObjAssociation>
class ObjAssociation {
uintptr_t _policy;
id _value;
}
objc_setAssociatedObject(
self, -> AssociationsManager下AssociationsHashMap中的 disguised_ptr_t
@selector(weight), -> ObjectAssociationMap中的void *
@(weight), -> ObjAssociation中的 _value
OBJC_ASSOCIATION_RETAIN_NONATOMIC); -> ObjAssociation中的 _policy
使用关联对象添加属性,当对象被销毁后,添加到AssociationsManager->_map中的对应信息也会被移除
网友评论