美文网首页
Category的实现原理

Category的实现原理

作者: buding_ | 来源:发表于2024-03-26 17:54 被阅读0次
Category的实现原理
  1. 在程序编译过程后的底层结构是struct _category_t,里面包含着分类的对象方法、类方法、属性和协议信息
  2. 在程序运行过程中,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中的对应信息也会被移除

相关文章

网友评论

      本文标题:Category的实现原理

      本文链接:https://www.haomeiwen.com/subject/elultjtx.html