新创建一个命令行项目,创建ZJPerson类和ZJPerson(Study)分类
这样分类就算间接完成添加属性的功能,我们在main函数中使用一下
可以看到分类添加的属性使用效果和在类里直接添加的属性效果一样
我们打开源码,搜索出objc_setAssociatedObject(, 找到这个方法的源码
点击进入_object_set_associative_reference方法
上面这段源码怎么理解呢
大概意思就是有AssociationsManager这么一个类,它的内部维护了一个全局的字典AssociationsHashMap
AssociationsHashMap字典的key对应的是disguised(object),类似于当前对象的内存地址,而value存储的是ObjectAssociationMap字典
ObjectAssociationMap字典的key对应的是添加的属性的名字,value呢,则对应的是ObjcAssociation实例
ObjcAssociation实例则存储着添加属性的值和策略
结构如下图所示
ZJPerson在study分类里添加了一个属性bookName,在main函数中给person实例的bookName属性赋值了@"How to study",那么系统是怎么存储这个属性的值呢?
其存储结构如下
在例如,我们在main函数中创建两个person对象
则其存储结构如下
Category能否添加成员变量?如果可以,如何给Category添加成员变量?
原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性(Runtime)。
举个例子,给系统UIButton添加个model 属性 新建个分类文件
.h文件
.m文件 需要导入runtime头文件
category的主要作用是为已经存在的类添加方法。
使用分类的好处,可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,
a)可以减少单个文件的体积
b)可以把不同的功能组织到不同的category里
c)可以由多个开发者共同完成一个类
d)可以按需加载想要的category 等等。
f) 声明私有方法
1)、类的名字(name)
2)、类(cls)
3)、category中所有给类添加的实例方法的列表(instanceMethods)
4)、category中所有添加的类方法的列表(classMethods)
5)、category实现的所有协议的列表(protocols)
6)、category中添加的所有属性(instanceProperties)
从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
但是为什么网上很多人都说Category不能添加属性呢?
实际上,Category实际上允许添加属性的,同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法。但实际上可以使用runtime去实现Category为已有的类添加新的属性并生成getter和setter方法。这里使用的就是关联对象,会在下一篇文章中讲解。
那么这里有人会问,为什么不能添加成员变量呢?
我们知道,oc的类struct 中的结构如下
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE // 父类
const char *name OBJC2_UNAVAILABLE // 类名
long version OBJC2_UNAVAILABLE // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE // 协议链表
#endif
} OBJC2_UNAVAILABLE
在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。
extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
但是category则完全不一样,它是在运行期决议的。 就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
1、分类只能增加方法,不能增加成员变量。
2、分类方法实现中可以访问原来类中声明的成员变量。
3、分类可以重新实现原来类中的方法,但是会覆盖掉原来的方法,会导致原来的方法没法再使用(实际上并没有真的替换,而是Category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的Category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休,殊不知后面可能还有一样名字的方法)。
4、当分类、原来类、原来类的父类中有相同方法时,方法调用的优先级:分类(最后参与编译的分类优先) –>原来类 –>父类,即先去调用分类中的方法,分类中没这个方法再去原来类中找,原来类中没有再去父类中找。
5、Category是在runtime时候加载,而不是在编译的时候。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)