objc category的秘密

category的真面目

objc所有类和对象都是c结构体,category当然也一样,下面是runtime中category的结构:

1
2
3
4
5
6
7
8
struct _category_t {const char *name; // 1struct _class_t *cls; // 2 const struct _method_list_t *instance_methods; // 3 const struct _method_list_t *class_methods; // 4 const struct _protocol_list_t *protocols; // 5 const struct _prop_list_t *properties; // 6 }; 
  1. name注意,并不是category小括号里写的名字,而是类的名字
  2. cls要扩展的类对象,编译期间这个值是不会有的,在app被runtime加载时才会根据name对应到类对象
  3. instance_methods这个category所有的-方法
  4. class_methods这个category所有的+方法
  5. protocols这个category实现的protocol,比较不常用在category里面实现协议,但是确实支持的
  6. properties这个category所有的property,这也是category里面可以定义属性的原因,不过这个property不会@synthesize实例变量,一般有需求添加实例变量属性时会采用objc_setAssociatedObjectobjc_getAssociatedObject方法绑定方法绑定,不过这种方法生成的与一个普通的实例变量完全是两码事。

编译器,你对category干了什么?

举个栗子看,定义下面一个类和它的category,实现忽略,保存为sark.hsark.m

1
2
3
4
5
6
7
@interface Sark : NSObject
- (void)speak;
@end @interface Sark (GayExtention) - (void)burst; @end 

请出clang的重写命令:

1
$ clang -rewrite-objc sark.m

同级目录下会生成sark.cpp,这就是objc代码重写成c++(基本就是c)的实现。
打开生成的文件,发现茫茫多,排除include进来的header,自己的代码都在文件尾部了,看看上面的category被编译器搞成什么样子了:

1
2
3
4
5
6
7
8
9
static struct _category_t _OBJC_$_CATEGORY_Sark_$_GayExtention __attribute__ ((used, section ("__DATA,__objc_const"))) =
{"Sark",0, // &OBJC_CLASS_$_Sark, (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Sark_$_GayExtention, 0, 0, 0, }; 

先注意这个category的名字_OBJC_$_CATEGORY_Sark_$_GayExtention,这是一个按规则生成的符号了,中间的Sark是类名,后面的GayExtention是类别的名字,这也就是为什么同一个类的category名不能冲突

对应看上面_category_t的定义,因为category里面只添加了一个- burst方法,所以只有实例方法那一项被填充了值_OBJC_$_CATEGORY_INSTANCE_METHODS_Sark_$_GayExtention

其中_I_Sark_GayExtention_burst符号就代表了category里面的- burst方法,同样遵循了一定的命名规范,里面的I表示实例方法

最后,这个类的category们生成了一个数组,存在了__DATA段下的__objc_catlistsection里

1
2
3
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {&_OBJC_$_CATEGORY_Sark_$_GayExtention,
};

至此编译器的任务完成了。

runtime,我的category哪儿去了?

我们知道,category动态扩展了原来类的方法,在调用者看来好像原来类本来就有这些方法似的,有两个事实:

  1. 不论有没有import category 的.h,都可以成功调用category的方法,都影响不到category的加载流程,import只是帮助了编译检查和链接过程
  2. runtime加载完成后,category的原始信息类结构里将不会存在

这需要探究下runtime对category的加载过程,这里就简单说一下

