前言
前文iOS底层原理之OC类的加载原理(下)已经分析了分类加载的流程,以及主类和分类搭配加载的情况。本文就来分析下特殊的分类--类扩展
和分类属性的存取--关联对象
。
准备工作
- objc4-818.2源码。
一: list_array_tt
数据结构分析
开始今天主要内容之前,先补充一点小内容。
根据前文的分析,我们知道方法列表在底层的数据结构是method_array_t
,下面就来探究下这个数据结构以及lldb
调试时使用的函数的由来。
method_array_t
继承自模板类list_array_tt
。
// Element = method_t, List = method_list_t, Ptr = method_list_t_authed_ptr
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
struct array_t {
uint32_t count;
Ptr<List> lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
size_t byteSize() {
return byteSize(count);
}
};
...
public:
union { // 互斥
Ptr<List> list;
uintptr_t arrayAndFlag;
};
bool hasArray() const {
return arrayAndFlag & 1;
}
array_t *array() const {
return (array_t *)(arrayAndFlag & ~1);
}
void setArray(array_t *array) {
arrayAndFlag = (uintptr_t)array | 1;
}
...
uint32_t count() const {
uint32_t result = 0;
for (auto lists = beginLists(), end = endLists();
lists != end;
++lists)
{
result += (*lists)->count;
}
return result;
}
...
const Ptr<List>* beginLists() const {
if (hasArray()) {
return array()->lists;
} else {
return &list;
}
}
const Ptr<List>* endLists() const {
if (hasArray()) {
return array()->lists + array()->count;
} else if (list) {
return &list + 1;
} else {
return &list;
}
}
// attachLists函数也在这里,此处省略,感兴趣的朋友可以看上一篇文章
void attachLists(List* const * addedLists, uint32_t addedCount) {
...
}
-
list_array_tt
类通过传入模板可以生成method_array_t
,property_array_t
,protocol_array_t
等。
根据传入的模板Element = method_t, List = method_list_t, Ptr = method_list_t_authed_ptr
,先来查看method_list_t
。
method_list_t
继承自entsize_list_tt
,根据传入模板不同可以生成method_list_t
,property_list_t
,protocol_list_t
等。
- 根据
get(i)
函数获取i
位置的method_t *
。
method_t::pointer_modifier
的实现。
struct method_t {
...
struct pointer_modifier {
template <typename ListType>
static method_t *modify(const ListType &list, method_t *ptr) {
if (list.flags() & smallMethodListFlag)
return (method_t *)((uintptr_t)ptr | 1);
return ptr;
}
};
...
}
PointerModifierNop
的实现。
struct PointerModifierNop {
template <typename ListType, typename T>
static T *modify(__unused const ListType &list, T *ptr) { return ptr; }
};
再来看看method_t
的数据结构。
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
private:
// arm64架构(包括M1版iMac),小端模式
bool isSmall() const {
return ((uintptr_t)this & 1) == 1;
}
// small里面存的是三个相对偏移值
struct small {
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP> imp;
bool inSharedCache() const {
return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
objc::inSharedCache((uintptr_t)this));
}
};
// 获取small信息
small &small() const {
ASSERT(isSmall());
return *(struct small *)((uintptr_t)this & ~(uintptr_t)1);
}
...
// 获取big信息
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
SEL name() const {
if (isSmall()) {
return (small().inSharedCache()
? (SEL)small().name.get()
: *(SEL *)small().name.get());
} else {
return big().name;
}
}
...
}
-
method_t
里面有我们经常使用的big()
,small()
,name()
等函数。
到这里list_array_tt
的数据结构和我们lldb
调试时使用的函数由来就介绍完了。
二: 类扩展
1.1: category
VS extension
category
(分类/类别)
- 专门用来给类添加新的方法。
- 不能给类添加成员变量,即使添加了成员变量,也无法取到。
- 可以通过
runtime
给分类添加属性。 - 分类中
@property
定义的属性,只会生成属性的getter, setter
方法的声明,不会生成方法实现和带下划线的成员变量。
extension
(类扩展)
- 可以说成是特殊的分类,也称作匿名分类。
- 可以给类添加成员变量和属性,但是是私有的。
- 可以给类添加方法,也是私有方法。
1.2: extension
格式
1.2.1: .m
文件中
对于我们iOS
开发者来说,最常见的就是这种定义在.m
文件中的extension
了:
⚠️
extension
必须写在类的声明之后,实现之前。.h
文件中的声明部分在编译时也会被展开放到.m
文件中。
1.2.2: 单独创建文件
当然也可以单独创建extension
文件:
这种方式就只会生成.h
文件,因为类扩展的实现也是需要在类的.m
文件中的,而且这个.h
文件必须导入类的.m
文件,不然的话类无法访问extension
定义的属性和成员变量(也不会生成属性的getter
和setter
),编译的时候这个extension
也不会合并到类的ro
中(方法因为实现就在类里面,就算不导入也不影响)。
这种方式其实只是把.m
文件里的extension
写成了一个单独的头文件而已。
1.3: extension
底层原理分析
在main.m
创建XJBoy
类,添加extension
。
@interface XJBoy : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface XJBoy ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation XJBoy
- (void)instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)classMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XJPerson * person = [XJPerson alloc];
[person saySomething];
}
return 0;
}
通过clang
用main.m
生成main.cpp
文件探索。
XJBoy
类实现部分:
XJBoy
成员变量、属性、方法列表:
可以看出extension
的数据在编译时就是存储在类中的。
给XJBoy
类添加+load
方法,并在realizeClassWithoutSwift
函数添加断点调试:
从调试结果可以看出extension
的数据也是在ro
中的,而ro
在编译时就已确定。
Category
会影响类的加载,因为它有自己的.m
文件,可以实现自己的+load
方法。Extension
不会影响类的加载,因为它没有自己的.m
文件,不管有几个Extension
,所有的实现都是在类的.m
文件中的。
三: 关联对象
分类添加属性后,会报相应的警告:
- 提示开发者自己实现属性的
getter & setter
方法。
这是因为分类用@property
声明的属性,只会生成getter & setter
方法的声明,不会生成方法的实现和带下划线(_
)的成员变量。而getter & setter
方法是通过成员变量的偏移值,对成员变量进行存值和取值的,连成员变量都没有,所以也就没有getter & setter
方法的实现。
这种情况下就需要通过关联对象
来给分类属性添加getter & setter
方法了。
- (void)setXja_name:(NSString *)xja_name
{
objc_setAssociatedObject(self, "xja_name", xja_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)xja_name
{
return objc_getAssociatedObject(self, "xja_name");
}
3.1: 关联对象setter
3.1.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
函数。
objc4-781
及之前的版本中是通过SetAssocHook.get()
函数获取到_base_objc_setAssociatedObject
函数,然后再调用_object_set_associative_reference
函数。
3.1.2: _object_set_associative_reference
// 关联对象 存储 self xja_name value OBJC_ASSOCIATION_COPY_NONATOMIC
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));
// 包装 object 成统一类型 DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 包装 {policy, value} 为 ObjcAssociation
ObjcAssociation association{policy, value};
// 根据 policy 对 value 进行操作
// retain 或 copy value
association.acquireValue();
bool isFirstAssociation = false;
{
// manager 不是单例,调用构造函数创建 manager,内部进行了加锁
AssociationsManager manager;
// AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap
// 它是在`map_images`的时候初始化。
AssociationsHashMap &associations(manager.get());
if (value) {//有值
// 在全局关联对象哈希表查找或创建插入object对应的关联对象哈希表迭代器
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {// 第一次,也就是插入 bucket 的时候 second 为 true
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
// 获取`object`的`关联对象`哈希表
auto &refs = refs_result.first->second;
// 这时候的key就是成员变量的key,将 association 插入object的表中
// 有值的情况下没有插入,没有值的情况下才插入
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {// second为false 证明 LookupBucketFor 找到了,有值
// result.first->second为表中的association
// 将表中替换为新的。association 变为旧值
association.swap(result.first->second);
}
} else {// 没有值,进行清空处理。
// 找到object的关联对象哈希表的迭代器
auto refs_it = associations.find(disguised);
// 不是 end 标记
if (refs_it != associations.end()) {
// 获取object的关联对象哈希表
auto &refs = refs_it->second;
// 找到对应key的 association 的迭代器
auto it = refs.find(key);
// 不是 end 标记
if (it != refs.end()) {
// it->second为表中的association
// 将表中value修改为nil,association修改为之前的值
association.swap(it->second);
// 擦除对应key的 association 的迭代器
refs.erase(it);
if (refs.size() == 0) {
// 如果`object`对应的关联对象哈希表空了,就擦除此表
associations.erase(refs_it);
}
}
}
}
// 出了作用域,会调用AssociationsManager的析构函数
// 进行解锁
}
// 只在第一次标记对象的isa是否有关联对象
if (isFirstAssociation)
object->setHasAssociatedObjects();
// 释放`association`中交换得来的旧值
association.releaseHeldValue();
}
对象和值都为空就直接返回。
对象的类是否禁止关联对象。
将对象统一包装成
DisguisedPtr
类型。将
policy
和value
统一包装成ObjcAssociation
,并根据policy
对值进行retain
或copy
操作。AssociationsManager manager
调用AssociationsManager
类的构造函数创建manager
,构造函数和析构函数内部只是进行了加锁和解锁的处理。manager
不是单例(后文具体分析)。AssociationsHashMap &associations(manager.get())
通过manager
获取AssociationsHashMap
类的全局关联对象总哈希表associations
。此表是一张单例表,在map_images
流程初始化(后文具体分析)。-
value
有值的情况下:- 调用
associations.try_emplace
函数在全局关联对象总哈希表查找或创建插入object
对应的关联对象哈希表的迭代器,返回迭代器与是否第一次添加关联对象组成的pair
,即refs_result
。 - 如果当前
object
的关联对象哈希表第一次插入,refs_result.second
就为true
,将isFirstAssociation
标记为true
。 -
refs_result.first->second
获取object
的关联对象哈希表refs
。 - 调用
refs.try_emplace
将policy
和value
包装成的association
插入当前object
的关联对象哈希表,返回当前object
的关联对象哈希表的迭代器和是否已经存在组成的pair
,即result
。 - 如果之前已经存在,
result.second
就为false
,通过result.first->second
获取之前的ObjcAssociation
类对象,调用association.swap
,将policy
和value
的新值存储进去,旧值交换出来,为后续进行释放做准备。
- 调用
-
value
为空的情况下:- 调用
associations.find
函数在全局关联对象哈希表中查找object
对应的关联对象哈希表的迭代器refs_it
。 - 返回的迭代器不为
end
标记,则通过refs_it->second
获取object
对应的关联对象哈希表refs
。 - 调用
refs.find
在object
对应的关联对象哈希表中查找key
对应的ObjcAssociation
类的迭代器it
。 - 返回的迭代器不为
end
标记,通过it->second
获取之前的ObjcAssociation
类对象,调用association.swap
,将{policy, nil}存进去,旧值交换保存到association
中,为后续进行释放做准备。 - 调用
refs.erase
擦除object
对应的关联对象哈希表中key
对应的ObjcAssociation
类的迭代器。 - 如果
object
对应的关联对象哈希表空了,就擦除此表。
- 调用
根据
isFirstAssociation
,设置object
的isa
是否有关联对象。释放
association
中交换得来的旧值。
3.1.3: lldb
查看disguised
和association
3.1.4: acquireValue
inline void acquireValue() {
if (_value) {
// 0xFF = 0B 1111 1111
switch (_policy & 0xFF) {
// 0B 0000 0001 & 0B 1111 1111 = 0B 0000 0001
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
// 0B 0000 0011 & 0B 1111 1111 = 0B 0000 0011
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
- 根据
policy
对值进行retain
或copy
操作。
3.1.5: AssociationsManager
与AssociationsHashMap
// 对象的关联对象哈希表类型
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
// 关联对象总哈希表类型,两层哈希表结构,里面为 object, ObjectAssociationMap
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
// 关联对象总哈希表
// 静态变量,声明在AssociationsManager中,只能AssociationsManager类的实例调用
// 相当于单例
static Storage _mapStorage;
public:
// 构造函数,加锁
AssociationsManager() { AssociationsManagerLock.lock(); }
// 析构函数,解锁
~AssociationsManager() { AssociationsManagerLock.unlock(); }
// 获取关联对象总哈希表,全局单例
// _mapStorage是静态变量,相当于单例
AssociationsHashMap &get() {
return _mapStorage.get();
}
// static表示类方法,在map_images流程调用
static void init() {
_mapStorage.init();
}
};
AssociationsManager::Storage AssociationsManager::_mapStorage;
-
_mapStorage
是一个静态变量,声明在AssociationsManager
类中,只能AssociationsManager
类的实例调用,相当于单例。 -
AssociationsManager
构造函数和析构函数进行加解锁操作。 -
AssociationsHashMap
类的关联对象总哈希表,通过AssociationsManager
类的get
方法获取。 - 关联对象总哈希表是在
AssociationsManager
类的类方法init
中初始化的,init
在map_images
流程调用。
3.1.5.1: AssociationsManager
构造、析构仿照验证
- 出了作用域就调用了析构函数。
想要验证
c++
的构造和析构函数,需要修改.m
文件为.mm
文件或者修改文件的Type
为Objc++
:
3.1.5.2: AssociationsHashMap
初始化流程
在AssociationsManager
类的init
函数加上断点,查看函数调用栈:
关联对象总哈希表初始化流程:
-
map_images -> map_images_nolock -> arr_init -> _objc_associations_init -> AssociationsManager::init() -> _mapStorage.init()
。
map_images
:
map_images_nolock
:
arr_init
:
_objc_associations_init
:
AssociationsManager::init()
:
3.1.5.3: AssociationsHashMap
单例验证
实例化多个实例,lldb
输出地址验证。
-
AssociationsManager
不是单例,AssociationsHashMap
才是单例(全局关联对象总哈希表,所有对象的关联对象哈希表都存在里面)。
验证需要暂时注释
AssociationsManager
类构造和析构函数里的加解锁操作,否则会造成死锁:
3.1.6: try_emplace
// 外层哈希表 Key = disguised(object) Args = ObjectAssociationMap{}
// 内存哈希表 key = key Args = association
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
// 根据key去查找对应的Bucket
// TheBucket指针传递,会随着里面的值改变而改变
if (LookupBucketFor(Key, TheBucket)) // 查询到,已经存在
// 通过make_pair生成相应的pair
// second 为 false
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// 如果没有查询到 将数据插入bucket中,返回bucket
// TheBucket的first为key,second为Args
// 所以外面可以通过 xxx.first->second 来获取 Args
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
// 通过make_pair生成相应的pair
// second 为 true
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
-
try_emplace
在value
有值的情况下会调用两次(关联对象总哈希表是双层哈希表结构):- 第一次是全局关联对象总哈希表调用,
key
是object
包装成的DisguisedPtr
类对象,Args
是DisguisedPtr
类对象对应的关联对象哈希表。 - 第二次是
DisguisedPtr
类对象对应的关联对象哈希表调用,key
是关联对象的key
,Args
是{policy, value}包装成的ObjcAssociation
类对象。
- 第一次是全局关联对象总哈希表调用,
- 调用
LookupBucketFor
查找key
对应的TheBucket
,第一次为对象对应的关联对象哈希表,第二次为关联对象key
对应的ObjcAssociation
类对象(都进行了相应的包装)。 - 找到则生成迭代器与
false
组队(make_pair
)返回。 - 没找到就调用
InsertIntoBucket
将key & Args
插入TheBucket
,并生成迭代器与true
组队返回(有旧值的情况下,不替换直接返回,以便后面交换之后释放旧值)。
全局关联对象总哈希表调用try_emplace
的lldb
调试(第一次):
object
包装成的DisguisedPtr
类对象对应的关联对象哈希表调用try_emplace
的lldb
调试(第二次):
3.1.6.1: LookupBucketFor
LookupBucketFor
函数在源码里有两个,BucketT
参数不同,一个有const
修饰,一个没有。try_emplace
传入的BucketT
类型的TheBucket
是没有const
修饰的,所以我们先查看它:
很明显BucketT
类型不带const
的函数调用了带const
的函数,BucketT
类型参数同样是指针传递。
- 根据
key
获取bucket
地址存到ConstFoundBucket
指针中。 - 将
ConstFoundBucket
指针的地址赋值给&FoundBucket
指针中,这样FoundBucket
存放的数据就会实时更新。 - 查询到
Result
返回true
。
接下来查看BucketT
类型参数带const
的LookupBucketFor
:
template<typename LookupKeyT>
// BucketT 被 const 修饰了
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
// 获取buckets的首地址
const BucketT *BucketsPtr = getBuckets();
// 获取buckets数量
const unsigned NumBuckets = getNumBuckets();
// 如果buckets数量为0,返回false,FoundBucket = nullptr
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
// 获取空bucket的key
const KeyT EmptyKey = getEmptyKey();
// 获取墓碑key
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
// 计算哈希下标
// key的哈希值 & (容量 - 1 ),类似于cache的mask,也就是找到index
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
//与cache插入类似
while (true) {
// 内存平移,ThisBucket = 首地址 + 第几个
// 根据下标找到对应的 bucket
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
// LLVM_LIKELY 就是 fastpath
// 找到了bucket,也就是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 += ProbeAmt++;
//再哈希,计算下标
BucketNo &= (NumBuckets-1);
}
}
- 此处逻辑与方法缓存的
cache
很像,首先key的哈希值 & (容量 - 1 )
计算哈希下标。 - 然后根据哈希下标查找
bucket
,有值就回传bucket
指针,并返回true
,没有就回传空的bucket
或墓碑标记
,并返回false
。 - 哈希冲突就再哈希,重新计算哈希下标。
lldb
调试验证:
第一次查找因为没有数据,设置FoundBucket = nullptr
,并返回false
,然后走InsertIntoBucket
流程。
第二次,在初次开辟空间或者扩容之后,获取空的bucket
返回准备插入数据:
3.1.6.2: InsertIntoBucket
没找到相应的bucket
,就会进入插入流程。
Btemplate <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
// 获取空的`bucket`
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
// TheBucket的first为Key,second为Values
// 所以外面可以通过 xxx.first->second 来获取 Values
// values 第一次为对象对应的关联对象哈希表,
// 第二次为{policy, value}包装成的`ObjcAssociation`类对象
TheBucket->getFirst() = std::forward<KeyArg>(Key);
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
- 获取空的
bucket
,容量不够的话就进行扩容。 - 设置
bucket
对应的值,first
为Key
,second
为Values
。- 第一次
Key
是object
包装成的DisguisedPtr
类对象,Values
是DisguisedPtr
类对象对应的关联对象哈希表。 - 第二次
Key
是关联对象的key
,Values
是{policy, value}包装成的ObjcAssociation
类对象。
- 第一次
lldb
调试验证:
first
赋值:
second
赋值:
3.1.6.2.1: 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
// 查找,实际是获取一个空的bucket
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
// 加上墓碑标记的bucket达到7/8时,重新开辟,将非墓碑标记换到新的bucket中
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
// 找到一个empty bucket,被占用数量+1,返回这个bucket
// 当前bucket被占用的数量 + 1
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
// 找到一个墓碑标记的bucket,被占用数量+1,墓碑标记数量-1,返回这个bucket
incrementNumEntries();
decrementNumTombstones();
} else {
// we should be purging a zero. No accounting changes.
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
- 容量不够的情况下,调用
grow
函数进行开辟或扩容,首次开辟容量为4
,扩容分为两种情况:- 负载因子达到
3/4
时,进行两
倍扩容。 - 已被占用加上墓碑标记数量为
7/8
时,重新开辟空间,将非墓碑标记换到新的bucket
中。
- 负载因子达到
- 调用
LookupBucketFor
查找bucket
,获取到empty bucket
情况下,被占用数量+1
,返回这个bucket
,墓碑标记的bucket
情况下,被占用数量+1
,墓碑标记数量-1
,返回这个bucket
。
lldb
调试验证:
3.1.6.2.2: grow
空间不足的情况下,开辟空间和扩容会进入grow
函数。
首先进入DenseMapBase
类的grow
函数。
void grow(unsigned AtLeast) {
static_cast<DerivedT *>(this)->grow(AtLeast);
}
调试继续会进入DenseMap
类的grow
函数。
void grow(unsigned AtLeast) {
unsigned OldNumBuckets = NumBuckets;
BucketT *OldBuckets = Buckets;
// #define 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);
}
- 通过
max
函数取MIN_BUCKETS
和NextPowerOf2(AtLeast-1)
的最大值作为bucket
的个数。MIN_BUCKETS
为4
,首次开辟就是4
。 - 调用
allocateBuckets
函数开辟上一步计算出的个数的bucket
类型的内存空间。 -
OldBuckets
为空,就遍历将bucket
都初始化为空bucket
(empty bucket
的first
为1
),然后返回。 - 调用
moveFromOldBuckets
函数将旧的buckets
移动到新的buckets
中,这点和cache
扩容是不一样。 - 释放旧的
buckets
。
3.1.6.2.3: 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;
}
-
A
是无符号整型,首次传进来是-1
,-1
转换成1
的二进制的规则是:1
的原码是0b0000000000000001
,取反是0b1111111111111110
,再加1
是0b1111111111111111
(为了不那么长,都只以16
位表示),再进行A |= (A >> n)
运算,其实最后还是等于0b1111111111111111
,然后A+1
溢出等于0
。 - 第二次传进来的是
7
(因为第一次开辟为4
),二进制为0b0000000000000111
,进行A |= (A >> n)
运算,最好还是0b0000000000000111
,然后A+1
等于8
。
lldb
调试验证:
3.1.6.2.4: initEmpty
调试继续进入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
:
首先进入llvm-DenseMap.h
的getEmptyKey
:
static const KeyT getEmptyKey() {
static_assert(std::is_base_of<DenseMapBase, DerivedT>::value,
"Must pass the derived type to this template!");
return KeyInfoT::getEmptyKey();
}
继续调试进入llvm-DenseMapInfo.h
的struct DenseMapInfo
里的getEmptyKey
:
static inline DisguisedPtr<T> getEmptyKey() {
return DisguisedPtr<T>((T*)(uintptr_t)-1);
}
-
DisguisedPtr
大家应该很熟悉,object
也是封装成DisguisedPtr
类型。
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
,也就意味着空的bucket
的first
是1
。
3.1.6.2.5: moveFromOldBuckets
void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) {
initEmpty();
// Insert all the old elements.
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
for (BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B != E; ++B) {
if (ValueInfoT::isPurgeable(B->getSecond())) {
// Free the value.
B->getSecond().~ValueT();
} else if (!KeyInfoT::isEqual(B->getFirst(), EmptyKey) &&
!KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) {
// Insert the key/value into the new table.
BucketT *DestBucket;
bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket);
(void)FoundVal; // silence warning.
ASSERT(!FoundVal && "Key already in new map?");
// bucket的first 非EmptyKey && 非墓碑(TombstoneKey),就插入新的bucket
DestBucket->getFirst() = std::move(B->getFirst());
::new (&DestBucket->getSecond()) ValueT(std::move(B->getSecond()));
// 已占用数量+1
incrementNumEntries();
// Free the value.
B->getSecond().~ValueT();
}
B->getFirst().~KeyT();
}
}
将新
buckets
的first
都初始化为EmptyKey
。-
遍历
OldBuckets
中的OldBucket
:-
OldBucket
的second
可free
:- 直接
free
。
- 直接
-
不可
free
:-
OldBucket
的first
不为EmptyKey
,并且不为TombstoneKey
,就插入到新的buckets
中。 - 已占用数量
+1
。 -
free OldBucket
的second
。
-
-
free OldBucket
的first
。
3.1.7 setHasAssociatedObjects
associations.try_emplace(disguised, ObjectAssociationMap{})
全局关联对象总哈希表associations
调用try_emplace
函数首次给object
包装成的DisguisedPtr
类型对象添加对应的关联对象哈希表时,组队(make_pair
)返回了添加的对应的关联对象哈希表和true
。此时isFirstAssociation
就会被设置为true
。后面就会标记对象是否有关联对象。
...
if (isFirstAssociation)
object->setHasAssociatedObjects()
...
inline void
objc_object::setHasAssociatedObjects()
{
//Tagged Pointer 直接返回
if (isTaggedPointer()) return;
// 纯指针 && 有默认的 release,retain等方法 && 非future类 && 非元类
if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) {
// 获取_noteAssociatedObjects 方法
void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects));
// 不为消息转发,也就是找到了方法。
if ((IMP)setAssoc != _objc_msgForward) {
// 调用 _noteAssociatedObjects
(*setAssoc)((id)this, @selector(_noteAssociatedObjects));
}
}
// 设置新的isa
isa_t newisa, oldisa = LoadExclusive(&isa.bits);
do {
newisa = oldisa;
// 纯指针/已经有关联对象标记
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
// isa关联对象标记
newisa.has_assoc = true;
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
-
isa
纯指针调用_noteAssociatedObjects
函数,系统会判断是否实现了此函数。 -
isa
非纯指针将has_assoc
设置为true
。
_noteAssociatedObjects
函数系统并没有实现,应该是提供给我们实现的。但是以_
开头应该是私有方法。一般情况下我们是用不到的。
3.1.8: 关联对象设值流程
将
object
包装成DisguisedPtr
类对象。用策略和值生成
ObjcAssociation
类对象,并根据策略对ObjcAssociation
中的值进行处理(retain
或copy
)。根据
AssociationsManager
类拿到全局关联对象总哈希表AssociationsHashMap
。-
判断需要设置的关联对象的值释放存在:
- 4.1. 存在,走插入流程
- 获取
DisguisedPtr
对应的关联对象哈希表ObjectAssociationMap
,不存在就插入空的ObjectAssociationMap
,并标记对象首次添加关联对象(isFirstAssociation
)。 - 查询
ObjectAssociationMap
中是否有key
对应的ObjcAssociation
,没有就插入,有就返回,跟2
中的ObjcAssociation
交互数据,第6
步释放。
- 获取
- 4.2. 不存在,插入空值(插入空值,相当于清除)
- 获取
DisguisedPtr
对应的关联对象哈希表ObjectAssociationMap
。 - 获取
key
对应的ObjcAssociation
。 - 将空值交换进去(即清除),然后擦除
ObjcAssociation
。 - 如果
DisguisedPtr
对应的ObjectAssociationMap
空了,就擦除。
- 获取
- 4.1. 存在,走插入流程
根据
isFirstAssociation
设置对象是否存在关联对象。释放
4.1
交换出来的旧值。
3.2: 关联对象getter
关联对象取值调用的是objc_getAssociatedObject
函数。
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
- 直接调用了
_object_get_associative_reference
函数。
3.2.1: _object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
// 创建空的ObjcAssociation类型对象
ObjcAssociation association{};
{
// AssociationsManager manager调用构造函数加锁
AssociationsManager manager;
// 获取全局关联对象总哈希表
AssociationsHashMap &associations(manager.get());
// 获取对象对应的关联对象哈希表的迭代器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
// 不是 end 标记
if (i != associations.end()) {
// 获取对象对应的关联对象哈希表
ObjectAssociationMap &refs = i->second;
// 获取关联对象`key`对应的`ObjcAssociation`类对象的迭代器
ObjectAssociationMap::iterator j = refs.find(key);
// 不是 end 标记
if (j != refs.end()) {
// 获取 ObjcAssociation类型对象
association = j->second;
// 根据策略需要retain value
association.retainReturnedValue();
}
}
// 出了作用域,会调用AssociationsManager的析构函数
// 进行解锁
}
// 根据策略需要将value设为autorelease
return association.autoreleaseReturnedValue();
}
- 获取全局关联对象总哈希表
associations
。 - 包装
object
为DisguisedPtr
类型对象从associations
获取对象对应的关联对象哈希表的迭代器。 - 通过
key
获取关联对象key
对应的ObjcAssociation
类对象的迭代器。 - 从
ObjcAssociation
类型对象中获取value
返回。
3.2.2: find
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end();
}
- 调用
LookupBucketFor
函数查找TheBucket
,如果存在就返回相关迭代器,不存在就返回end
标记。
lldb
调试查看:
3.2.3: 关联对象取值流程
- 根据
AssociationsManager
类拿到全局关联对象总哈希表AssociationsHashMap
。 - 根据当前
object
封装的DisguisedPtr
在AssociationsHashMap
中获取对应的迭代器。 - 此迭代器不是
end
标记,取出对象对应的关联对象哈希表ObjectAssociationMap
。 - 根据
key
在ObjectAssociationMap
中获取对应的迭代器。 - 此迭代器不是
end
标记,取出对应的ObjcAssociation
({policy, value}
)。 - 根据策略需要处理
ObjcAssociation
中的value
之后返回。
3.3: 关联对象remove
苹果提供objc_removeAssociatedObjects
函数以供我们移除关联对象。
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false);
}
}
- 判断
object
是否有关联对象,如果有,就调用_object_remove_assocations
函数移除关联对象。
3.3.1: hasAssociatedObjects
判断对象是否有关联对象:
inline bool
objc_object::hasAssociatedObjects()
{
if (isTaggedPointer()) return true;
if (isa.nonpointer) return isa.has_assoc;
return true;
}
- 默认返回
true
。
3.3.2: _object_remove_assocations
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
// AssociationsManager manager 调用AssociationsManager构造函数,加锁
AssociationsManager manager;
// 获取全局关联对象总哈希表
AssociationsHashMap &associations(manager.get());
// 获取对象对应的关联对象哈希表的迭代器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
// 不是 end 标记
if (i != associations.end()) {
// 将对象对应的关联对象哈希表数据交互到refs
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) { // 对象非释放的情况下
// 遍历对象对应的关联对象哈希表中所有的`ObjcAssociation`类对象的迭代器
for (auto &ref: refs) {
// ref.second是ObjcAssociation类型对象{policy, value}
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// 重新将OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象插入
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
// 没有需要重新插入的,则擦除对象对应的关联对象哈希表的迭代器
associations.erase(i);
}
}
// Associations to be released after the normal ones.
SmallVector<ObjcAssociation *, 4> laterRefs;
// release everything (outside of the lock).
// 遍历对象对应的关联对象哈希表中所有的`ObjcAssociation`类对象的迭代器
for (auto &i: refs) {
// i.second是ObjcAssociation类型对象{policy, value}
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
if (deallocating)
//dealloc的时候,OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象,先放入laterRefs 稍后释放,否则不处理。
laterRefs.append(&i.second);
} else {
//释放非OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象
i.second.releaseHeldValue();
}
// 出了作用域,调用AssociationsManager析构函数,解锁
}
for (auto *later: laterRefs) {
// dealloc 的情况下释放OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象
later->releaseHeldValue();
}
}
- 根据第二个参数
deallocating
判断是不是dealloc
的时候调用的。 - 创建一个临时的
ObjectAssociationMap
对象refs
。 - 获取全局关联对象总哈希表
associations
。 - 获取对象对应的关联对象哈希表(
ObjectAssociationMap
)的迭代器I
。 - 将对象对应
ObjectAssociationMap
数据交互到refs
中,原来的i
置空。 - 非
deallocating
情况下(也就是自己调用),将系统的关联对象(通过policy & OBJC_ASSOCIATION_SYSTEM_OBJECT
判断)重新插入i
,并标记didReInsert
为true
。 - 没有需要重新插入的情况下,则擦除对象对应
ObjectAssociationMap
的迭代器i
。 - 创建
laterRefs
,记录dealloc
情况下需要是否的系统ObjcAssociation
。 - 循环
refs
非系统的关联对象直接释放,系统的关联对象判断是否deallocating
,deallocating
的情况下加入laterRefs
。 - 循环释放
laterRefs
里的ObjcAssociation
,也就是系统的关联对象(deallocating
的情况下才有值)。
3.3.3: 关联对象移除流程
- 创建一个临时的
ObjectAssociationMap
对象refs
。 - 根据
AssociationsManager
类拿到全局关联对象总哈希表AssociationsHashMap
。
- 根据
- 根据当前
object
封装的DisguisedPtr
在AssociationsHashMap
中获取对应的迭代器。 - 此迭代器不是
end
标记,将对象对应ObjectAssociationMap
数据交互到refs
中,原来迭代器置空,用于后续存放需要重新插入的系统关联对象。 - 非
deallocating
情况下,循环获取ObjcAssociation
,将系统的关联对象重新插入,没有需要重新插入的,就擦除被置空的迭代器。 - 循环获取
ObjcAssociation
,如果是系统关联对象,deallocating
情况下就加入临时Vector
中,非系统的就直接释放。 - 如果系统关联对象
Vector
有数据,就遍历释放(系统的关联对象在deallocating
情况下才释放)。
四: dealloc
实际开发过程中,我们一般不会主动调用移除关联对象的API
,那么对象想要释放,必然需要移除关联对象,下面就查看下dealloc
流程,看看里面是否有相应的操作。
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
调用流程dealloc -> _objc_rootDealloc -> obj->rootDealloc
,rootDealloc
函数里就有关联对象相关流程。
4.1: rootDealloc
inline void
objc_object::rootDealloc()
{
// Tagged Pointer
if (isTaggedPointer()) return; // fixme necessary?
// isa非纯指针
// 没有弱引用
// 没有关联对象
// 没有c++析构函数
// 没有引用计数表
// 同时满足这5个条件直接free,否则调用object_dispose
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
-
isa
非纯指针&&
没有弱引用&&
没有关联对象&&
没有c++
析构函数&&
没有引用计数表,同时满足这5
个条件直接free
,否则调用object_dispose
函数。
4.2: object_dispose
id
object_dispose(id obj)
{
if (!obj) return nil;
// 破坏对象
objc_destructInstance(obj);
free(obj);
return nil;
}
- 调用
objc_destructInstance
函数破坏对象,然后free
。
很显然移除关联对象的核心逻辑就在objc_destructInstance
函数中。
4.3: objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
// 调用C++析构函数
if (cxx) object_cxxDestruct(obj);
// 移除关联对象
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
// 清除`object`对应的弱引用表和引用计数表
obj->clearDeallocating();
}
return obj;
}
- 如果有
c++
析构函数,调用。 - 如果有关联对象,移除。
- 清除
object
对应的弱引用表和引用计数表。
4.4: clearDeallocating
inline void
objc_object::clearDeallocating()
{
// 两个不调用通一个方法的原因是
// 纯指针需要 SIDE_TABLE_WEAKLY_REFERENCED 判断是否有弱引用,
// nonpointer 可以通过isa字段判断
if (slowpath(!isa.nonpointer)) {//纯指针
// Slow path for raw pointer isa.
//清空散列表
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {//非纯指针,有弱引用表或者引用计数表
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
- 根据
isa
是否为纯指针分为两个逻辑,纯指针调用sidetable_clearDeallocating
函数,非纯指针调用clearDeallocating_slow
函数。
4.4.1: sidetable_clearDeallocating
和clearDeallocating_slow
sidetable_clearDeallocating:
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
// 在散列表中找到自己的引用计数表迭代器
RefcountMap::iterator it = table.refcnts.find(this);
// 不为 end 标记
if (it != table.refcnts.end()) {
// 判断是否有弱引用
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
// 清除自己的弱引用表
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 擦除自己的引用计数表
table.refcnts.erase(it);
}
table.unlock();
}
- 获取散列表。
- 在散列表中找到自己的引用计数表迭代器。
- 根据
SIDE_TABLE_WEAKLY_REFERENCED
判断是否有弱引用,有就清除。 - 擦除自己的引用计数表。
clearDeallocating_slow:
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
- 获取散列表。
- 通过
isa.weakly_referenced
判断是否有弱引用,有就清除。 - 通过
isa.has_sidetable_rc
判断是否有引用计数表,有则擦除。
4.4.2: weak_clear_no_lock
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
// 要清除的对象
objc_object *referent = (objc_object *)referent_id;
// 查找对应的weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
……
// zero out references weak对象引用表
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[I];
if (referrer) {
if (*referrer == referent) {
// weak引用置为nil
*referrer = nil;
}
else if (*referrer) {
……
objc_weak_error();
}
}
}
// 移除弱引用表中的entry
weak_entry_remove(weak_table, entry);
}
- 在弱引用计数表中找到自己的
weak_entry_t
。 - 根据
out_of_line
获取指向对象的弱引用指针referrers
与inline_referrers
。 - 循环弱引用指针数组将弱引用指针置为
nil
。 - 将弱引用
weak_entry_t
从弱引用表中删除。
4.5: dealloc
调用流程
- 能直接释放的情况(
isa
非纯指针&&
没有弱引用&&
没有关联对象&&
没有c++
析构函数&&
没有引用计数表):- 直接
free
。
- 直接
- 不能直接释放的情况:
- 如果有
c++
析构函数,调用. - 如果有关联对象,移除。
- 清空弱引用表(遍历弱引用指针数组,全部置为
nil
)。 - 从引用计数表中擦除自己的数据。
-
free
。
- 如果有
已知在
ARC
模式下dealloc
方法不再需要显式调用[super dealloc]
了,分析dealloc
流程时在objc
源码里也并没有看到[super dealloc]
的调用(objc
部分已经是运行时了),那么相关处理肯定在编译时了。使用
hopper
反汇编查看伪代码:
- 编译时就已经添加了
[super dealloc]
相关处理。代码编译相关内容,以后有机会单独发文分析。
五: 关联对象实现weak
属性
给分类添加关联对象的策略objc_AssociationPolicy
如下:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
可以发现在这个枚举中没有针对weak
相关的策略,官方注释让我们使用OBJC_ASSOCIATION_ASSIGN
来添加weak
属性的关联对象,但是weak
和assign
是有相关差别的:
weak
:修饰OBJC
对象,不会持有指向修饰对象,同样指向的对象引用计数就不会增加,当指向的对象被释放的时候,weak
修饰的对象会被置为nil
。
因为堆内存是动态的,所以当某个地址的对象被释放的时候,所有指向他的指针都应该被置为空。
weak
就是为了满足避免循环引用,同时在对象被释放的时候可以被置为空的情况而存在的。
assign
:修饰栈内存中的基本数据类型,当使用assign
修饰了一个OBJC
对象的时候,当指向的对象被释放的时候,assign
修饰的对象不会主动被置为nil
,可能造成野指针。
为了避免野指针的情况出现,只能另辟蹊径:强引用一个中间者,让中间者持有这个weak
属性。主流的实现方案就是使用block
:强引用block
,block
持有weak
属性。
@property (nonatomic, weak) id weak_obj;
- (void)setWeak_obj:(id)weak_obj {
id __weak weakObject = weak_obj;
// 声明实现block,执行block就会返回weakObject
id (^block)(void) = ^{ return weakObject; };
// 存储block,copy策略
return objc_setAssociatedObject(self, "weak_obj", block, OBJC_ASSOCIATION_COPY);
}
- (id)weak_obj {
// 获取block
id (^block)(void) = objc_getAssociatedObject(self, "weak_obj");
// 执行block,取到block返回的weakObject
id weakObject = (block ? block() : nil);
return weakObject;
}
关联对象时仍然是强引用,但是block
内部持有了属性的弱引用。在weak_obj
释放后,block
也就获取不到weak_obj
了。
关联对象在什么情况下会造成内存泄漏?
关联对象可以理解就是持有了一个对象,如果是强持有,而该对象也强持有了本类,那就会导致循环引用。
总结
关联对象的存储结构是两层哈希map
,存、取、移除都需要进行两层处理。