要想知道区别,首先要知道用途及特点
一、分类
1、分类常怎么用?
a.把内容臃肿的类文件用分类的方法分解成一个或者几个类。
b.framework私有方法公开化(重写私有方法)
c.声明私有方法
2、分类的特点(跟扩展的区别)
a.运行时决议
b.可以为系统类添加分类
3、分类可以添加什么
a.实例方法
b.类方法
c.协议
d.属性,但不能添加实例变量,需要用到runtime关联对象的方法。
如以下源码(代码取自官方runtime demo):
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;
}
};
Tip1:假如一个类有好几个分类,并且这些分类里面有重名的方法,哪个方法会生效?为什么分类是运行时决议?
因为runtime进行时是倒序访问编译的分类的,也就是编译的越早越先访问,所以最后编译的那个分类的同名方法才会生效。
源码如下:
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
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));
// 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];
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);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
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);
}
Tip2:分类添加的方法为什么能“覆盖”原来类的方法?
因为在tip拼接类方法的时候是把分类的方法拼接到方法列表的最前面,方法选择器在查找方法的时候查到同名方法就会返回,所以会实现分类里面的方法。因此原来类的方法并不是被覆盖。源码如下:
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]));
}
}
3、通过关联对象的方法给分类添加实例变量,其实用的是runtime的方法。
用法如下:
static NSString *const kDefaultImageViewKey = @"defaultImageView";
- (UIImageView *)defaultImageView {
UIImageView *imgView = objc_getAssociatedObject(self, &kDefaultImageViewKey);
if (!imgView) {
imgView = [[UIImageView alloc] init];
imgView.image = [UIImage imageNamed:@"JMDefaultPage_order"];
imgView.contentMode = UIViewContentModeScaleAspectFill;
objc_setAssociatedObject(self, &kDefaultImageViewKey, imgView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return imgView;
}
其底层实现是通过的runtime的方法,如下:
id objc_getAssociatedObject(id object, const void *key)
{
return objc_getAssociatedObject_non_gc(object, key);
}void objc_setAssociatedObject(id object, const void *key, id value,
objc_AssociationPolicy policy)
{
objc_setAssociatedObject_non_gc(object, key, value, policy);
}#endif
void objc_removeAssociatedObjects(id object)
{
#if SUPPORT_GC
if (UseGC) {
auto_zone_erase_associative_refs(gc_zone, object);
} else
#endif
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
}// 底层实现方法
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());// 获取其维护的一个hashMap
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);
}
其本质用图表示如下:
二、扩展
1、扩展常用作什么
a、声明私有属性、私有变量。
b、声明私有方法
2、扩展的特点
a、编译时决议
b、只以声明的形式存在
c、不能为系统类添加扩展