  1. objc runtime的加载入口是一个叫_objc_init的方法,在library加载前由libSystem dyld调用,进行初始化操作
  2. 调用map_images方法将文件中的imagemap到内存
  3. 调用_read_images方法初始化map后的image,这里面干了很多的事情,像load所有的类、协议和category,著名的+ load方法就是这一步调用的
  4. 仔细看category的初始化,循环调用了_getObjc2CategoryList方法,这个方法拿出来看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
#define GETSECT(name, type, sectname)                                   \type *name(const header_info *hi, size_t *outCount) \ { \ unsigned long byteCount = 0; \ type *data = (type *) \ getsectiondata(hi->mhdr, SEG_DATA, sectname, &byteCount); \ *outCount = byteCount / sizeof(type); \ return data; \ } // ... // GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist"); 

眼熟的__objc_catlist,就是上面category存放的数据段了,可以串连起来了

在调用完_getObjc2CategoryList后,runtime终于开始了category的处理,简化的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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 (isRealized(cls)) { remethodizeClass(cls); classExists = YES; } } if (cat->classMethods || cat->protocols ) { addUnattachedCategoryForClass(cat, cls->isa, hi); if (isRealized(cls->isa)) { remethodizeClass(cls->isa); } } 

首先分成两拨,一拨是实例对象相关的调用addUnattachedCategoryForClass,一拨是类对象相关的调用addUnattachedCategoryForClass,然后会调到attachCategoryMethods方法,这个方法把一个类所有的category_list的所有方法取出来组成一个method_list_t **,注意,这里是倒序添加的,也就是说,新生成的category的方法会先于旧的category的方法插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void
attachCategoryMethods(class_t *cls, category_list *cats,BOOL *inoutVtablesAffected)
{if (!cats) return;if (PrintReplacedMethods) printReplacements(cls, cats); BOOL isMeta = isMetaClass(cls); method_list_t **mlists = (method_list_t **) _malloc_internal(cats->count * sizeof(*mlists)); // Count backwards through cats to get newest categories first int mcount = 0; int i = cats->count; BOOL fromBundle = NO; while (i--) { method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= cats->list[i].fromBundle; } } attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected); _free_internal(mlists); } 

生成了所有method的list之后,调用attachMethodLists将所有方法前序添加进类的方法的数组中,也就是说,如果原来类的方法是a,b,c,类别的方法是1,2,3,那么插入之后的方法将会是1,2,3,a,b,c,也就是说,原来类的方法被category的方法覆盖了,但被覆盖的方法确实还在那里。

转载于:https://www.cnblogs.com/wfwenchao/articles/4260565.html

iOS category解析相关推荐

  1. [iOS] dom解析xml数据,拿到lt;gt;里面的值

    [iOS] dom解析xml数据,拿到<>里面的值 <response result="success" timestamp="1338890206&q ...

  2. ios category类别的使用

    ios category类别的使用 ios category类别的使用 Objective-C提供了一个非常灵活的类(Class)扩展机制-类别(Category).类别用于对一个已经存在的类添加方法 ...

  3. 苹果手机充值显示服务器异常,梦幻西游手游充值出现异常的iOS系统解析

    梦幻西游手游充值出现异常的iOS系统解析,目前iOS系统充值出现异常,如果您在充值时遇到 "iOS目前无法处理您的购买" 的问题,这个情况主要是由于苹果服务器存在问题导致的,目前会 ...

  4. iOS Category 添加属性实现原理 - 关联对象

    iOS Category 添加属性实现原理 - 关联对象 RunTime为Category动态关联对象 使用RunTime给系统的类添加属性,首先需要了解对象与属性的关系.对象一开始初始化的时候其属性 ...

  5. iOS之解析ips文件

    iOS之解析ips文件 导出ips文件 dSYM配合 symbolicatecrash配合 执行解析 导出ips文件 途径一: 手机连接Xcode查看和导出 Xcode -> Window -& ...

  6. [iOS]-Category、Extension和关联对象

    目录: 参考的博客: 前言 一.Category分类 Extension扩展 Category的实质 Category结构体 将分类转成C++看起 对象方法列表结构体 类方法列表结构体 协议列表结构体 ...

  7. iOS JSON解析 NSJSONReadingMutableContainers的作用:

    转载自:   http://www.cnblogs.com/linyc/p/4272060.html 首先用代码来说明NSJSONReadingMutableContainers的作用: 1 NSSt ...

  8. ios markdown 解析_Shortcuts 教程:正则表达式修改 Markdown 链接

    这是我的「写作流」Shortcuts. 上一篇文章中,我谈到由于官方编辑器完全不支持 Markdown 语法,我不得不利用 Shortcut 以及公众号 Web API 处理 Markdown 文本的 ...

  9. ios网址解析中,中文部分如何处理

    在网络解析中,中文出现的时候,在解析数据是中文会显示为 %E7%81%AB%E5%BD%B1%E5%BF%8D%E8%80%85 这样的形式但是如果我们之间用字符串拼接键值对的时候但多数出现的不是错误 ...

最新文章

  1. spring获取webapplicationcontext,applicationcontext几种方法详解(转载)
  2. 安卓应用安全指南 4.4.3 创建/使用服务高级话题
  3. 关于C#调用API的理解(汇多考勤机HD4K)
  4. 判断奇数偶数和闰年(Python)
  5. NRedis-Proxy - 高性能中间件服务器
  6. 数据本地存储方法封装(笔记)localStorage、sessionStorage
  7. 7-121 删除字符串中的子串 (20 分)
  8. mysql 主从 锁库_mysql 5.7.21 主从集群恢复GTID方式(不锁库)
  9. 读《自己动手写操作系统》(于渊著)第一节
  10. java 最大素数,JAVA计算指定上限的最大素数
  11. css3立方体3d旋转,纯css3实现的3D立体动态旋转立方体特效
  12. select每门可说大于90分_MYSQL数据库查询
  13. Universal Style Transfer via Feature Transforms (WCT,风格迁移,NIPS2017)
  14. VM虚拟机:VMware Kernel Module Updater
  15. 关于编程的自我介绍和规划。
  16. hive 窗口函数--常用函数介绍
  17. PMP考试技巧+前九章内容提炼整理(不定时更新)
  18. Google Play下载到了内部测试包
  19. Ubuntu下GPU显存无法释放和多卡训练时候的一些总结
  20. Python 基础篇(三)--初步编程总结,各种常识

热门文章

  1. Java开发大厂面试资料,让你的面试不再困难!
  2. C语言基础教程篇之作用域规则
  3. Spring Boot Admin Reference Guide
  4. 2018年EMUI系统能力分论坛来啦
  5. matplotlib常用绘图方法【转】
  6. Android开发— 2016_最流行的Android组件、工具、框架大全(二)
  7. xshell登陆腾讯云服务器
  8. C语言及程序设计初步例程-42 将数据输出到文本文件
  9. ubuntu 14.04 下搭建SVN服务器 svn://
  10. 大卫谈学习5:成长的烦恼