关联对象简介
associatedObject又称关联对象。顾名思义,就是把一个对象关联到另外一个对象身上。使两者能够产生联系。之前写项目的时候在cell中加入了输入框为了避免输入框内容的复用以及获取特定输入框的文本,需要给UITextField添加一个新的属性indexpath和cell关联。众所周知,分类是无法静态添加成员变量的,所以这里就利用到了Associated Object为该分类动态添加属性。
关联对象的使用
下面就为NSObject的分类添加一个属性。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (testCategory)
@property (nonatomic, strong) id associatedObject;
@end
NS_ASSUME_NONNULL_END
主要在于实现部分:
#import "NSObject+testCategory.h"
#import <objc/runtime.h>
@implementation NSObject (testCategory)
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, _cmd);
}
@end
可以看到上面主要运用了两个方法,下面具体解析一下这两个方法。
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) : 这个方法有四个参数。
- @param object 关联源对象,也就是需要关联的对象。
- @param key 对应的key
- @param value 对象的key值。传递nil来清除现有关联。
- @param policy 内存管理策略
内存管理策略: 点进去看可以发现是一个枚举类型。
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
};
看一下这个方法的源码,这个方法世纪调用的是下面的方法。:
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
if (!object && !value) return;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
isFirstAssociation = true;
}
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
if (isFirstAssociation)
object->setHasAssociatedObjects();
association.releaseHeldValue();
}
这里解释一下上面的几个点。
- AssociationsHashMap为什么是全局唯一?
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
这里可以看到hashMap每次都是从静态变量取出,所以全局只会获取他一个。而AssociationsManager没有修饰,可以多次创建。 2.AssociationsHashMap,ObjectAssociationMap是什么?
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
通过定义可以看出来,前者根据DisguisedPtr也就是object转化的类型对应了每个对象的后者,里面存的则是key对应的ObjcAssociation。 3.ObjcAssociation是什么?
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
ObjcAssociation(const ObjcAssociation &other) = default;
ObjcAssociation &operator=(const ObjcAssociation &other) = default;
ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
swap(other);
}
inline void swap(ObjcAssociation &other) {
std::swap(_policy, other._policy);
std::swap(_value, other._value);
}
inline uintptr_t policy() const { return _policy; }
inline id value() const { return _value; }
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
inline void releaseHeldValue() {
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
objc_release(_value);
}
}
inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
}
inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
};
可以看到这个类里面存的成员变量就是内存策略和关联的值,也就是我们添加的属性是存在这里的。
_object_get_associative_reference方法
这个源码就比较简单了
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
通过源码可知,主要分为以下几部分
1:创建一个 AssociationsManager 管理类
2:获取唯一的全局静态哈希Map:AssociationsHashMap
3:通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
4:如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (policy和value)
5:通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
6:返回 value
删除关联对象
系统提供了_object_remove_assocations(id object, bool deallocating)方法 ,你可能会在刚开始接触对象关联时想要尝试去调用objc_removeAssociatedObjects() 来进行删除操作,但如文档中所述,你不应该自己手动调用这个函数。此函数的主要目的是在“初试状态”时方便地返回一个对象。你不应该用这个函数来删除对象的属性,因为可能会导致其他客户对其添加的属性也被移除了。规范的方法是:调用objc_setAssociatedObject方法并传入一个nil 值来清除一个关联。
一些map对应关系
图片参考:iOS-底层原理 19:类扩展 与 关联对象 底层原理探索
写这个是为了了解怎么存取属性的,目前对整体了解的还是比较浅,没有对对象转化为disguised_object的分析,后续学习了过来补充。
try_emplace方法探究
之前源码中出现了try_emplace方法: try_emplace方法的作用就是去表中查找Key相应的数据,不存在就创建。 下面看一下该方法的具体实现:
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false);
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
- 通过LookupBucketFor方法去表中查找Key对应的TheBucket是否有存在,如果存在对TheBucket进行包装然后返回
- 如果不存在,通过InsertIntoBucket方法插入新值,扩容的规则和cache方法存储的规则是一样的
LookupBucketFor方法: 这个方法就是 根据Key 去表中查找Bucket ,如果已经缓存过,返回true ,否则返回false
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket;
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
InsertIntoBucket方法:
template <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
TheBucket->getFirst() = std::forward<KeyArg>(Key);
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
主要的工作都是在InsertIntoBucketImpl 方法中完成的
- 计算实际占用
buckets 的个数,如果超过负载因子(3/4),进行扩容操作this->grow(NumBuckets * 2) ; - 找到
TheBucke t的内存地址:LookupBucketFor(Lookup, TheBucket) ; - 更新占用的容量个数:
incrementNumEntries();
template <typename LookupKeyT>
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
unsigned NewNumEntries = getNumEntries() + 1;
unsigned NumBuckets = getNumBuckets();
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
this->grow(NumBuckets * 2);
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
incrementNumEntries();
decrementNumTombstones();
} else {
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
关联对象实际关系
|