iOS开发进阶

iOS开发进阶:关联对象、weak指针的底层实现原理

2021-10-27  本文已影响0人  __Null

一、关联对象的实现原理

在分类中使用@property声明属性,只是将该属性添加到类属性列表,并声明了settergetter方法,但是并没有生成对应的成员变量,也没有实现settergetter方法。由于没有实现实现settergetter方法,所以访问的时候会crash,提示... unrecognized selector sent to instance ...

如果在分类中使用@property声明属性后,手动实现settergetter方法,那么就可以正常设置和读取这个属性了,实现的方式可以参考如下:

@interface NXTeacher(Subperson)
@property (nonatomic, copy) NSString *address;
@end

static const char *kAddressKey = "AddressKey";

@implementation NXTeacher(Subperson)
- (void)setAddress:(NSString *)address{
    objc_setAssociatedObject(self, kAddressKey, address, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)address {
    return objc_getAssociatedObject(self, kAddressKey);
}
@end

这里用到了objc_setAssociatedObject(...)objc_getAssociatedObject(...)方法,该方法内部分别调用了_object_set_associative_reference(...)_object_get_associative_reference(...)

先看一下objc_setAssociatedObject(...)方法,第一个参数id object,传我们的对象实例self;第二个参数const void *key,需要我们定义一个跟属性相关的const *类型的变量;第三个参数id value, 传设置的新值;第四个参数objc_AssociationPolicy policy,表示关联策略,他有5个枚举值:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0, 
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 
    OBJC_ASSOCIATION_RETAIN = 01401,  
    OBJC_ASSOCIATION_COPY = 01403  
};
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){
     ...
    //disguised相当于是object的一个唯一key(内部对指针地址按位取反)
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};
    //acquireValue方法内部会根据关联策略是value对象进行objc_retain(value)或[value copy]操作。
    association.acquireValue();
    bool isFirstAssociation = false;
    {
        //AssociationsManager是一个关联对象管理类,内部_mapStorage是个静态变量。
        //manager在这里是利用构造函数、析构函数特性来自动加锁、解锁,manager.get()从_mapStorage中读出所有的关联表associations。
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());

        if (value) {
            //如果新的关联值不为空
            //根据disguised查找一个对应的ObjectAssociationMap,没有的话会创建一个并插入到associations中。
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                isFirstAssociation = true;
            }

            auto &refs = refs_result.first->second;
            //根据key查找对应的bucket(key和association的封装),替换掉原有的或插入新的association,并设置关联策略
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } 
        else {
            //如果新的关联值为空,查找该对象的对应的ObjectAssociationMap,返回查找的迭代器。
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);//通过key去refs中查找该属性的ObjectAssociation,返回查找的迭代器
                if (it != refs.end()) {
                    //如果存在,进行擦除操作
                    association.swap(it->second);
                    refs.erase(it);
                    
                    //如果refs也没有该对象的其他关联属性的话,直接从associations中移除该对象对应的refs_it。
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    //在isa中标记该对象已经有关联对象了
    if (isFirstAssociation) object->setHasAssociatedObjects();
    //上面对value进行了retain操作的,这里进行一次release
    association.releaseHeldValue();
}

知道了这个值怎么设置之后,那么读取这个值的思路就很简单了:

id _object_get_associative_reference(id object, const void *key){
    ObjcAssociation association{};
    {
        //全部的关联对象表associations
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            //refs当前对象的,找到后根据key取当前属性的(通过迭代器遍历获取)
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                //赋值给association
                association = j->second;
                //如果关联策略包含retain, 这里就会retain一下。
                association.retainReturnedValue();
            }
        }
    }
    //处理autorelease
    return association.autoreleaseReturnedValue();
}

除此之外还有一个跟关联对象密切相关的函数是objc_removeAssociatedObjects(...),如果对象有关联对象则内部会调用_object_remove_assocations(...)

