本文主要介绍Objectiive-C的Category,当你需要扩展系统SDK提供的类的时候,Category就十分有用.Category允许你为已经存在的类添加属性或者方法,供自己使用.
Category添加的属性和方法是可以供类和其子类使用的.
1.Category 添加实例方法,类方法和属性:
@interface UIView (TagID)
-(void)logUIView;
+(void)debugLogUIView;
@property (nonatomic,copy)NSString * tagName;
@end
-(void)setTagName:(NSString *)tagName{
objc_setAssociatedObject(self, "tagName", tagName, OBJC_ASSOCIATION_COPY);
}
-(NSString *)tagName{
return objc_getAssociatedObject(self, "tagName");
}
-(void)logUIView{
NSLog(@"self-->%@ infomation-->%@",[self class],[self description]);
}
+(void)debugLogUIView{
NSLog(@"self-->%@ info-->%@",self,[self debugDescription]);
}
2.Category 是不能添加实例变量的,因为category是在运行时才会去添加,此时对象的内存布局已经确定,不能添加实例变量.
3.Objectiive-C的Category 实现原理
Runtime源码中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;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
可以看到Category 的结构体中存在名字,类,实例方法,类方法,协议,实例属性,类属性,还有Category结构体自带的方法methodsForMeta(该方法用于返回Categroy的方法).
同时Runtime会把Categroy添加到类上
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
// 获取当前类是不是MetaClass
bool isMeta = cls->isMetaClass();
//为方法列表,和属性列表,协议列表开辟内存空间
// fixme rearrange to remove these intermediate allocations
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));
//循环把Categroy加到类上
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
// 获取Categroy方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//获取Categroy属性列表
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//获取Categroy协议列表
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//获取类的符号结构
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 将Catrgroy的方法列表添加到类上(copy到类的方法列表数组内部)
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
//将Catgroy的属性列表添加到类上(copy到类的方法列表数组内部)
rw->properties.attachLists(proplists, propcount);
free(proplists);
//将Catgroy的协议列表添加到类上(copy到类的方法列表数组内部)
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
下面这段代码就是attachLists的实现
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;
// 将原来的数组移动到新数组的addedCount位置之后
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将addedLists copy到新数组的首位开始的位置
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;
// 将addedLists copy到新数组的首位开始的位置
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找.
关于Categroy的关联对象
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);
// 如果value存在根据policy发送SEL_retain 或SEL_copy
id new_value = value ? acquireValue(value, policy) : nil;
{
// 获取到AssociationsManager的hashMap这是储存所有关联对象的hashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
// 迭代hashMap并存储关联的对象
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);
}
Runtime采用的是全局的HashMap存储关联对象.