美文网首页
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