Category底层原理

一:底层编译原理

先编译分类文件,会创建一个_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()判断这个对象是否头一次添加属性。

结构如下图:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容