Ios分类、扩展区别及底层实现

要想知道区别,首先要知道用途及特点
一、分类
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、不能为系统类添加扩展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值