Men的博客

欢迎光临!

0%

Category

Objective-C中category的实现原理和runtime有什么关系?

Objective-C 中的 Category 就是对装饰模式的一种具
体实现。它的主要作用是在不改变原有类的前提下,动态地
给这个类添加一些方法。在 Objective-C 中的具体体现为:
实例(类)方法、属性和协议。是的,在 Objective-C 
中可以用 Category 来实现协议
在runtime层,Category维护一个名为category_map的哈希表,哈希表存
储category_t对象,Category添加方法,然后调用remethodizeClass
向对应的class中添加Category的信息
将 Category 和它的主类(或元类)注册到哈希表中;
如果主类(或元类)已实现,那么重建它的方法列表。
Category 中的实例方法和属性被整合到主类中;
而类方法则被整合到元类中。
另外,对协议的处理比较特殊,Category 中的协议被同
时整合到了主类和元类中。
我们注意到,不管是哪种情况,最终都是通过调用 
static void remethodizeClass(Class cls) 
函数来重新整理类的数据的。
这个函数的主要作用是将 Category 中的方法、属性和协议
整合到类(主类或元类)中,更新类的数据字段 data() 中
method_lists(或 method_list)、properties 和
protocols 的值。进一步,我们通过
attachCategoryMethods 函数的源码可以找到真正处理
Category 方法的 attachMethodLists 函数:

category结构体的定义

struct category_t {
    const char *name;     //类的名字(name)
    classref_t cls;       //类(cls)
    struct method_list_t *instanceMethods; 
    //category中所有给类添加的实例方法的列表(instanceMethods)
    struct method_list_t *classMethods; 
    //category中所有添加的类方法的列表(classMethods)
    struct protocol_list_t *protocols; 
    //category实现的所有协议的列表(protocols)
    struct property_list_t *instanceProperties;
    //category中添加的所有属性(instanceProperties)
}

类别(category)主要有三个作用

1.将类的实现分散到多个不同文件或多个不同框架中
2.创建对私有方法的向前引用(cocoa没有真正的私有方法)
3.向对象添加非正式协议(创建一个NSObject的类别称为“创建一个非正式协
议”)因为,NSObject是顶级父类,在NSObject中添加了该方法,也就是说
通过继承关系,所有的类中都有该方法。

类别和Extension区别

和Category相似的还有Extension,二者的区别是Extension
在编译期就直接和原类编译在一起了,而Category是在
运行时添加到原类中。

类别为什么不能添加实例变量

category是无法添加实例变量的(因为在运行期,对象的内存布局已
经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来
说是灾难性的)。

类别重写类中方法

1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果
category和原来类都有methodA,那么category附加完成之后,类的方法列
表里会有两个methodA
2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新
方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类
的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,
它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的
方法。
重写其原有方法,会导致Category的方法覆盖原有类的方法,
但是load方法是例外,category和原有类的load方法都会被执行。

如何在重写后,保证原类方法会调用

可以在一个Category方法被调用后,便利方法列表,
并调用其他同名方法,但是需要注意一点是,便利过程不能
再调用自己的方法,否则会导致递归调用,为了避免这个问题,
可以在调用前判断被调用的方法IMP是否是当前方法的IMP

如何在重写后,保证只调用原类方法

根据上面方法调用的分析,Runtime在调用方法时会优先所
有Category调用,所以可以倒叙便利方法列表,只便利第一
个方法即可,这个方法就是元类的方法。

如何在Category中添加变量

关联对象来实现。我们可以看到所有的关联对象都由
AssociationsManager管理
AssociationsManager里面是由一个静态AssociationsHashMap来存储
所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。
而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不
同的),而这个map的value又是另外一个AssociationsHashMap,里面保
存了关联对象的kv对。
@interface TestObject (Category)
@property (nonatomic, strong) NSObject *object; @end
#import <objc/runtime.h>
#import <objc/message.h>
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);
}
从原码中可以看出,所有通过associated添加的属性,
都是被单独存放在一个哈希表AssociationsHashMap中,
objc_setAssociatedObject和
objc_getAssociatedObject本质上就是操作这个哈希表,
通过对哈希表进行映射来存取对象。

在协议 和 分类 中如何使用 @property

1、在 protocol 中使用 property 只会生成 setter 和 getter 方法
声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
2、category 使用 @property 也是只会生成 setter 和 getter 方法
的声明,真要添加属性建议使用关联对象来实现