Category是啥东东?
Category(分类)是在Objective-C 2.0之后出现的,主要用于给现有类添加方法,以便对原有类的一个补充和完善
Category有什么特点?
1、分类是在运行时期决议的。
2、分类中添加的方法子类是可继承的
Category中能添加哪些内容?
首先看下源码的实现:
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;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
在category_t的结构体中我们可以看到如下:
name:类名
cls:类
instanceMethods:实例方法列表
classMethods:类方法列表
protocols:协议列表
instanceProperties:属性列表
由上可知:
分类中可以添加:实例方法、类方法、协议、属性
但是category是不能添加成员变量的,从源代码层面上解释就是category_t的结构体里面没有ivars成员变量列表。
这里有个疑问:上面不是说可以添加属性么,按照正常情况来说,我在类文件里面使用@property声明一个属性,编译器就会自动生成一个带下划线的成员变量呀,这样不就是能添加成员变量嘛,NONONO,在category里面这样的想法是不对的,下面我解释一下原因:
第一、在分类里使用@property声明属性,只是将该属性添加到该类的属性列表(instanceProperties),声明的getter、setter方法添加到实例方法列表,但是不会生成相应的成员变量,也没有实现setter、getter方法。
第二、前面有讲到category是在运行时期决议的,因为在运行期也就是编译完成之后,对象的内存布局已经确定,如果添加实例变量就需要重新内存对齐,就会破坏类的内部布局,这对编译型语言来说是不可取的。
这里说明一点,其实有很多开发者包括我自己以前都会误认为category是不能添加属性的,其实这种理解是错误的,属性是可以添加的,在category中@property声明一个属性编译器并不会报错,但是在运行时期调用setter、getter方法的时候就会报错,错误原因是找不到该方法,所以也证明了category能添加属性,但是不能添加成员变量。
但是我们在开发过程中需要往分类中添加成员变量怎么办呢?
这个问题其实大家都知道改如何解决,那就是runtime的关联对象,说到这,可能有人又会有疑问啦,既然说category中添加成员变量会遇到内存对齐问题,为什么通过runtime就可以添加呢,所以请带着疑问继续往下继续看:
void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
class AssociationsManager {
static spinlock_t _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { _lock.lock(); }
~AssociationsManager() { _lock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
通过源代码我们可以看见,关联对象是由AssociationsManager进行管理,AssociationsManager结构体里面是由一个静态AssociationsHashMap来存储所有的关联对象的,
disguised_ptr_t disguised_object = DISGUISE(object);这里是获取要关联属性的对象的指针地址,作为map的key,value对应的是ObjectAssociationMap,看下ObjectAssociationMap里面都有什么
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>
其中void *就是我们在objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy)中传入的key,
然后看下ObjcAssociation
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
这里面的value就是我们关联的对象成员变量的值。
到这里,关联对象的解释基本就结束了,所以看得出来,通过关联对象给category添加成员变量由于是存储在一个全局的AssociationsManager里面,所以并不会对现有类的内存造成影响。