iOS开发进阶:关联对象、weak指针的底层实现原理
一、关联对象的实现原理
在分类中使用@property
声明属性,只是将该属性添加到类属性列表,并声明了setter
和getter
方法,但是并没有生成对应的成员变量,也没有实现setter
和getter
方法。由于没有实现实现setter
和getter
方法,所以访问的时候会crash,提示... unrecognized selector sent to instance ...
。
如果在分类中使用@property
声明属性后,手动实现setter
和getter
方法,那么就可以正常设置和读取这个属性了,实现的方式可以参考如下:
@interface NXTeacher(Subperson)
@property (nonatomic, copy) NSString *address;
@end
static const char *kAddressKey = "AddressKey";
@implementation NXTeacher(Subperson)
- (void)setAddress:(NSString *)address{
objc_setAssociatedObject(self, kAddressKey, address, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)address {
return objc_getAssociatedObject(self, kAddressKey);
}
@end
这里用到了objc_setAssociatedObject(...)
和objc_getAssociatedObject(...)
方法,该方法内部分别调用了_object_set_associative_reference(...)
和_object_get_associative_reference(...)
。
先看一下objc_setAssociatedObject(...)
方法,第一个参数id object
,传我们的对象实例self
;第二个参数const void *key
,需要我们定义一个跟属性相关的const *
类型的变量;第三个参数id value
, 传设置的新值;第四个参数objc_AssociationPolicy policy
,表示关联策略,他有5个枚举值:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
-
OBJC_ASSOCIATION_ASSIGN
等价于@property (assign)
,一般用于基础类型(如int
、CGFloat
等)或weak
类型(如delegate
类型,或MRC
下的block
类型)。 -
OBJC_ASSOCIATION_RETAIN_NONATOMIC
等价于@property (nonatomic, strong)
(或MRC下@property (nonatomic, retain)
)。 -
OBJC_ASSOCIATION_COPY_NONATOMIC
等价于@property (nonatomic, copy)
。 -
OBJC_ASSOCIATION_RETAIN
等价于@property (strong)
(或MRC下@property (retain)
)。 -
OBJC_ASSOCIATION_COPY
等价于@property (copy)
。
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy){
...
//disguised相当于是object的一个唯一key(内部对指针地址按位取反)
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
//acquireValue方法内部会根据关联策略是value对象进行objc_retain(value)或[value copy]操作。
association.acquireValue();
bool isFirstAssociation = false;
{
//AssociationsManager是一个关联对象管理类,内部_mapStorage是个静态变量。
//manager在这里是利用构造函数、析构函数特性来自动加锁、解锁,manager.get()从_mapStorage中读出所有的关联表associations。
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
//如果新的关联值不为空
//根据disguised查找一个对应的ObjectAssociationMap,没有的话会创建一个并插入到associations中。
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
isFirstAssociation = true;
}
auto &refs = refs_result.first->second;
//根据key查找对应的bucket(key和association的封装),替换掉原有的或插入新的association,并设置关联策略
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
}
else {
//如果新的关联值为空,查找该对象的对应的ObjectAssociationMap,返回查找的迭代器。
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);//通过key去refs中查找该属性的ObjectAssociation,返回查找的迭代器
if (it != refs.end()) {
//如果存在,进行擦除操作
association.swap(it->second);
refs.erase(it);
//如果refs也没有该对象的其他关联属性的话,直接从associations中移除该对象对应的refs_it。
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
//在isa中标记该对象已经有关联对象了
if (isFirstAssociation) object->setHasAssociatedObjects();
//上面对value进行了retain操作的,这里进行一次release
association.releaseHeldValue();
}
知道了这个值怎么设置之后,那么读取这个值的思路就很简单了:
id _object_get_associative_reference(id object, const void *key){
ObjcAssociation association{};
{
//全部的关联对象表associations
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
//refs当前对象的,找到后根据key取当前属性的(通过迭代器遍历获取)
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
//赋值给association
association = j->second;
//如果关联策略包含retain, 这里就会retain一下。
association.retainReturnedValue();
}
}
}
//处理autorelease
return association.autoreleaseReturnedValue();
}
除此之外还有一个跟关联对象密切相关的函数是objc_removeAssociatedObjects(...)
,如果对象有关联对象则内部会调用_object_remove_assocations(...)
。
void _object_remove_assocations(id object, bool deallocating){
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
//读取到当前对象所有的关联对象
refs.swap(i->second);
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
associations.erase(i);
}
}
SmallVector<ObjcAssociation *, 4> laterRefs;
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
if (deallocating)
laterRefs.append(&i.second);
}
else {
//对应用的对象发送release消息
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
上面所使用的关键概念作一下解释:
- AssociationsManager是关联对象的管理者,内部维护了一个静态的_mapStorage,存储了所有对象的关联对象AssociationsHashMap。
- 从AssociationsHashMap通过object取出当前对象的关联对象表ObjectAssociationMap。
-
从ObjectAssociationMap通过setter和getter指定的key获取该属性的关联信息ObjcAssociation。
相关类的关系如图所示:
关联对象类关系如图.png
二、weak指针的实现原理
weak指针主要用于打破循环引用,应用场景较为广泛。那么weak修饰的指针与被指向的对象在底层的运作机制是怎样的呢?
1.weak指针实现原理的源码在static id storeWeak(id *location, objc_object *newObj)
,跟着源码进入一看究竟(源码已做大量精简和修改,仅留下核心流程):
static id storeWeak(id *location, objc_object *newObj){
if (haveOld) {
//通过weak指针之前指向的对象oldObj来找到之前的所存储在的sidetable
id oldObj = *location;
SideTable *oldTable = &SideTables()[oldObj];
//当前weak指针从旧对象的弱引用表oldTable->weak_table中清除
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
//通过新的对象newObj来找到现在对应的sidetable
SideTable *newTable = &SideTables()[newObj];
//写入弱引用表
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
//标记新对象的isa.weakly_referenced = true;
if (!newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock();}
//赋值新值
*location = (id)newObj;
}
return (id)newObj;
}
该函数总体来说做了2件事:
- 如果该weak指针有指向的旧对象oldObj,则根据oldObj拿到所在的弱引用表oldTable,然后从oldTable->weak_table中清除掉当前weak指针。
- 如果有设置新对象newObj,则根据newObj拿到所在的弱引用表newTable,然后将当前weak指针写入newTable->weak_table。再标记新对象已经被弱引用,最后赋值,返回新值。
2.weak_unregister_no_lock的流程
void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id){
objc_object *referent = (objc_object *)referent_id;//被弱引用的对象
objc_object **referrer = (objc_object **)referrer_id;//weak指针
if (!referent) return;
weak_entry_t *entry;
//weak_entry_for_referent是指从weak_table->weak_entries中通过referent,拿到referent的引用实体
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//从entry->referrers中移除weak指针
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
//如果entry->referrers中的weak指针全部移除了(空了),那么将当前的entry从weak_table中移除。
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
该函数有三个核心步骤:
- weak_entry_t *entry = weak_entry_for_referent(weak_table, referent),从weak_table-> weak_entries中通过referent,拿到referent的引用实体。
- 从entry->referrers中移除weak指针
- 如果entry->referrers中的weak指针全部移除了(空了),那么将当前的entry从weak_table中移除。
- weak_register_no_lock的流程
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions){
objc_object *referent = (objc_object *)referent_id;//被弱引用的对象
objc_object **referrer = (objc_object **)referrer_id;//weak指针
if (referent->isTaggedPointerOrNil()) return referent_id;
...
weak_entry_t *entry;
//weak_entry_for_referent是指从weak_table->weak_entries中通过referent,拿到referent的引用实体
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//将weak指针添加到entry->referrers中
append_referrer(entry, referrer);
}
else {
//如果没有新对象的weak_entry_t,则新建一个new_entry(写入referent和referrer)
weak_entry_t new_entry(referent, referrer);
//如果weak_table中存储的存储已满3/4,则扩容2倍(首次开辟大小为64),扩容后会将原有的weak指针列表赋值一份到新的weak_entries中。
weak_grow_maybe(weak_table);
//将当前new_entry插入weak_table-> weak_entries中
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
该函数有三个核心步骤:
- weak_entry_t *entry = weak_entry_for_referent(weak_table, referent))从weak_table->weak_entries中通过referent,拿到referent的引用实体
- 如果entry存在,则直接将weak指针插入到entry->referrers中。
- 如果entry不存在,则新建一个new_entry,将referent与 referrer关联,插入到weak_table-> weak_entries中。(插入之前会按需扩容)。
无论是插入一个新的weak指针还是移除一个旧的weak指针,都仅仅围绕着weak_table_t和weak_entry_t展开的,看一下数据结构:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;//弱引用表
};
//weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;//weak_entry_t列表
size_t num_entries;//weak_entries中weak_entry_t的个数
uintptr_t mask;
uintptr_t max_hash_displacement;
};
//weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;//被引用的对象
union {
struct {
weak_referrer_t *referrers;//weak指针
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
整个查找的思路:从最开始的StripedMap<SideTable>>中通过对象的hash查找对应的SideTable,然后从SideTable->weak_table->weak_entries中根据对象的hash查找出weak_entry_t。再根据weak_entry_t-> referrers进行weak指针的插入和删除。在向weak_table-> weak_entries插入weak_entry_t的时候涉及到扩容(如果需要),当weak_table->num_entries >= (weak_table.mask+1)3/4的时候,会进行2倍扩容(首次容量大小64)。在向weak_entry_t-> referrers插入weak指针的时候也涉及到扩容(如果需要),当weak_entry_t->num_refs >= (weak_entry_t-> mask+1)3/4的时候,会进行2倍扩容(首次容量大小为8)。