前言
在 IOS底层原理之类和分类加载 中探究了分类的加载。今天探究下分类中属性的存储,也就是大家常说的关联对象
,以及类扩展
的探究
准备工作
- Objc-818.2
- 冰镇西瓜
类扩展
类扩展作用
- 为某个类附加额外的属性,成员变量,方法声明
- 网上许多博客都写到类扩展是一种特殊的匿名分类,这种说法我是不认同,分类和类扩展是有本质的区别的
- 分类的底层是
category_t
结构题类型,而类扩展底层是直接编译到主类中的 - 一般的类扩展写到
.m
文件中,一般的私有属性或者想要独立区别的属性写到类扩展
类扩展格式
.m
文件中的格式
@interface LWTeacher()
{
NSInteger height; //成员变量
}
@property(nonatomic,assign)NSInteger lw_age;//属性
-(void)helloWord;//对象方法
+(void)helloClass;//类方法
@end
这种声明在.m
文件的类扩展,基本上作为开发者每天都在用,但是我们确很少有人知道这就是类扩展
注意的是类扩展必须写在类的声明和类的实现直接
疑问:在.m
文件中没看到类的声明,只有扩展和实现
类的声明是在.h
文件中的,在编译时.h
文件会被展开放在.m
文件中
Extension
#import "LWTeacher.h"
@interface LWTeacher ()
@property(nonatomic,assign)int age;
@end
创建类扩展时发现只有一个.h
文件,没有与之对应的.m
。类扩展的所有的实现都是在一个.m
文件中实现的,其实Extension
就是相当于把.m
文件中类扩展的方式写到一个单独的头文件中,其实没啥区别
类扩展底层探究
把LWTeacher
写在main.m
目的是为了更好的探究.cpp
文件,代码如下
@interface LWTeacher : NSObject
- (void)sayHello;
@end
@interface LWTeacher()
{
NSInteger height;
}
@property(nonatomic,assign)NSInteger lw_age;
-(void)helloWord;
+(void)helloClass;
@end
@implementation LWTeacher
+(void)load{
}
- (void)sayHello{
}
- (void)helloWord{
}
+(void)helloClass{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LWTeacher * teacher = [[LWTeacher alloc] init];
[teacher sayHello];
}
return 0;
}
将main.m
文件生成main.cpp
文件
图中显示类扩展的变量和方法都是在编译时就已经确定,其实就是存储在类中。下面就验证下是否是在编译期就确定的,在_read_images
方法中下断点
图中很明显类扩展的方法也是在ro
中的,ro
是在编译期就确定的。所以现在得出结论类可以有多个类扩展,但是所有的实现都是在.m
文件中实现的
关联对象 - set
关联对象
应用的场景一般是在分类中添加属性,现在就探究下关联对象底层实现。创建LWTeacher+HH
分类和LWPerson+LWA
分类,源码如下
int main(int argc, const char * argv[]) {
@autoreleasepool {
LWPerson * person = [[LWPerson alloc] init];
person.lw_name = @"luwenhan";
person.lw_showHello = @"hello";
person.lw_showHello1 = @"H1111";
person.lw_showHello2 = @"H2222";
LWTeacher * teacher = [[LWTeacher alloc] init];
teacher.lw_teacher = @"teacher";
}
return 0;
}
//LWPerson+LWA
@implementation LWPerson (LWA)
+(void)load{
}
-(void)setLw_name:(NSString *)lw_name{
objc_setAssociatedObject(self, "lw_name", lw_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)lw_name{
return objc_getAssociatedObject(self, "lw_name");
}
...
// LWTeacher+HH
@implementation LWTeacher (HH)
+(void)load{
}
-(void)setLw_teacher:(NSString *)lw_teacher{
objc_setAssociatedObject(self, "lw_teacher", lw_teacher, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)lw_teacher{
return objc_getAssociatedObject(self, "lw_teacher");
}
@end
在LWPerson+LWA
分类定义4
个属性,在LWTeacher+HH
分类中创建定义一个属性。在objc_setAssociatedObject
中添加断点,运行源码,进入objc_setAssociatedObject
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
进入_object_set_associative_reference
方法里面的代码比较多,看下核心代码的实现
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
if (!object && !value) return; //没有值直接返回
if (object->getIsa()->forbidsAssociatedObjects())
...
//将 object 封装成 DisguisedPtr 目的是方便底层统一处理
DisguisedPtr<objc_object> disguised{(objc_object *)object};
//将policy和 value封装成ObjcAssociation目的是方便底层统一处理
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
// 根据policy策略去判断是进去retain还是copy操作
association.acquireValue();
bool isFirstAssociation = false;
{ // 实例化 AssociationsManager 注意这里不是单例
AssociationsManager manager;
//实例化 全局的关联表 AssociationsHashMap 这里是单例
AssociationsHashMap &associations(manager.get());
// 如果value有值
if (value) {
//AssociationsHashMap:关联表 ObjectAssociationMap:对象关联表
//首先根据对象封装的disguised去关联表中查找有没有对象关联表
//如果有直接返回表,如果没有则根据`disguised`去创建对象关联表
//创建ObjectAssociationMap时当(对象的个数+1大于等于3/4,进行两倍扩容
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
//表示第一次关联该对象
isFirstAssociation = true;
}
/* establish or replace the association */
//获取ObjectAssociationMap中存储值的地址
auto &refs = refs_result.first->second;
//将需要存储的值存放在关联表中存储值的地址中
//同时会根据key去查找,如果查找到`result` = false ,如果找不到就创建result = true
//创建association类型当(association的个数+1)超过3/4,就会进行两倍扩容
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
//交换association和查询到的`association`进行交换
//其实可以理解为更新查询到的`association`数据,新值替换旧值
association.swap(result.first->second);
}
} else {
//value没有值走else流程
//查找disguised 对应的ObjectAssociationMap
auto refs_it = associations.find(disguised);
//如果找到对应的 ObjectAssociationMap 对象关联表
if (refs_it != associations.end()) {
//获取 refs_it->second 里面存放了association类型数据
auto &refs = refs_it->second;
//根据key查询对应的association
auto it = refs.find(key);
if (it != refs.end()) {
//如果找到更新旧的association里面的值
//旧的association会存放在association中
association.swap(it->second);
//value= nil时释放关联对象表中存的`association`
refs.erase(it);
//如果该对象关联表中所有的关联属性数据被清空,那么该对象关联表会被释放
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
//每一次第一个关联对象调用setHasAssociatedObjects
//通过setHasAssociatedObjects方法`标记对象存在关联对象`设置`isa指针`的`has_assoc`属性为`true`
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
//释放旧值因为如果有旧值会被交换到`association`中
//原来`association`的新值会存放到对象关联表中
association.releaseHeldValue();
}
_object_set_associative_reference
核心作用其实就两个
- 根据
object
在关联表中查询ObjectAssociationMap
如果没有就去开辟内存创建ObjectAssociationMap
,创建的规则就是在3/4
时,进行两倍扩容 - 将根据
key
查询到相关的association
就是关联的数据value
和policy
,如果查询到直接更新里面的数据,如果没有则去获取空的asociation
类型然后将值存放进去,如果ObjectAssociationMap
中是第一次去关联数据或者关联的数据的个数满足了3/4
时,进行两倍扩容 - 扩容的规则和
cache
方法存储的规则时一样的
AssociationsManager manager
可能大家有疑问为什么不是单例,而AssociationsHashMap &associations
是单例,探究下AssociationsManager
和AssociationsHashMap
结构
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
// 构造函数 加锁
AssociationsManager() { AssociationsManagerLock.lock(); }
// 析构函数 解锁
~AssociationsManager() { AssociationsManagerLock.unlock(); }
// 获取全局的一张AssociationsHashMap表
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
源码显示static Storage _mapStorage
,是全局的静态变量,只是在AssociationsManager
内部 可以使用,如果你把它放在外面一样可以使用,AssociationsManager
有一个构造函数和一个析构函数。AssociationsManager manager
就相当于调用了构造函数加锁的功能,AssociationsManager
的作用域结束会自动调用析构函数进行解锁功能。AssociationsManager
就是为了防止多线程访问出现混乱,AssociationsHashMap
就是全局的静态变量获取的只调用一次
验证下AssociationsManager
是否为单例
AssociationsManager
是class
类型,此时的mmanager
还没有初始化赋值,但是manager
是AssociationsManager
类型。&manager
变成是AssociationsManager*
类型既指针类型,
&manager
存放的就是AssociationsManager
的对象类型,&manager
、&manager1
和&manager2
不同的地址说明实例化后的地址不是同的,不是同一个对象
验证下AssociationsHashMap &associations
是否为单例
很明显AssociationsHashMap &associations
是单例,只有单例才能值开辟一次内存只有一个地址,其它指针存的都是这一块地址
try_emplace
方法
下面用断点调试跟流程的方式进行探究
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
//根据key去查找对应的Bucket
if (LookupBucketFor(Key, TheBucket))
//通过make_pair生成相应的键值对
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); //false 表示往哈希关联表已经存在bucket
//如果没有查询到 将数据插入bucket中,返回bucket
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
// 通过 make_pair生成相应的键值对
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);//true表示第一次往哈希关联表中添加bucket
}
- 首先去根据
key
去查找有没有对应的bucket
,如果有将bucket
进行封装返 - 如果没有将去找空的
bucket
,如果空的bucket也没有就会去创建bucket
,然后将数据存在bucket
中Key
就是系统对object
进行封装的结构,Args
是传过来空的ObjectAssociationMap{}
对象关联表
LookupBucketFor
探究
进入LookupBucketFor
方法发现有两个相同的方法,源码如下
这俩两个方法的区别就是第二个参数一个带const
修饰一个不带,很明显是第二个调用第一个方法。而且第二个参数值是指针类型,也就是常说的指针传递。
- 首先根据key获取到的
bucket
地址存放在ConstFoundBucket
指针中,然后将ConstFoundBucket
指针的地址赋值给&FoundBucket
指针中,这样FoundBucket
存放的数据就会实时更新 - 如果查询到就返回
true
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
//获取buckets的首地址
const BucketT *BucketsPtr = getBuckets();
//获取当前buckets的个数
const unsigned NumBuckets = getNumBuckets();
// 如果NumBuckets = 0 返回 false
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();// 获取肯空bucket的key
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
//hash 获取下标和cache中的很像
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
// 进行while循环
while (true) {
//根据下标找到对应的`bucket`内存偏移 ThisBucket = 首地址 + 第几个
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
// 如果查询到`bucket`的`key`和`Val`相等 返回当前的bucket说明查询到了
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
//没有查询到获取一个空的bucket的 目的是可以向空的bucket插入数据
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
// If this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
// BucketNo ++
BucketNo += ProbeAmt++;
//在hash获取下标
BucketNo &= (NumBuckets-1);
}
}
在 LookupBucketFor
下断点进行调试源码如下
因为是第一进行关联对象还没有创建关联对象表所以地址是空的,也没有bucket
,所以返回false
FoundBucket
= nil
在try_emplace
方法中的InsertIntoBucket
下断点,查看写TheBucket
是否为nil
TheBucket
确实为nil
,下面进入插入流程
InsertIntoBucket
Btemplate <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
//获取空的`bucket`
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
//将key插入TheBucket中的key为first中
TheBucket->getFirst() = std::forward<KeyArg>(Key);
//如果的将value值插入TheBucket中的key为second中
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
核心功能获取空的bucket
然后进行键值的匹配存储将key
和value
插入到bucket
中
InsertIntoBucketImpl
template <typename LookupKeyT>
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
// NewNumEntries 表示将要插入一个 bucket
unsigned NewNumEntries = getNumEntries() + 1;
//获取bucket总个数
unsigned NumBuckets = getNumBuckets();
//如果当前要插入的个数 大于等于总个数的3/4 进行两倍扩容
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
this->grow(NumBuckets * 2);//进行两倍扩容,但是如果NumBuckets = 0 默认是开辟4个buckeet
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
//这种情况是负载小于1/8,这种情况比较复杂暂不讨论 很少会出现
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
//
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
// 当前bucket被占用的数量 + 1
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
incrementNumEntries();
decrementNumTombstones();
} else {
// we should be purging a zero. No accounting changes.
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
核心功能就是开辟内存然后获取空的bucket
,开辟内存是this->grow(NumBuckets)
,获取空的bucket
是通过LookupBucketFor
,在this->grow(NumBuckets)
下断点
进入两倍扩容流程grow(NumBuckets * 2)
,跟断点流程如下进入grow
方法
void grow(unsigned AtLeast) {
static_cast<DerivedT *>(this)->grow(AtLeast);
}
断点进入grow(unsigned AtLeast)
方法
#define MIN_BUCKETS 4
void grow(unsigned AtLeast) {
unsigned OldNumBuckets = NumBuckets;
BucketT *OldBuckets = Buckets;
// MIN_BUCKETS = 4
allocateBuckets(std::max<unsigned>(MIN_BUCKETS, static_cast<unsigned>(NextPowerOf2(AtLeast-1))));
ASSERT(Buckets);
if (!OldBuckets) {
this->BaseT::initEmpty();
return;
}
this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets);
// Free the old table.
operator delete(OldBuckets);
}
allocateBuckets
方法就是根据bucket
的类型和Num
开辟的个数去开辟内存空间bucket
的个数是MIN_BUCKETS
和NextPowerOf2(AtLeast-1))
取最大值。MIN_BUCKETS
是一个宏值为4
BaseT::initEmpty()
初始化空的bucket
具体类型根据T
的类型注意空的桶子key
为first
对应的值是1
moveFromOldBuckets
将旧的buckets
移动到新的buckets
中,这点和cache
扩容是不一样- 释放旧的
buckets
断点进入NextPowerOf2
方法
// 32位
/// NextPowerOf2 - 返回 2 的下一个幂(32 位)
/// 严格大于 A。溢出时返回零。
inline uint32_t NextPowerOf2(uint32_t A) {
A |= (A >> 1);
A |= (A >> 2);
A |= (A >> 4);
A |= (A >> 8);
A |= (A >> 16);
return A + 1;
}
// 64位
/// NextPowerOf2 - 返回 2 的下一个幂(64 位)
/// 严格大于 A。溢出时返回零。
inline uint64_t NextPowerOf2(uint64_t A) {
A |= (A >> 1);
A |= (A >> 2);
A |= (A >> 4);
A |= (A >> 8);
A |= (A >> 16);
A |= (A >> 32);
return A + 1;
}
在macOS
中断点进入的是uint32_t NextPowerOf2
方法,严格大于A
,超过的话就返回0
。且参数A
是无符号整型。目前传来的参数是-1
,-1
转换成二进制的规则是:1的原码=0x01,取反=0xfe,再加1=0xff
,如果是32
位,-1
的二进制是0xffffffff
。如果是64
位-1
的二进制是0xffffffffffffffff
,在进行A |
= (A >> n)
,其实就是等于其本身,然后A+1
溢出等于0
,当然你也可以理解为-1
+ 1
= 0
,因为是两倍扩容,而二进制之间也是2
倍,所以只要不超过范围就是等于传进来的值
断点进入initEmpty
void initEmpty() {
setNumEntries(0);
setNumTombstones(0);
ASSERT((getNumBuckets() & (getNumBuckets()-1)) == 0 &&
"# initial buckets must be a power of two!");
//设置空的key
const KeyT EmptyKey = getEmptyKey();
for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B)
::new (&B->getFirst()) KeyT(EmptyKey);
}
断点进入getEmptyKey
,getEmptyKey
方法很多最后进入的方法如下
static inline DisguisedPtr<T> getEmptyKey() {
return DisguisedPtr<T>((T*)(uintptr_t)-1);
}
DisguisedPtr
大家应该很熟悉,object
也是封装成Disguis类型edPtr
template** <typename T>
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
...
}
ptr
= (uintptr_t)-1
,再次经过-(uintptr_t)ptr
计算。就等于-(uintptr_t)((uintptr_t)-1)
= 1
所以最后的value
= 1
,也就意味着空的bucket
中key
为first
的值是value
= 1
扩容完成以后开始调用LookupBucketFor(Lookup, TheBucket)
。Lookup
就是封装好的object
,作为参数传进来的
调用LookupBucketFor
返回的是一个空的bucket
。就是扩容以后创建的buckets
中的一个.此时bucket
的总个数是4
个
断点继续调试会进入incrementNumEntries()
方法
incrementNumEntries()
方法就是获取当前的有数据buckets
的个数,之前是0
,调用以后变成1
void incrementNumEntries() {
// getNumEntries() 是原来有数据的个数
// getNumEntries() +1 表示将要插入的
// 设置NumEntries的个数
setNumEntries(getNumEntries() + 1);
}
源码显示将原有的NumEntries
的数量加1
。incrementNumEntries()
执行完以后返回bucket
TheBucket
的first
赋值
//获取键值里面的first键
KeyT &getFirst() { return std::pair<KeyT, ValueT>::first; }
TheBucket->getFirst()
= std::forward<KeyArg>(Key)
把Key
赋值给TheBucket
中的first
将Key
赋值给TheBucket
的first
TheBucket
的second
赋值
TheBucket
的second
默认可能有脏数据,将value
赋值给second
赋值成功后返回通过make_pair
封装好的bucket
isFirstAssociation
第一次关联
secound
= true
是make_pair
方法中的第二个参数为true
赋值的,意思就是第一次关联该对象
获取ObjectAssociationMap
根据disguised
通过refs_result.first->second
获取AssociationsHashMap
表中的ObjectAssociationMap
插入association
的数据或者是更新ObjectAssociationMap
中的已有的数据
result.second
= ture
表示在ObjectAssociationMap
中没有该数据是第一次存储,如果result.second
= false
表示ObjectAssociationMap
中有该数据然后更细数据。此时存储的第一个属性是LWPerson
类中的lw_name
,存储的值是luwenhan
ObjectAssociationMap
的结构和cache
结构是比较相似,不仅可以存储值,还可以有其它的变量配合使用
设置isa
中是关联对象属性
...
if (isFirstAssociation)
object->setHasAssociatedObjects()
...
isFirstAssociation
第一次给关联对象设置值的时候才会调用setHasAssociatedObjects
方法
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
...
isa_t newisa, oldisa = LoadExclusive(&isa.bits);
do {
newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
//将isa中的has_assoc 设置为true
newisa.has_assoc = true;
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
通过setHasAssociatedObjects
方法设置对象存在关联对象,即isa指针
的has_assoc
属性设置为true
最后通过releaseHeldValue
方法释放旧值
关联对象存储流程图
关联对象 - get
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
objc_getAssociatedObject
调用了_object_get_associative_reference
。进入_object_get_associative_reference
方法,关联对象取值就是比较简单的了就是查表
id
_object_get_associative_reference(id object, const void *key)
{
//创建空的关联对象
ObjcAssociation association{};
{ //创建一个管理类
AssociationsManager manager;
//获取全局唯一的HashMap表
AssociationsHashMap &associations(manager.get());
// iterator是个迭代器实际上相当于找到objc_object和对应的ObjectAssociationMap
// 类似{first = objc_object,second =ObjectAssociationMap}结构
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
//如果这个迭代器封装的一个结构不是最后一个进入判断流程
if (i != associations.end()) {
//获取 ObjectAssociationMap
ObjectAssociationMap &refs = i->second;
//获取 ObjectAssociationMap 的迭代器
ObjectAssociationMap::iterator j = refs.find(key);
//如果这个迭代器封装的一个结构不是最后一个进入判断流程
if (j != refs.end()) {
//获取 association
association = j->second;
//retain 新值
association.retainReturnedValue();
}
}
}
//release 旧值,返回新值
return association.autoreleaseReturnedValue();
}
注释已经写的很清楚,就不在赘述一遍,直接调试查看最后的结果
关联对象其实就是一个查表取值的过程,然后返回存储的值。相比较关联对象存值,取值流程也更加容易理解
总结
关联对象的底层探究大家可能觉着不常用,没必要探究,其实探究过程你对苹果底层的封装思想以及哈希表都会有一个深刻的理解