美文网首页
Category 原理源码分析 load、initialize

Category 原理源码分析 load、initialize

作者: 咖啡豆8888 | 来源:发表于2018-05-11 11:38 被阅读279次

    本节将解释下一下问题:

    1.Category的实现原理?

    2.Category跟Extension的区别?

    3.Category有load方法么?父类子类Category之间调用顺序是什么?

    4.load、initialize方法的区别,调用顺序是什么?以及出现继承他们的调用顺序是什么?

    5.Categoryn能添加成员变量么?如果可以,如何添加?

    1、Category实现原理

    创建一个Category

    首先可以将OC转换成C语言编译文件查看一下,当前目录下(注意方法要实现)

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc NSObject+Test.m -o NSObject+Test.cpp

    Category转换成C语言编译

    原理: 从编译文件我们可以看出,Category编译之后底层会转换成一个_category_t的结构体,内部包含着Category的对象方法,类方法,属性,协议信息。在运行时期,会经过runtime合并到类信息中(类方法放置元类对象中)。 PS: 类方法存储方式,可以看我之前的文章Class本质

    下面我们就来搞下:runtime是如何将Category中的信息,合并到对应类信息中呢?

    runtime源码地址

    下载runtime的源码:objc-os.mm这个文件是入口文件,内部void_objc_init(void)方法是初始化方法

    runtime初始化

    内部调用map_images函数 (读取镜像/模块),进入函数,在进入map_images_nolock函数实现,再进入_read_images函数,(从函数命也能够看出来 到了读取模块的地方了)

    开始读取Category

    在进入重新布局class方法,内部调用了attachCategories方法,追加类别

    开始追加Category 解析Category方法内部实现

    然后进入attachLists函数调用。

    Category追加到当前类

    2.Category跟Extension的区别?

    class extension 是在编译前就储存在类信息中了,而Category是在运行时才会被载入类信息中的

    Category是在运行时才会被合并到类信息中去的。

    3.Category有load方法么?父类子类Category之间调用顺序是什么?

    load方法特点:是在runtime装载类/Category的时候调用,不管你是否用的该类、Category都会调用load方法,runtime是采用的是直接找到对应类的load函数地址调用,而并非是用objc_msgsend()来调用。

    调用顺序是先调用类的load方法,再调用Category的load方法,如果类中存在继承关系,先调用super class的load方法在调用子类的load方法。

    源码来看:(objc-os.mm-->load_images-->call_load_methods())

    load方法加载顺序 真正调用class的load方法

    从图中可以看出,先进行循环class的load方法执行,在进行Category方法的执行。

    那么继承关系又是怎么调用的呢?同样我们也可以通过源码来分析。

    源码路径:(objc-os.mm-->load_images-->prepare_load_methods()-->schedule_class_load)

    预加载class列表

    可以看到apple用递归调用,然后每次传入自身父类,生成的数组是@[@(super class),@(son class)]这种类型,而且我们从上上图 我们也可以看出,循环数组是从i=0开始的,也就意味着父类的调用顺序会比子类要早。

    总结:load方法的调用顺序是:  

    1.先加载class中的load方法。

           > 存在继承关系的类,会先进行加载父类,在加载子类,比如person.h、 son.h(son继承person)会先加载person的load方法

           >不存在继承关系的类,会根据编译顺序来加载load方法。比如dog.h 、cat.h两个类的编译顺序谁在先先调用。(编译顺序工程>Tagets>build phases >complie sources  可以看到编译顺序)

    2.再进行加载Category中的load方法

             >会根据编译顺序来加载load方法。比如NSObject +dog.h 、NSObject +cat.h两个分类的编译顺序谁在先先调用。(编译顺序工程>Tagets>build phases >complie sources  可以看到编译顺序)

    4.load、initialize方法的区别,调用顺序是什么?以及出现继承他们的调用顺序是什么?

    initialze 方法是在对象第一次接受消息的时候调用,采用的是objc_msgSend()消息转发机制,

    而且每个类只调用一次,父类优先调用,父类存在列表,并且实现的该方法,根据消息转发机制,类别调用顺序优于类对象,所以会先调用父类类别。

    比如现在有两个类,person 、student  继承关系,还有两个类别,person+eat 、student+eat、四个文件的都实现了initialize方法,当发生[student alloc]的时候,也就是对象第一次接受纤细的时候,会先从父类中寻找,superclass的方法列表中,分类优于对象,所以执行的是person+eat 中的initialize方法,进而在调用student中的initialize方法,相同道理,会调用student+eat中的initialze方法。

    load、initialze区别在于实现方式不同,load是直接找到load函数地址来调用,而initialze是采用消息转发机制来进行调用。因为initialze是通过objc_msgSend进行调用的,所以会有以下特点:如果子类没有实现,会调用父类的initialze,所以父类的initialze可能被调用多次,如果分类实现了initialze,就会覆盖类本身的initialze调用。

    总结:load、initialze的区别总结?

    1》调用方式:load是根据函数地址直接到调用。initialze是通过objc_msgSend()调用。

    2》调用时刻:load是runtime加载类,Category的时候调用,只会调用一次,而initialze是在类第一次接受到消息的时候调用,每个类只会initialze一次,(父类的initialze可能会被多次调用)

    3》load调用顺序:

    a>  load 先调用类的load,先编译的类优先调用load方法,调用子类的load之前,会调用父类的load

    b> 再调用分类的load

    4》initialze调用顺序:

    a> 先初始化父类

    b> 再初始化子类,可能最终调用的父类的initialze方法。

    5.Categoryn能添加成员变量么?如果可以,如何添加?

    不能直接添加成员变量,因为Category中添加属性不会自动生成 _成员变量 、setter、getter方法的实现,只会生成setter、getter方法的声明。但是可以用运行时间接关联对象,完成添加属性。

    运用runtime  关联对象的api,可以达到属性关联的目的。这里并不是说类别就可以添加属性了,而是通过runtime达到跟添加属性一样的目的而已。

    关联属性

    objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)

    三个参数,第一个是当前对象,第二个是一个常亮指针。这里面用@selector(age) 返回的是一个唯一的age的函数指针地址,可以作为常亮。

    第三个 

    第三个参数

    根据你参数定义的关键字选择对应的枚举值即可

    相关文章

      网友评论

          本文标题:Category 原理源码分析 load、initialize

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