美文网首页
Category底层原理

Category底层原理

作者: 分流替躺欧阳克 | 来源:发表于2019-05-20 00:22 被阅读0次

    一:底层编译原理

    先编译分类文件,会创建一个_Category_t结构体,多少个分类就会生成多少个结构体(属性,协议,协议)都在分类里。

    运行时,再把各个分类的结构体添加合并到原本类中。方法,属性和协议添加到原类的顺序是,先把原有的方法属性协议的数组指针在数组中的位置后移(后移位置取决于多少个分类),再把各分类的方法,属性,协议加入原数组中,位置处于原原类方法列表指针的前面,这里证明了,当对象调用分类和类中都有的同名方法时,先调用分类的方法(后编译先调用)。

    runtime源码查看与证明,以下步骤是在源码中一步步点的代码

    1:void _objc_init(void)这个是runtime代码入口。

    苹果动态链接器注册链接方法的代码2:_dyld_objc_notify_register(&map_images, load_images, unmap_image);

    这里的images是镜像的意思

    点进_map_images查看

    3:map_images_nolock点进去。

    4:_read_images 点进去 

     5:remethodizeClass重新方法化,点进去

    6:attachCategories(添加分类方法属性协议)

    7:rw->methods.attachLists(mlists, mcount);把方法添加进数组

    8:把原来的方法列表向后移,移n个分类的位置,移动数据的长度是旧方法的数据长度的和

      memmove(array()->lists + addedCount, array()->lists,oldCount* sizeof(array()->lists[0]));

     把所有分类的方法列表放在数组的前面

      memcpy(array()->lists, addedLists, addedCount* sizeof(array()->lists[0]));

     由8里以上两个操作可以证明开发中类方法或者实例方法调用是后编译先调用。

    二:类的Load方法底层原理

    是运行时,直接查找到类方法列表和分类的方法列表里的函数指针直接调用load,所以会出现父类,分类同名方法都调用的情况。与平常我们调用方法-objc_msgSend(”class”,”methodName”);不同,消息发送是通过对象的isa指针去寻找类或元类,然后在类或元类的方法列表里查找方法由于编译时的原因(上一段),会优先执行分类中的后编译方法。

    1:void _objc_init(void)入口

    2:_dyld_objc_notify_register(&map_images, load_images, unmap_image);

    点进load_images查看

    3: call_load_methods(); 调用方法,具体调用的代码是(*load_method)(cls, SEL_load);

    4: prepare_load_methods里的代码决定调用顺序 

    5: _getObjc2NonlazyClassList(获取类里不是懒加载的方法,这里面的顺序和编译顺序有关)

     _getObjc2NonlazyCategoryList(获取分类里不是懒加载的方法)

    6: schedule_class_load(规划类的加载,递归方法)先调用父类的load再调用自己的load

    7:add_category_to_loadable_list(cat);(按什么顺序添加分类的方法在此)

     loadable_categories[loadable_categories_used].cat = cat;

     loadable_categories[loadable_categories_used].method = method;

     loadable_categories_used++;这三句证明了把分类方法添加进数组的顺序,一个个往后添加

    三:load方法和initialze的区别

    一)调用方式

    1:load是根据函数地址调用 ;2:initialize是通过objc_msgSend调用

    二)调用时刻

    1:load是runtime加载类,分类的时候调用(只会调用一次)

    2:initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize可能会被子类调用多次。当子类没有实现initialize的时候)

    三)load、initialize的调用顺序

    1:load 

    1)先调用类的load(a先编译的类,优先调用load;b调用子类的load之前,会先调用父类的load)

    2)再调用分类的load 先编译的分类,优先调用load

    2:initialize

    1)先初始化父类 2)再初始化子类(可能最终调用的是父类的initialize方法);

    源码部分

    1:class_getInstanceMethod->lookUpImpOrNil->lookUpImpOrForward

    if(initialize  &&  !cls->isInitialized()) {

            runtimeLock.unlock();

            _class_initialize (_class_getNonMetaClass(cls, inst));

            runtimeLock.lock();

        }源码->如果自己需要初始化并且没有初始化,就调用_class_initialize初始化

    initialize函数里:

        supercls = cls->superclass;

        if(supercls  &&  !supercls->isInitialized()) {

            _class_initialize(supercls);

        }如果父类存在并且没有初始化,则再次调用父类initialize,递归函数,一直到基类。

    然后由顶层往下调用callInitialize(cls);

    这个函数里代码是((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

    给这个类发送消息调用initialize方法

    四、关联对象

    objc_setAssociatedObject(id_Nonnull object,const void*_Nonnull key,  id_Nullable value, objc_AssociationPolicy policy)

    objc_AssociationPolicy policy:关联策略

    OBJC_ASSOCIATION_ASSIGN = 0,                                     assign;

    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,             strong,nonatomic

    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,              copy,nonatomic

    OBJC_ASSOCIATION_RETAIN = 01401,                          strong atomic

    OBJC_ASSOCIATION_COPY = 01403                            copy,atomic

    1、分类添加属性

    在分类h文件里,声明属性

    @property (nonatomic,copy)NSString *name;

    在m文件里写set和get方法

    - (void)setName:(NSString*)name{

    objc_setAssociatedObject(self, MJNameKey, name,OBJC_ASSOCIATION_COPY_NONATOMIC);

    }

    - (NSString*)name{

      return objc_getAssociatedObject(self, MJNameKey);

    }

    其中也可以把,方法的隐式参数当key传进去绑定对象 - (NSString*)name:(id)self _cmd:(SEL)_cmd  get方法中self和—_cmd是隐式参数.

    2、关联对象底层原理

    实现关联对象技术的核心对象有

    1)AssociationsManager //管理所有关联的属性的类,全局的

    2)AssciationsHashMap  //存着所有关联属性的键值对,他存在于manager里

    3)ObjectAssociationMap //一个对象的所有关联属性键值对,根据传进来的self即object(我们调用runtime方法所传)从AssciationsHashMap取得,这个提供的iterator

    4)ObjcAssociation//每个关联属性的键值队----值和策略。根据传进来的key(我们调用runtime方法所传)从ObjectAssociationMap取得

    ****************关联对象属性并不是存储在被关联对象本身内存中,而是存在全局的统一的一个AssciationsManager中的AssociationsHasMap中**********************************************

    源码路径:

    void objc_setAssociatedObject ->_object_set_associative_reference 在这个方法里面有上面四个核心对象。

    这四个对象关系:

    AssociationsManager 里存着AssciationsHashMap,AssciationsHashMap里存着我们传进去的key:disguised_ptr_t和value:ObjectAssociationMap;ObjectAssociationMap里面存着key: void * 和value:ObjcAssociation,ObjcAssociation里存着我们传的策略uintptr_t _policy 和值id value。

    实现原理源代码

    //获取全局关联属性管理器manager

    AssociationsHashMap &associations(manager.associations());

    //获取Manager内的AssociationsHashMap(存着所有对象的关联属性map)

    AssociationsHashMap &associations(manager.associations());

    根据传进来的object生成一个key

    disguised_ptr_t disguised_object =DISGUISE(object);

    //找到AssociationsHashMap里与disguised_object 相关的一个遍历器,即与我们传进去的object相关的一个遍历器,这个遍厉器里的某项成员就是我们传进去这个类的所有关联属性列表

    AssociationsHashMap::iterator i = associations.find(disguised_object);

    从这个遍历器中找到ObjectAssociationMap

    ObjectAssociationMap *refs = i->second;

    根据key从ObjectAssociationMap对应的单个关联属性信息的对象

    ObjectAssociationMap::iterator j = refs->find(key);

    //从这个iterator里面拿到其中的second便是我们设置的ObjcAssociation(存着这个关联属性的策略和值)

    old_association = j->second;

    把新的值覆盖进去

    (*refs)[key] = ObjcAssociation(policy, new_value);

    源码里associations.end()//判断整个项目里有没有添加过关联属性,refs->end()判断这个对象是否头一次添加属性。

    结构如下图:

    相关文章

      网友评论

          本文标题:Category底层原理

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