OC底层 - 类的扩展&关联对象
前言
一、类的加载回顾
二、类的扩展
三、关联对象
四、关联对象的策略分析
五、association属性的weak实现
六、关联对象是线程安全的吗?
七、关联对象总结
经过前3篇博客的总结,类的加载流程基本上过了一遍,下面我们继续来探索类的扩展和关联对象。
类的加载回顾
_objc_init -> _dyld_objc_notify_register -> map_images (_read_images -> readClass -> realizeClassWithoutSwift ....) -> load_images
整个流程,我们都分析了一遍。
抛出一个问题:
启动app之后,macho文件会被映射到内存中,而数据映射之后,比如类的数据,是怎么读取到class_ro_t格式的呢?
首先了解一下class_ro_t的数据结构:

class_ro_t的成员如果要被准确的映射。那么肯定在编译的时候就确定了数据结构。所以我们直接看llvm的源码:搜索struct class_ro_t

可以看到成员是一一对应的,并且有一个read方法。
继续搜索:class_ro_t::Read
bool ClassDescriptorV2::class_ro_t::Read(Process *process, lldb::addr_t addr) {
size_t ptr_size = process->GetAddressByteSize();
//获取size
size_t size = sizeof(uint32_t) // uint32_t flags;
+ sizeof(uint32_t) // uint32_t instanceStart;
+ sizeof(uint32_t) // uint32_t instanceSize;
+ (ptr_size == 8 ? sizeof(uint32_t)
: 0) // uint32_t reserved; // __LP64__ only
+ ptr_size // const uint8_t *ivarLayout;
+ ptr_size // const char *name;
+ ptr_size // const method_list_t *baseMethods;
+ ptr_size // const protocol_list_t *baseProtocols;
+ ptr_size // const ivar_list_t *ivars;
+ ptr_size // const uint8_t *weakIvarLayout;
+ ptr_size; // const property_list_t *baseProperties;
DataBufferHeap buffer(size, '\0');
Status error;
//读取内存情况
process->ReadMemory(addr, buffer.GetBytes(), size, error);
if (error.Fail()) {
return false;
}
DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(),
process->GetAddressByteSize());
lldb::offset_t cursor = 0;
m_flags = extractor.GetU32_unchecked(&cursor);
m_instanceStart = extractor.GetU32_unchecked(&cursor);
m_instanceSize = extractor.GetU32_unchecked(&cursor);
if (ptr_size == 8)
m_reserved = extractor.GetU32_unchecked(&cursor);
else
m_reserved = 0;
m_ivarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
m_name_ptr = extractor.GetAddress_unchecked(&cursor);
m_baseMethods_ptr = extractor.GetAddress_unchecked(&cursor);
m_baseProtocols_ptr = extractor.GetAddress_unchecked(&cursor);
m_ivars_ptr = extractor.GetAddress_unchecked(&cursor);
m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
m_baseProperties_ptr = extractor.GetAddress_unchecked(&cursor);
DataBufferHeap name_buf(1024, '\0');
process->ReadCStringFromMemory(m_name_ptr, (char *)name_buf.GetBytes(),
name_buf.GetByteSize(), error);
if (error.Fail()) {
return false;
}
m_name.assign((char *)name_buf.GetBytes());
return true;
}
通过Read函数,读取了class_ro_t的整个信息(class_rw_t等类似)。
那么在什么时候会调用Read函数呢?继续找源码,是通过Read_class_row调用的。
bool ClassDescriptorV2::Read_class_row(
Process *process, const objc_class_t &objc_class,
std::unique_ptr<class_ro_t> &class_ro,
std::unique_ptr<class_rw_t> &class_rw) const {
class_ro.reset();
class_rw.reset();
Status error;
uint32_t class_row_t_flags = process->ReadUnsignedIntegerFromMemory(
objc_class.m_data_ptr, sizeof(uint32_t), 0, error);
if (!error.Success())
return false;
if (class_row_t_flags & RW_REALIZED) {
class_rw = std::make_unique<class_rw_t>();
if (!class_rw->Read(process, objc_class.m_data_ptr)) {
class_rw.reset();
return false;
}
class_ro = std::make_unique<class_ro_t>();
if (!class_ro->Read(process, class_rw->m_ro_ptr)) {
class_rw.reset();
class_ro.reset();
return false;
}
} else {
class_ro = std::make_unique<class_ro_t>();
if (!class_ro->Read(process, objc_class.m_data_ptr)) {
class_ro.reset();
return false;
}
}
return true;
}
由上面的分析,就可以大概清楚了为什么macho的数据可以映射成对应的结构了。
二、类的扩展
1、大致总结一下分类和扩展的区别:
分类:
(1)用来给类添加新的方法
(2)不能给类添加成员属性,添加了成员变量,也无法取到
(3)可以通过runtime给分类添加属性
(4)分类中用@property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法实现和带下划线的变量。
(5)重点:
回顾懒加载类和非懒加载,以及attachCategories的加载时机:
1、懒加载类 + 懒加载分类 :
消息第一次调用的时候,加载类的数据 (编译时期就完成分类的加载)
* 2、非懒加载类 + 懒加载分类:
read_image时,加载类的数据 (编译时期就完成分类的加载)
3、非懒加载类 + 非懒加载分类 :
read_image时,加载类的数据 (分类的加载在运行时,流程为:load_images ->
loadAllCategories -> load_categories_nolock -> attachCategories)
4、懒加载类 + 非懒加载分类:
read_image时,加载类的数据,迫使类成为非懒加载类样式来提前加载数据。
分类加载有2种情况:
(1)当只有一个分类为非懒加载分类时:分类是在编译期完成加载的。
*(2)当大于一个分类为非懒加载分类时:
分类是在运行时完成加载的(流程是:read_images -> realizeClassWithoutSwift
->methodizeClass -> attachToClass -> attachCategories)
当以上为第2中情况和第4中情况中第(2)种情况时,都会走attachCategories的流程。
在attachCategories中会创建rwe,同时会把分类中的property拷贝到rwe中去。
如果是其他的情况,分类中的property不会拷贝到rwe中去。
扩展:
(1)可以说是特殊的分类,也叫做匿名分类
(2)可以给类添加成员属性,但是是私有变量
(3)可以给类添加方法,也是私有方法
2、扩展的位置
扩展必须在声明之后,实现之前:
@interface MyTestExtClass : NSObject
@end
//扩展必须在这之间,否则会报错
@interface MyTestExtClass ()
//扩展
@end
@implementation MyTestExtClass
@end
3、通过clang -rewrite-objc MyTestExtClass.m -o MyTestExtClass.cpp查看
extern "C" unsigned long OBJC_IVAR_$_MyTestExtClass$_ext_name;
struct MyTestExtClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_ext_name;
};
static NSString * _I_MyTestExtClass_ext_name(MyTestExtClass * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MyTestExtClass$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_MyTestExtClass_setExt_name_(MyTestExtClass * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyTestExtClass, _ext_name), (id)ext_name, 0, 1); }
可以看到通过扩展添加的属性,会写入成员变量和get、set方法。
再来看方法:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_MyTestExtClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
5,
{{(struct objc_selector *)"ext_instanceMethod", "v16@0:8", (void *)_I_MyTestExtClass_ext_instanceMethod},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_MyTestExtClass_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_MyTestExtClass_setExt_name_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_MyTestExtClass_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_MyTestExtClass_setExt_name_}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_MyTestExtClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"ext_classMethod", "v16@0:8", (void *)_C_MyTestExtClass_ext_classMethod}}
};
添加的实例方法和类方法,也写入到了method_list中了。说明在编译的时候就都添加到了类中。
三、关联对象
@implementation AssociationsClassTest
- (void)setCate_name:(NSString *)cate_name {
/**
* 1:对象
* 2: 标识符
* 3: value
* 4: 策略
*/
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name {
return objc_getAssociatedObject(self, "cate_name");
}
@end
进入objc_setAssociateObject源码:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
核心函数:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// 当为对象和键传递 nil 时,此代码曾经起作用。
//一些代码 // 可能依赖于它不会崩溃。 明确检查和处理。
// rdar://problem/44094390
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};
// retain the new value (if any) outside the lock.
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) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
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);
}
}
}
}
}
// 在锁外调用 setHasAssociatedObjects,因为如果它
// 将调用对象的 _noteAssociatedObjects 方法
// 这可能会触发 +initialize ,它可能会执行
// 任意操作,包括设置更多关联的对象。
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
具体分析这个核心函数:
1、DisguisedPtr
template <typename T>
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
public:
DisguisedPtr() { }
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }
DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}
..........
};
DisguisedPtr 是一个类型,用来包装我们的对象。
2、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;
}
}
}
......
};
ObjcAssociation 用来包装策略和值。
3、 association.acquireValue();
判断如果有值的情况下,根据策略进行copy或者strong。如果不是copy和strong 就不需要处理。
4、核心代码
(1)AssociationsManager (关联对象管理类)
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();
}
};
AssociationsManager 并不是一个单利。
它在构造方法中加了锁是为了防止多线程情况下重复创建,但不代表不能创建。
它的锁相当于加在了这两个代码作用域内:
bool isFirstAssociation = false;
{
AssociationsManager manager;
lock();
.......
unlock();
}
它内部的Stroage才是一个单利。而_mapStorage是全局唯一的一张关联对象表。它是在map_images_nolock中进行初始化的。
map_images_nolock:
if (firstTime) {
sel_init(selrefCount);
arr_init();
.....
void arr_init(void)
{
AutoreleasePoolPage::init();
SideTablesMap.init();
_objc_associations_init();
}
void _objc_associations_init()
{
AssociationsManager::init();
}
(2) 打印refs_result
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation,
objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>,
objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
objc::DenseMapValueInfo<objc::DenseMap<const void *,
objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>,
objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
objc::DenseMapInfo<DisguisedPtr<objc_object> >,
objc::detail::DenseMapPair<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation,
objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>,
objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>,
bool>)
$3 = {
first = {
Ptr = 0x0000000101906930
End = 0x0000000101906970
}
second = true
}
(lldb)
这个变量的结构是真的复杂。我们简化一下它:
(std::pair<
objc::DenseMapIterator<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation,
objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *,
objc::ObjcAssociation> >,
objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation,
objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *,
objc::ObjcAssociation> > >,
objc::DenseMapInfo<DisguisedPtr<objc_object> >,
objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *,
objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *,
objc::ObjcAssociation> > >,
false>,
bool>)
相当于:
std::pair<
objc,
bool>)
if (refs_result.second) 就相当于判断第二个bool类型。如果存在isFirstAssociation = true 则调用: object->setHasAssociatedObjects();
(3)LookupBucketFor 哈希查找
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket)
两个参数:第一个是封装之后的对象,第二个是一个空的桶子
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets(); //获取首地址
const unsigned NumBuckets = getNumBuckets(); //获取桶子的数量
//如果没有,则返回false
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
//哈希函数 -> 下标
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
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;
}
// 如果这是墓碑,记住它。
// 如果 Val 最终不在表中,我们更愿意返回它而不是需要更多探测的东西。
// 零值也是如此。
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// 否则,它是哈希冲突或墓碑,继续二次哈希
// 探测。
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
template <typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
const BucketT *ConstFoundBucket;
//获取查找结果
bool Result = const_cast<const DenseMapBase *>(this)
->LookupBucketFor(Val, ConstFoundBucket);
//如果找到把结果给FoundBucket,也就是外界传进来的Bucket。
FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
return Result;
}
(3) try_emplace
// Inserts key,value pair into the map if the key isn't already in the map.
// The value is constructed in-place if the key is not in the map, otherwise
// it is not moved.
// 如果键不在映射中,则将键值对插入映射中。
// 如果键不在表中,则就地构造值,否则
// 不会移动它。
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); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
LookupBucketFor 通过哈希查找(和cache_t类似)判断存储桶子是否包含键和值,如果包含直接返回。如果不包含,执行InsertIntoBucket插入。
(4)打印TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *)
和(2)中打印的refs_result做一下对比,可以将(2)简化为:
(std::pair<
objc::DenseMapIterator<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>,
objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
objc::DenseMapInfo<DisguisedPtr<objc_object> >,
TheBucket,
false>,
bool>)
(5)核心流程
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
拿到对象对应的那个桶子(refs),然后根据key(就是我们设置关联对象时定的那个key),继续执行try_emplace流程,然后判断result.second是否刚建立,如果不是就更新。
注意:
当associations.try_emplace 时,key是当前对象,value是ObjectAssociationMap 。
当refs.try_emplace时,key是关联属性设置的那个key,value是association(value,policy)。
(6)setHasAssociatedObjects
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) {
void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects));
if ((IMP)setAssoc != _objc_msgForward) {
(*setAssoc)((id)this, @selector(_noteAssociatedObjects));
}
}
isa_t newisa, oldisa = LoadExclusive(&isa.bits);
do {
newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
} while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
}
设置isa中的has_assoc标志为true。
四、关联对象的策略分析
分析完关联对象的整个设置流程之后(取值流程类似)。
那么我们再思考一个问题:
关联对象策略policy的OBJC_ASSOCIATION_RETAIN和OBJC_ASSOCIATION_RETAIN_NONATOMIC的有区别吗?
首先看下策略的枚举:
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. */
};
内部策略:
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8),
OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16
};
内部在存取关联对象的时候会根据这两个策略的组合来做不同的逻辑处理
存值的逻辑:
1、_object_set_associative_reference中会调用association.acquireValue()来retain新值
这里OBJC_ASSOCIATION_RETAIN和OBJC_ASSOCIATION_RETAIN_NONATOMIC是都会执行objc_retain。
2、在设置完了之后,会判断是否需要释放旧值association.releaseHeldValue()
同样的OBJC_ASSOCIATION_RETAIN和OBJC_ASSOCIATION_RETAIN_NONATOMIC是都会执行objc_release。前提是已经设置了key对应的数据。
取值的逻辑:
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();
}
主要看retainReturnedValue和autoreleaseReturnedValue
inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
int a = 0;
}
inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
我们做一下位运算就知道,OBJC_ASSOCIATION_RETAIN会走objc_retain和objc_autorelease流程。
相当于返回了[[_value retain] autorelease],但是注意一个细节,[_value retain]的时候是加锁的,解锁之后才相当于返回了[[_value retain] autorelease]。
而OBJC_ASSOCIATION_RETAIN_NONATOMIC则直接返回了_value。
这和我们在属性上直接设置atomic和nonatomic不是一个概念。
那么个人认为苹果在这里备注的"The association is made atomically."
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
并不是真正的原子性。而且在存值和取值的过程中,都会通过AssociationsManager加锁。所以我们一般使用NONATOMIC的策略比较好。
五、association属性的weak实现
在实现weak策略之前,先来实现一个之前面试过我的问题:
问题:如何监听一个对象释放?
这个问题有人会说,直接在dealloc中做一些释放工作不就行了吗?干嘛还要再做一个监听呢?是的,当时我面试的时候也是这么说的...
但是举2个场景来说一下这个监听的作用:
场景1:我们在类中使用kvo、notification的时候,添加observer之后,在dealloc里面需要去remove的。如果有了一层监听,那我们是不是就可以统一的在这个监听里面去remove了呢,而不用在每一个dealloc里面做remove的操作了(当然特殊的释放操作还是要放在dealloc里面去做)。
场景2: 比如我们有些业务,需要依赖于某一个类A释放之后继续操作。那直接从类A的dealloc方法回调给当前类处理也太麻烦了吧。
那么针对这个问题,我们首先从dealloc的底层分析:
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj) {
ASSERT(obj)
obj->rootDealloc();
}
inline void objc_object::rootDealloc() {
if (isTaggedPointer()) return; // fixme necessary?
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);
}
}
id object_dispose(id obj) {
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
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.
if (cxx) object_cxxDestruct(obj);
//编译器会在object_cxxDestruct之后插入cxx_destruct,在cxx_destruct中又会调用objc_destroyWeak(引用 "弱引用的对象",也就是有弱引用属性的对象,释放时会调用)
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
以上代码就是整个dealloc的流程。
dealloc -> _objc_rootDealloc -> object_dispose(主要判断了是否被弱引用,是否有关联对象)
-> objc_destructInstance -> _object_remove_assocations ,clearDeallocating。
由于对象dealloc时候,如果有关联对象,会先移除关联对象。
方案如下:
1、我们可以给NSObject增一个分类,添加一个关联对象。因为当关联对象释放之后,当前对象也会释放。所以我们可以监听这个关联对象。
2、在这个关联对象里面加一个target属性,用来弱引用宿主对象。
3、给外界提供一个block回调,在关联对象释放的时候回调回来做额外的操作。
看代码:
自定义的关联对象:
@interface MGAssociatedObject : NSObject
- (instancetype)initWithTarget:(NSObject *)target;
- (void)addActionBlock:(MGBlock)block;
@end
@interface MGAssociatedObject ()
/*
这里不用weak是由于在target释放的时候,先释放关联对象。
然后有weak引用会调用objc_destroyWeak,清除weak表中对应的弱引用指针,并将弱引用指针指向nil
(通过源码分析,在dealloc回调的时候weak表并没有清除。
objc_destroyWeak也是在MGBlock回调之后才走的。
但是可能是内部的多线程引起,系统不让你读到弱引用对象)。
所以回调的地方拿到的就是nil了,此时应该使用unsafe_unretained
(由于target一定是在之后才彻底释放的,所以这里用unsafe_unretained不会产生野指针问题)。
**/
@property (nonatomic, unsafe_unretained) NSObject *target;
@property (nonatomic, strong) NSMutableArray<MGBlock> *deallocBlocks;
@end
@implementation MGAssociatedObject
- (instancetype)initWithTarget:(NSObject *)target {
self = [super init];
if (self) {
_deallocBlocks = [NSMutableArray arrayWithCapacity:0];
_target = target;
}
return self;
}
- (void)addActionBlock:(MGBlock)block {
[self.deallocBlocks addObject:[block copy]];
}
- (void)dealloc {
[_deallocBlocks enumerateObjectsUsingBlock:^(MGBlock _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj ? obj(_target) : nil;
}];
}
@end
自定义的分类:
typedef void(^MGBlock)(__unsafe_unretained NSObject *target);
@interface NSObject (MGWillDealloc)
- (void)mg_doSthWhenDeallocWithBlock:(MGBlock)block;
@end
@implementation NSObject (MGWillDealloc)
- (void)mg_doSthWhenDeallocWithBlock:(MGBlock)block {
if (block) {
//设置关联对象
MGAssociatedObject *associatedObject = objc_getAssociatedObject(self, &kMGAssociatedObjectKey);
if (!associatedObject) {
associatedObject = [[MGAssociatedObject alloc] initWithTarget:self];
objc_setAssociatedObject(self, &kMGAssociatedObjectKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[associatedObject addActionBlock:block];
}
@end
这样我们就可以在任何oc的类中去通过mg_doSthWhenDeallocWithBlock来监听它的释放了。
接下来就看怎么实现关联对象的weak策略了。
weak策略
首先关联对象中有OBJC_ASSOCIATION_ASSIGN策略,那么assign如果修饰oc对象的话,确实不会产生引用计数+1,但是当对象释放之后,会造成野指针。
- (void)testAssignCase {
{
UILabel *associatedLabel = [UILabel new];
objc_setAssociatedObject(self, @selector(associatedLabel), associatedLabel, OBJC_ASSOCIATION_RETAIN);
}
UILabel *label = objc_getAssociatedObject(self, @selector(associatedLabel)); // EXC_BAD_ACCESS
[self.view addSubview:label];
}
例如以上代码,当associatedLabel出了作用域之后,就被释放了。后面通过objc_getAssociatedObject拿到已经释放的内存地址,无法访问。
所以我们需要自己实现一个weak策略:
因为assign会造成野指针问题,那么当关联对象释放时,将ObjectAssociationMap表中key对应的association也移除。就可以避免由于assign的方式访问了非法内存的异常了。也就达到了weak的效果了。
/// 设置关联对象支持weak的方式
/// @param object 宿主对象
/// @param key 关联key
/// @param value 关联的对象
//声明:
extern void objc_setWeakAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value);
//实现:
void objc_setWeakAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value) {
if (value) {
[value mg_doSthWhenDeallocWithBlock:^(NSObject *__unsafe_unretained _Nonnull target) {
objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_ASSIGN); // clear association
}];
}
objc_setAssociatedObject(object, key, value, OBJC_ASSOCIATION_ASSIGN);
}
可以测试一下:
- (void)testWeakCase {
{
UILabel *associatedLabel = [UILabel new];
objc_setWeakAssociatedObject(self, @selector(associatedLabel), associatedLabel);
}
UILabel *label = objc_getAssociatedObject(self, @selector(associatedLabel)); // EXC_BAD_ACCESS
[self.view addSubview:label];
}
完美达到weak的效果,但是某些场景下,记得判空处理。
六、关联对象是线程安全的吗?
是线程安全的。 当我们调用get和set方法时,都会通过底层的AssociationsManager管理的一张AssociationsHashMap表来设置值和取值的。
以get方法为例:首先会以对象的地址为key,计算出下标值,从AssociationsHashMap取出对应的ObjectAssociationMap表,再以void *key 去ObjectAssociationMap表中取到ObjcAssociation,ObjcAssociation中包含了value和policy,从而取到value。 而刚才说的AssociationsManager(关联对象管理器)在构造方法中会加一把自旋锁,从而保证了取值过程和设置值的过程线程安全。
追问:那在什么时候解锁的呢?
是在AssociationsManager析构的时候,也就是设置完值,或者取完值的时候,AssociationsManager被系统释放时,会调用析构方法,从而解锁。
关于锁的分析,后面会具体探索。
七、关联对象总结
1、流程图:

2、设置值流程:
(1)创建一个AssociationsManager管理类
(2)获取唯一的全局静态哈希Map (_mapStorage)
(3)判断要插入的关联值是存在,如果存在走第(4)步,否则走插入空流程
(4)创建一个空的ObjectAssociatioinMap去取查询的键值对
(5)如果发现没有这个关联对象,就插入一个空的BuckeT进去,然后返回。再设置isFirstAssociation = true,标记为第一次创建关联对象。
(6)拿当前的key和当前策略以及值组成的ObjectAssociatioin,去查找对应的key是否设置了关联值,如果没有设置则设置,如果设置了,就替换原来的策略和值。
(7)标记此对象存在关联对象(修改isa中has_assoc=true)
3、插入空的流程
(1)根据DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
(2)清理迭代器
(3)相当于清除AssociationsHashMap中的关联对象。
4、取值流程
(1)创建一个AssociationsManager管理类
(2)获取唯一的全局静态哈希Map
(3)根据DisgusedPtr找到AssociationsHasMap中的iterator迭代查询器
(4)通过迭代器,获取ObjectAssociationMap (这里有策略和value)
(5)找到ObjcetAssociationMap的迭代查询器获取一个经过属性修饰符(key)修饰的value
(6)返回value
5、关联对象怎么释放,对象释放时怎么释放关联对象?
当我们设置的值为nil的时候,会从ObjectAssociationMap中,移除objcAssociation。
而整个关联对象会跟随对象释放。在对象释放的时候,会走dealloc方法。此时底层会判断,当前对象isa中保存的has_assoc(关联对象标志)是否为true,也就是,是否包含关联对象。如果包含会走object_dispose->objc_destructInstance->_object_remove_assocations,从AssociationHashMap中移除此关联对象。