iOS分类Category探索
什么是Category?
Category是Objective-C 2.0之后添加的语言特性,Category的主要作用是为已经存在的类添加方法,一般称为分类,文件名格式是"NSObject+A.h"。
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;
struct property_list_t *_classProperties;
}
从结构能看出分类可以扩展实例方法列表、类方法列表、协议列表,也支持扩展属性,但不支持扩展成员变量(之后会说)。
一般使用的场景有扩展现有类方法、代码分区、添加私有方法(不对外暴露category.h)、模拟多继承(使用关联对象的方式添加属性实现)
什么是Extension?
Extension一般被称为类扩展、匿名分类,用于定义私有属性和方法,不可被继承。只能依附自定义类写于.m中,定义一般为:
@interface ViewController ()
@property (nonatomic, strong) NSObject *obj;
@end
类扩展支持写在多个.h文件,但都必须在.m文件中引用,且不能有自己的实现。
类扩展很多时候会与分类搞混,我在文后问答环节详细整理了他们的区别。
Category如何加载的?
struct objc_class : objc_object {
Class superclass;
class_data_bits_t bits;
class_rw_t *data() {
return bits.data();
}
...
}
struct class_rw_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
...
}
struct class_ro_t {
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //只有ro才有实例变量表
property_list_t *baseProperties;
...
};
先简单了解一下Class对象的结构,每个objc_class
都包含有class_data_bits_t
数据位,其中储存了class_rw_t
的指针地址和一些其他标记。class_rw_t
中包含有属性方法协议列表,以及class_ro_t
指针地址。而在class_ro_t
结构中,储存的是编译器决定的属性方法协议。
那么是怎么运行的呢?
在编译期类的结构中的class_data_bits_t
指向的是一个 class_ro_t
指针。
在运行时调用realizeClass
方法,初始化一个class_rw_t
结构体,设置ro值为原数据中的class_ro_t
后设为数据位中的指向,最后调用methodizeClass
方法加载。
static void methodizeClass(Class cls)
{
auto rw = cls->data();
auto ro = rw->ro;
//从ro中加载方法表
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
//加载属性
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
//加载协议
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
//基类添加初始化方法
if (cls->isRootMetaclass()) {
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
//加载分类
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
if (cats) free(cats);
}
可以看到,在methodizeClass
中加载了原先类在编译期决定的方法属性和协议,然后获取了未连接的分类表,将列表中的扩展方法添加到运行期类中。
Category方法覆盖
如果不同的分类实现了相同名字的方法,那么调用时会使用最后加入的实现,这是为什么呢?
加载Category
dyld链接并初始化二进制文件后,交由ImageLoader
读取,接着通知runtime
处理,runtime
调用map_images
解析,然后执行_read_images
分析文件中包含的类和分类。
//加载分类
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
//分类指定的类还没加载,可能是链接库顺序的问题
catlist[i] = nil;
continue;
}
//添加分类到类的分类表中,伺机重载入
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
}
//添加分类到元类中
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
添加方法属性和协议
如果有新增的分类,就分别添加到原类和meta类,并通过remethodizeClass
更新,具体就是调用attachCategories
方法把分类中所有的方法都添加到指定类中。
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
bool isMeta = cls->isMetaClass();
//新建数组指针
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;//倒序获取最新的分类
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
//分别获取列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
//加载列表到rw中
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
可以看到最后调用了rw->methods.attachLists(mlists, mcount);
把新增分类中的方法列表添加到实际运行时查询的方法列表头部。
在进行方法调用时会从头部查询,一旦查到后就返回结果,因此后编译的文件中的方法会被优先调用。
同时之前添加的方法实现也保存了,可以通过获取同名方法的方式查找原类的实现。
Category实现属性
分类不能添加成员变量
属性(Property)包含了成员变量(Ivar)和Setter&Getter。
可以在分类中定义属性,但由于分类是在运行时添加分类属性到类的属性列表中,所以并没有创建对应的成员变量和方法实现。
关联对象
如果我们想让分类实现添加新的属性,一般都通过关联对象的方式。
// 声明文件
@interface TestObject (Category)
@property (nonatomic, strong) NSObject *object;
@end
// 实现文件
static void *const kAssociatedObjectKey = (void *)&kAssociatedObjectKey;
@implementation TestObject (Category)
- (NSObject *)object {
return objc_getAssociatedObject(self, kAssociatedObjectKey);
}
- (void)setObject:(NSObject *)object {
objc_setAssociatedObject(self, kAssociatedObjectKey, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
这种方式可以实现存取对象,但是不能获取_object
变量。
问答
分类和扩展有什么区别?
1.分类多用于扩展方法实现,类扩展多用于申明私有变量和方法。
2.类扩展作用在编译期,直接和原类在一起,而分类作用在运行时,加载类的时候动态添加到原类中。
3.类扩展可以定义属性,分类中定义的属性只会申明setter/getter,并没有相关实现和变量。
分类有哪些局限性?
1.分类只能给现有的类加方法或协议,不能添加实例变量(ivar)。
2.分类添加的方法如果与现有的重名,会覆盖原有方法的实现。如果多个分类方法都重名,则根据编译顺序执行最后一个。
分类的结构体里面有哪些成员?
分类结构体包含了分类名,绑定的类,实例与类方法列表,实例与类方法属性以及协议表。
参考
神经病院 Objective-C Runtime 入院第一天—— isa 和 Class
iOS分类Category探索的更多相关文章
- iOS分类(category),类扩展(extension)—史上最全攻略
背景: 在大型项目,企业级开发中多人同时维护同一个类,此时程序员A因为某项需求只想给当前类currentClass添加一个方法newMethod,那该怎么办呢? 最简单粗暴的方式是把newMethod ...
- ios 分类(Category)
今天研究了类别,都是网上找的资料,类别的作用 类别主要有3个作用: (1)将类的实现分散到多个不同文件或多个不同框架中. (2)创建对私有方法的前向引用. (3 ...
- 关于ios object-c 类别-分类 category 的静态方法与私有变量,协议 protocol
关于ios object-c 类别-分类 category 的静态方法与私有变量,协议 protocol 2014-02-18 19:57 315人阅读 评论(0) 收藏 举报 1.category, ...
- iOS之分类(category)
1.分类(category)的作用 1.1作用:可以在不修改原来类的基础上,为一个类扩展方法.1.2最主要的用法:给系统自带的类扩展方法. 2.分类中能写点啥? 2.1分类中只能添加“方法”,不能增加 ...
- iOS中 分类(category)与扩展(Extension)的区别?
1.分类(category)的作用 (1).作用:可以在不修改原来类的基础上,为一个类扩展方法.(2).最主要的用法:给系统自带的类扩展方法. 2.分类中能写点啥? (1).分类中只能添加“方法”,不 ...
- 从C#到Objective-C,循序渐进学习苹果开发(3)--分类(category)和协议Protocal的理解
本随笔系列主要介绍从一个Windows平台从事C#开发到Mac平台苹果开发的一系列感想和体验历程,本系列文章是在起步阶段逐步积累的,希望带给大家更好,更真实的转换历程体验.本文继续上一篇随笔<从 ...
- iOS类别(Category)
iOS类别(Category)与扩展(Extension) 苹果的官方文档 Category在iOS开发中使用非常频繁.尤其是在为系统类进行拓展的时候,我们可以不用继承系统类,直接给系统类添加方法,最 ...
- iOS分类底层实现原理小记
摘要:iOS分类底层是怎么实现的?本文将分如下四个模块进行探究分类的结构体编译时的分类分类的加载总结本文使用的runtime源码版本是objc4-680文中类与分类代码如下//类@interfaceP ...
- 分类(Category)的本质 及其与类扩展(Extension) /继承(Inherit)的区别
1.分类的概念 分类是为了扩展系统类的方法而产生的一种方式,其作用就是在不修改原有类的基础上,为一个类扩展方法,最主要的是可以给系统类扩展我们自己定义的方法. 如何创建一个分类?↓↓ ()Cmd+N, ...
随机推荐
- ORA-28001: the password has expired (DBD ERROR: OCISessionBegin)解决办法
1.问题描述: 打开oracle在线管理页面发现这个错误:界面如下 2问题原因 造成这个问题的主要原因是因为DBSNMP .SYSMAN用户密码已经过期. 3解决办法 可以使用sys以管理员的身份登录 ...
- Hadoop学习之路(三)Hadoop-2.7.5在CentOS-6.7上的编译
下载Hadoop源码 1.登录官网 2.确定你要安装的软件的版本 一个选取原则: 不新不旧的稳定版本 几个标准: 1)一般来说,刚刚发布的大版本都是有很多问题 2)应该选择某个大版本中的最后一个小版本 ...
- C语言偏冷知识点汇总
1.C语言函数声明中参数类型写在右括号后是什么意思?如下代码所示: int add(a, b) int a; int b; { return a + b; } 像这样的声明是什么意思,我测试过在gcc ...
- java基础 三 概念和java程序的结构.
一.java的一些概念: JRE(java runtime environment):java程序运行环境,如果要运行java程序,需要jre支持.jre里包含jvm JDK(java devel ...
- smtp发送html报告与日志附件图片png
1.非ssl发送: 授权码机制,开启smtp,获取授权码以qq邮箱为例: 附件展示: #!/usr/bin/python3 import os import smtplib from email.mi ...
- python logging模块日志回滚TimedRotatingFileHandler
# coding=utf-8 import logging import time import os import logging.handlers import re def logger(app ...
- gulp插件 run-sequence(同步执行任务)
功能描述 gulp默认使用最大并发数执行任务,也就是说所有的任务几乎都是同时执行,而不会等待其它任务.但很多时候,任务是需要有先后次序的,比如要先清理目标目录,然后再执行打包. run-sequenc ...
- CAN总线布线规范
CAN总线布线规范 摘要:今天的CAN总线已从汽车电子慢慢渗透入工业自动化,医疗,铁路等众多领域.据我们的数据统计,客户在使用CAN总线时约80%的问题均是由总线布局布线不合理导致,今天我们就来扒一扒 ...
- 把外置sd卡映射为内置sd卡地一个目录
教程:1.已root机器运行re浏览器2.在/sdcard卡上创建目录sd-ext3.找到/etc/rc.local,长按选编辑4.拉到文件最后,在最后一行exit 0前行添加: (sleep ...
- 4D产品(DLG、DEM、DOM、DRG)介绍及区别
4D产品(DLG.DEM.DOM.DRG)是什么? 4D产品是指DRG(数字栅格地图).DLG(数字线化图) .DEM(数字高程模型).DOM(数字正射影像图).4D 复合产品是将4D产品中的任意两种 ...