美文网首页
深入理解Objective-C:Category

深入理解Objective-C:Category

作者: sajiner | 来源:发表于2018-09-29 13:28 被阅读12次

    category简介

    • Objective-C 2.0之后添加的语言特性

    • 使用场景
      1> 为存在的类添加方法
      2> 把类的实现分开在不同的文件中。好处:a)减少单个文件的体积, b)把不同的功能分开,c)可以多人开发同一个类,d)按需加载想要的分类
      3> 声明私有方法
      4> 模拟多继承

    • https://opensource.apple.com/tarballs/objc4/源码的objc-runtime-new.h中的定义如下:

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    即category的底层其实就是一个结构体,里边有类名、类、实例方法、类方法、协议信息、属性信息

    • Category和Class Extension的区别
      1> Class Extension属于类的一部分,在编译的时候,其信息就已经包含在了类信息中了
      2> Category是在Runtime时,才将信息合并到类中

    Category的加载处理过程

    源码层面分析

    • 先找到oc运行的入口方法_objc_init(objc_os.mm文件中)
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    
    • category被附加到类上是在map_images的时候发生的,在objc_runtime_new.mm文件中找到map_images方法
    void
    map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    {
        rwlock_writer_t lock(runtimeLock);
        return map_images_nolock(count, paths, mhdrs);
    }
    
    • 接下来的跟踪路径:
      map_images_nolock -> _read_images -> remethodizeClass -> attachCategories -> attachLists -> realloc -> memmove -> memcpy

    • 总结
      1> Category是通过runtime进行加载的
      2> 先编译的分类会存在在数组的后边(即,调用方法的时候,后编译的分类中的方法会先调用)
      3> 合并后的Category方法、属性、协议会被放入数组中,插入到类原来数据的前面
      4> 类与分类中相同的方法不会被相互覆盖,只是调用的时候,会调用分类中的该方法

    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    - (void)test1;
    @end
    
    
    #import "Person.h"
    @interface Person (Test1) 
    - (void)test1;
    @end
    
    /// 在main.m文件中
    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Person+Test1.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *person = [[Person alloc] init];
            [person test1]; /// 此句调用的是 Person+Test1 文件中的 test1 方法
        }
        return 0;
    }
    

    Category中的+load方法

    • +load方法会在runtime加载类、分类的时候调用
    • 每个类、分类的+load方法在运行中只调用一次
    • 调用顺序:
      1> 先调用类的+load方法。按照先编译先调用的顺序,调用子类的+load方法之前会先调用父类的+load方法
      2> 再调用分类的+load方法。调用顺序按照编译的先后顺序
    源码分析+load方法的加载过程
    • 类的加载
      1> objc-os.mm 文件的_objc_init -> load_images -> prepare_load_methods -> call_load_methods -> call_class_loads -> (*load_method)(cls, SEL_load)
      2> 其中 prepare_load_methods 方法中的schedule_class_load,会对拥有继承关系的类进行排序:父类优先于子类先加载
    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        assert(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
    
        // Ensure superclass-first ordering
        schedule_class_load(cls->superclass);
    
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    
    • 分类的加载
      objc-os.mm 文件的_objc_init -> load_images -> prepare_load_methods -> call_load_methods -> call_category_loads -> (*load_method)(cls, SEL_load)

    • 特别注意:不管是类还是分类,+load方法是根据方法地址直接调用,并不是通过objc_msgSend函数调用

    Category中的+initinitialize方法

    • 在类第一次接到消息时调用

    • 每个类只会initialize一次,父类的+initialize方法可能会调用多次

    • 调用顺序:
      先调用父类再调用子类

    • initialize的加载过程
      objc-runtime-new.mm文件中 class_getInstanceMethod -> lookUpImpOrNil -> lookUpImpOrForward -> _class_initialize 中的部分代码如下 -> callInitialize -> objc_msgSend

    void _class_initialize(Class cls)
    {
        Class supercls;
        bool reallyInitialize = NO;
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
     }
    

    +load和+initialize的区别

    • 调用方式
      1> load是根据函数地址直接调用
      2> initialize是通过objc_msgSend调用

    • 调用时刻
      1> load是runtime加载类、分类的时候调用(只会调用1次)
      2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

    • 调用顺序
      load
      1> 先调用类的load
      a) 先编译的类,优先调用load
      b) 调用子类的load之前,会先调用父类的load
      2> 再调用分类的load
      a) 先编译的分类,优先调用load
      initialize
      1> 先初始化父类
      2> 再初始化子类(可能最终调用的是父类的initialize方法)

    关联对象

    • 原理图


      关联对象原理图.png
    • 相关API
      1> 添加关联对象

    void objc_setAssociatedObject(id object, const void * key,
                                    id value, objc_AssociationPolicy policy)
    

    2> 获得关联对象

    id objc_getAssociatedObject(id object, const void * key)
    

    1> 移除所有的关联对象

    void objc_removeAssociatedObjects(id object)
    

    相关文章

      网友评论

          本文标题:深入理解Objective-C:Category

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