无论一个类设计的多么完美,在未来的需求演进中,都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢?一般而言,继承和组合是不错的选择。但是在Objective-C 2.0中,又提供了category这个语言特性,可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码的各个角落,从Apple官方的framework到各个开源框架,从功能繁复的大型APP到简单的应用,catagory无处不在。本文对category做了比较全面的整理,希望对读者有所裨益。  

  Objective-C中类别特性的作用如下:

  (1)可以将类的实现分散到多个不同文件或多个不同框架中(补充新的方法)。

  (2)可以创建私有方法的前向引用。

  (3)可以向对象添加非正式协议。

  Objective-C中类别特性的局限性如下:

  (1)类别只能想原类中添加新的方法,且只能添加而不能删除或修改原方法,不能向原类中添加新的属性。

  (2)类别向原类中添加的方法是全局有效的而且优先级最高,如果和原类的方法重名,那么会无条件覆盖掉原来的方法。 

 一、Category的底层实现

   Objective-C 通过 Runtime 运行时来实现动态语言这个特性,所有的类和对象,在 Runtime 中都是用结构体来表示的,Category 在 Runtime 中是用结构体 category_t 来表示的,下面是结构体 category_t 具体表示:

typedef 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;//添加的所有属性
} category_t;

  从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。

  我们将结合 runtime 的源码探究下 Category 的实现原理。打开 runtime 源码工程,在文件 objc-runtime-new.mm 中找到以下函数:

void _read_images(header_info **hList, uint32_t hCount)
{..._free_internal(resolvedFutureClasses);}// Discover categories. for (EACH_HEADER) {category_t **catlist =_getObjc2CategoryList(hi, &count);for (i = 0; i < count; i++) {category_t *cat = catlist[i];Class cls = remapClass(cat->cls);if (!cls) {// Category's target class is missing (probably weak-linked).// Disavow any knowledge of this category.catlist[i] = nil;if (PrintConnecting) {_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with ""missing weak-linked target class",cat->name, cat);}continue;}// Process this category. // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if // the class is realized. BOOL classExists = NO;if (cat->instanceMethods ||  cat->protocols||  cat->instanceProperties){addUnattachedCategoryForClass(cat, cls, hi);if (cls->isRealized()) {remethodizeClass(cls);classExists = YES;}if (PrintConnecting) {_objc_inform("CLASS: found category -%s(%s) %s",cls->nameForLogging(), cat->name,classExists ? "on existing class" : "");}}if (cat->classMethods  ||  cat->protocols/* ||  cat->classProperties */){addUnattachedCategoryForClass(cat, cls->ISA(), hi);if (cls->ISA()->isRealized()) {remethodizeClass(cls->ISA());}if (PrintConnecting) {_objc_inform("CLASS: found category +%s(%s)",cls->nameForLogging(), cat->name);}}}}// Category discovery MUST BE LAST to avoid potential races // when other threads call the new category code before // this thread finishes its fixups.// +load handled by prepare_load_methods()
...
}

  我们可以知道在这个函数中对 Category 做了如下处理:

  (1)将 Category 和它的主类(或元类)注册到哈希表中;

  (2)如果主类(或元类)已实现,那么重建它的方法列表;

  Category的实现原理:

  • 在编译时期,会将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t ,然后通过这些结构体生成一个结构体 category_t 。
  • 然后将结构体 category_t 保存下来
  • 在运行时期,Runtime 会拿到编译时期我们保存下来的结构体 category_t
  • 然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中
  • 将结构体 category_t 中的类方法列表、协议列表添加到主类的 metaClass 中

二、为何Category中的方法优先级高于原类中的方法?

category_t 中的方法列表是插入到主类的方法列表前面(类似利用链表中的 next 指针来进行插入),所以这里 Category 中实现的方法并不会真正的覆盖掉主类中的方法,只是将 Category 的方法插到方法列表的前面去了。运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找,这里就会出现覆盖方法的这种假象了。
// 这里大概就类似这样子插入
newproperties->next = cls->data()->properties;
cls->data()->properties = newproperties;,

三、为何Category中不能添加实例变量?

  通过结构体 category_t ,我们就可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。这里没有 objc_ivar_list 结构体,代表我们不可以在分类中添加实例变量。

因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这个就是 Category 中不能添加实例变量的根本原因。
参考:深入理解Objective-C:Category

转载于:https://www.cnblogs.com/xjf125/p/11179570.html

