深入理解Objective-C:Category

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)
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容