前言
前面几篇文章主要分析了类的加载过程,那么这篇文章主要分析分类中属性的存储,也就是大家常说的关联对象
,以及类扩展
的探究。
准备工作
- Objc-818.2
类扩展
- 为某个类附加
额外
的属性
,成员变量
,方法声明
- 网上许多博客都写到类扩展是一种特殊的匿名分类,这种说法我是不认同,
分类
和类扩展
是有本质的区别
的 - 分类的底层是category_t结构题类型,而类扩展底层是直接编译到主类中的
- 一般的类扩展写到
.m
文件中,一般的私有属性
或者想要独立区别的属性
写到类扩展
类扩展格式
.m
文件中的格式
@interface XJLTeacher()
{
NSInteger height; //成员变量
}
@property(nonatomic,assign)NSInteger lw_age;//属性
-(void)helloWord;//对象方法
+(void)helloClass;//类方法
@end
这种声明在.m文
件的类扩展
,基本上每天都在用,但是我们确很少留意到这就是类扩展
, 注意的是类扩展必须写在类的声明和类的实现直接。
注意:类的声明是在.h
文件中的,在编译时.h
文件会被展开放在.m
文件中。
Extension
格式
#import "XJLTeacher.h"
@interface XJLTeacher ()
@property(nonatomic,assign)int age;
@end
创建类扩展时发现只有一个.h
文件,没有与之对应的.m
。类扩展的所有的实现都是在一个.m
文件中实现的,其实Extension
就是相当于把.m
文件中类扩展的方式写到一个单独的头文件
中,其实没有区别
。
类扩展底层探究
把XJLTeacher
写在main.m
目的是为了更好的探究.cpp
文件,代码如下:
@interface XJLTeacher : NSObject
- (void)sayHello;
@end
@interface XJLTeacher()
{
NSInteger height;
}
@property(nonatomic,assign)NSInteger lw_age;
-(void)helloWord;
+(void)helloClass;
@end
@implementation XJLTeacher
+(void)load{
}
- (void)sayHello{
}
- (void)helloWord{
}
+(void)helloClass{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XJLTeacher * teacher = [[XJLTeacher alloc] init];
[teacher sayHello];
}
return 0;
}
通过xcrun
命令行将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
分类中创建定义1
个属性。在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 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, 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
类型,此时的manager
还没有初始化赋值
,但是manager
是AssociationsManager
类型。&manager
变成是AssociationsManager*
类型既指针
类型,
&manager
存放的就是AssociationsManager
的对象类型,&manager
、&manager1
和&manager2
不同的地址说明实例化后的地址不是同的,不是同一个对象
。
验证AssociationsHashMap &associations
是否为单例
很明显
AssociationsHashMap &associations
是单例,只有单例
才能值开辟一次
内存只有一个
地址,其它指针存的都是这一块地址。
try_emplace 方法
使用断点调试
的方法一步步走下去:
template
std::pair 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(Args)...);
// 通过 make_pair生成相应的键值对
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);//true表示第一次往哈希关联表中添加bucket
}
- 首先去根据
key
去查找有没有对应的bucket
,如果有
将bucket进行封装返回
- 如果
没有
将去找空的bucket
,如果空的bucke
t也没有就会去创建bucket
,然后将数据存在bucket
中
注意:Key
就是系统对object
进行封装
的结构,Args
是传过来空
的 ObjectAssociationMap{}
对象关联表
。
LookupBucketFor探究
进入LookupBucketFor
方法发现有两个相同的方法,源码如下:
这两个方法的区别就是第二个参数一个带
const
修饰一个不带,很明显是第二个调用
第一个方法。而且第二个参数值是指针类型
,也就是常说的指针传递
。
- 首先根据
key
获取到的bucket
地址存放在ConstFoundBucket指针
中,然后将ConstFoundBucket指针
的地址赋值给&FoundBucket
指针中,这样FoundBucket
存放的数据就会实时更新
- 如果查询到就返回
true
template
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
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
//获取空的`bucket`
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
//将key插入TheBucket中的key为first中
TheBucket->getFirst() = std::forward(Key);
//如果的将value值插入TheBucket中的key为second中
::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);
return TheBucket;
}
核心功能获取空的bucket
然后进行键值的匹配
存储将key
和value
插入到bucket
中。
InsertIntoBucketImpl
template
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
是通过LookupBucketFo
r,在this->grow(NumBuckets)
下断点:
进入
两倍扩容
流程grow(NumBuckets * 2)
,跟断点流程如下进入grow
方法:
void grow(unsigned AtLeast) {
static_cast(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(MIN_BUCKETS, static_cast(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 getEmptyKey() {
return DisguisedPtr((T*)(uintptr_t)-1);
}
DisguisedPtr
我们应该很熟悉,object
也是封装成DisguisedPtr
类型
template**
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::first; }
TheBucket->getFirst() = std::forward
把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
中有该数据然后更新数据。此时存储的第一个属性是XJLPerson
类中的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();
}
直接调试查看最后的结果:
关联对象取值
其实就是一个查表取值
的过程,然后返回存储的值
。相比较关联对象存值
,取值
流程也更加容易理解。
总结
这篇文章只要是理解查表的存取值过程,从而更深入理解关联对象的底层原理,需要花好多时间区分析源码,可能说关联对象的底层我们极少用到,但是对探究过程你对苹果底层的封装思想以及哈希表都会有一个深刻的理解,大家再接再厉把!!