【Objective-C】探索Category底层的实质相关推荐

  1. 【iOS开发】——Category底层原理、Extension、关联对象

    [iOS开发]--Category底层原理.Extension.关联对象 Category是什么?它可以用来干什么? Category特点 Category的实质以及实现过程 Category结构体 ...

  2. Objective C 类别(Category)与类扩展(Extensions)

    一.类别(Category) 类别(Category)是一种可以为现有的类(包括类簇:NSString...,甚至源码无法获得的类)添加新方法的方式无需从现有的类继承子类.类别添加的新方法可以被子类继 ...

  3. app启动页数秒加载 代码_iOS 底层探索 - 应用加载

    一.前导知识 以下参考自 WWDC 2016 Optimizing App Startup Time : 1.1 Mach-O Mach-O is a bunch of file types for ...

  4. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  5. iOS 进阶之底层原理一OC对象原理alloc做了什么

    人狠话不多,直接上干货.这是第一篇,之后还会持续更新,当作自己学习的笔记,也同时分享给大家,希望帮助更多人. 首先,我们来思考,下面这段代码的输出是否相同.答案很明显,p1.p2.p3是指向相同的对象 ...

  6. 探索式软件测试—Exploratory Software Testing

    最近找到去年上半年看过一本关于测试方面书籍的总结笔记,一直放在我的个人U盘里,当时是用Xmind记录的,现在重新整理下分享给大家了! James A.Whittaker [美] 詹姆斯·惠特克(软件测 ...

  7. iOS之深入解析对象isa的底层原理

    对象本质 一.NSObject 本质 OC代码的底层实现实质是 C/C++代码 ,继而编译成汇编代码,最终变成机器语言. ① clang C/C++ 编译器 Clang 是⼀个 C 语⾔.C++.Ob ...

  8. ios底层核心模板结构 - list_array_tt entsize_list_tt

    探索objc底层原理过程中,经常会碰到一个模板 list_array_tt 你在类加载,方法查找,散列表,同步锁等等都会碰到这个结构,c++的模板功能十分强大,但代码看上去确实比较头疼 其实没那么糟了 ...

  9. 阿里二面:了解 MySQL 事务底层原理吗

    你好,我是坤哥,今天是国庆最后一天,不知大家是否玩得尽兴,我基本在家带娃了,累得半死,顺带肝了一篇文,来自读者曾经在阿里的面试题,希望对大家有帮助,另外也欢迎大家加我微信「geekoftaste」,一 ...

最新文章

  1. 面试鹅厂,我三面被虐的体无完肤。。。
  2. 1669 DINIC+二分
  3. Nginx 配置 SSL 证书步骤小记
  4. js 获取 本周、上周、本月、上月、本季度、上季度的开始结束日期
  5. s5pv210——中断系统相关介绍
  6. rpm安装mysql5.6.37_MySQL之—RPM方式安装MySQL5.6 代码实例详解
  7. 表头样式_1分钟学会制作Word两栏、三栏表头
  8. hdu 4417 树状数组查询区间不是1到n时需要转换,例[0,5]变成[1,6]
  9. 拓端tecdat|R语言生存分析可视化分析
  10. sqlmap重要参数详解+用法,解决入门难题
  11. EPSON爱普生系列打印机清洗更换墨盒方法
  12. 福昕阅读器中删除单个,多个注释,隐藏所有注释。
  13. cr2格式是什么意思?cr2格式用什么软件打开?cr2格式怎么转换成jpg
  14. 推荐一款使用快捷的免费文字识别OCR(图片转文字)在线服务
  15. Android应用在新浪微博授权提示:文件不存在 C8998 的解决方法
  16. LeetCode 2171. 拿出最少数目的魔法豆
  17. 关于LSB图片隐写的解法
  18. pin ——pin tool代码注释 各pin tool的用途
  19. (个人学习笔记)利用ensight进行EDEM耦合FLUENT后处理
  20. socket是一个编程接口

热门文章

  1. php date( ymd_PHP DATE()
  2. python水印倾斜_python中图像特定位置的水印算法
  3. 写代码用什么笔记本_1—2千预算,编程、写代码、办公、PS修图笔记本推荐/选购指南...
  4. Dataframe 新增一列, apply 通用方法
  5. React 16 Jest ES6 Class Mocks(使用ES6语法类的模拟) 实例三、四
  6. 空间复杂度,实现从excel导出到txt文件中的java代码自动构建,逻辑条件不同实现则不同...
  7. JVM可生成的最大Thread数量探索
  8. iOS调试技巧-断点调试
  9. Centos7 把php5.4升级到php5.6
  10. pycharm error:no module named caffe