void _object_remove_assocations(id object, bool deallocating){
    ObjectAssociationMap refs{};
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            //读取到当前对象所有的关联对象
            refs.swap(i->second);

            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    SmallVector<ObjcAssociation *, 4> laterRefs;

    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            if (deallocating)
                laterRefs.append(&i.second);
        } 
        else {
            //对应用的对象发送release消息
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

上面所使用的关键概念作一下解释:

二、weak指针的实现原理

weak指针主要用于打破循环引用,应用场景较为广泛。那么weak修饰的指针与被指向的对象在底层的运作机制是怎样的呢?
1.weak指针实现原理的源码在static id storeWeak(id *location, objc_object *newObj),跟着源码进入一看究竟(源码已做大量精简和修改,仅留下核心流程):

static id storeWeak(id *location, objc_object *newObj){
    if (haveOld) {
        //通过weak指针之前指向的对象oldObj来找到之前的所存储在的sidetable
        id oldObj = *location;
        SideTable *oldTable = &SideTables()[oldObj];
        //当前weak指针从旧对象的弱引用表oldTable->weak_table中清除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    if (haveNew) {
        //通过新的对象newObj来找到现在对应的sidetable
        SideTable *newTable = &SideTables()[newObj];
        //写入弱引用表
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location,  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        
        //标记新对象的isa.weakly_referenced = true;
        if (!newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock();}

        //赋值新值
        *location = (id)newObj;
    }
  
    return (id)newObj;
}

该函数总体来说做了2件事:

2.weak_unregister_no_lock的流程

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,   id *referrer_id){
    objc_object *referent = (objc_object *)referent_id;//被弱引用的对象
    objc_object **referrer = (objc_object **)referrer_id;//weak指针
    if (!referent) return;
    weak_entry_t *entry;
    //weak_entry_for_referent是指从weak_table->weak_entries中通过referent,拿到referent的引用实体
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //从entry->referrers中移除weak指针
        remove_referrer(entry, referrer);
        bool empty = true;
        if (entry->out_of_line()  &&  entry->num_refs != 0) {
            empty = false;
        }
        else {
            for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                if (entry->inline_referrers[i]) {
                    empty = false; 
                    break;
                }
            }
        }
        //如果entry->referrers中的weak指针全部移除了(空了),那么将当前的entry从weak_table中移除。
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
}

该函数有三个核心步骤:

  1. weak_register_no_lock的流程
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions){
    objc_object *referent = (objc_object *)referent_id;//被弱引用的对象
    objc_object **referrer = (objc_object **)referrer_id;//weak指针
    if (referent->isTaggedPointerOrNil()) return referent_id;
    ...
    weak_entry_t *entry;
    //weak_entry_for_referent是指从weak_table->weak_entries中通过referent,拿到referent的引用实体
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //将weak指针添加到entry->referrers中
        append_referrer(entry, referrer);
    } 
    else {
        //如果没有新对象的weak_entry_t,则新建一个new_entry(写入referent和referrer)
        weak_entry_t new_entry(referent, referrer);
        //如果weak_table中存储的存储已满3/4,则扩容2倍(首次开辟大小为64),扩容后会将原有的weak指针列表赋值一份到新的weak_entries中。
        weak_grow_maybe(weak_table);
        //将当前new_entry插入weak_table-> weak_entries中
        weak_entry_insert(weak_table, &new_entry);
    }
    return referent_id;
}

该函数有三个核心步骤:

无论是插入一个新的weak指针还是移除一个旧的weak指针,都仅仅围绕着weak_table_t和weak_entry_t展开的,看一下数据结构:

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;//弱引用表
};

//weak_table_t
struct weak_table_t {
    weak_entry_t *weak_entries;//weak_entry_t列表
    size_t    num_entries;//weak_entries中weak_entry_t的个数
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

//weak_entry_t
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;//被引用的对象
    union {
        struct {
            weak_referrer_t *referrers;//weak指针
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};

整个查找的思路:从最开始的StripedMap<SideTable>>中通过对象的hash查找对应的SideTable,然后从SideTable->weak_table->weak_entries中根据对象的hash查找出weak_entry_t。再根据weak_entry_t-> referrers进行weak指针的插入和删除。在向weak_table-> weak_entries插入weak_entry_t的时候涉及到扩容(如果需要),当weak_table->num_entries >= (weak_table.mask+1)3/4的时候,会进行2倍扩容(首次容量大小64)。在向weak_entry_t-> referrers插入weak指针的时候也涉及到扩容(如果需要),当weak_entry_t->num_refs >= (weak_entry_t-> mask+1)3/4的时候,会进行2倍扩容(首次容量大小为8)。

上一篇下一篇

猜你喜欢

热点阅读