Associated Objects 源码探究和实现动态添加weak属性

848 阅读4分钟

Associated Objects 提供了为既有类动态添加关联对象的能力,也是对 category 只能拓展方法的一个很好的补充。它通过 Key-Value 的形式将对象与类绑定,并提供类似属性的关联策略,最终保存在全局的 Hash map 中。从源码分析 Associated Objects 的实现及其能与不能,并通过一种 walk around 的方式实现 weak Associated Objects 。

关联对象的核心对象

image.png

实现

runtime 提供的我们熟悉的API如下:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)  
id objc_getAssociatedObject(id object, const void *key)  
void objc_removeAssociatedObjects(id object)

objc_setAssociatedObject

objc_setAssociatedObject()方法内部调用了_object_set_associative_reference(),将参数透传。_object_set_associative_reference的实现在 objc-references.mm 文件中。该文件包含了 ObjC 关联对象的相关实现,ObjcAssociation类是对关联对象的封装,包括关联策略(policy)以及 value 、初始化方法以及一些内部方法,这里包括根据不同关联策略对 value 的处理。AssociationsManager主要用来管理 Association Map 及相关的锁(spinlock_t)操作。

image.png

  • 1.创建一个asociationsManager管理类
  • 2.获取唯一的全局静态哈希Map:AssociationsHashMap
  • 3.判断是否插入的关联值value是否存在
    • 3.1 存在就走第4步
    • 3.2 不存在就走关联对象-插入空流程
  • 4.通过ry_emplac方法,并创建一个空的 ObjectAssociationMap 去取查询的键值对:
  • 5.如果发现没有这个 key插入一个 空的 BucketT进去并返回true
  • 6.通过setHasAssociatedObjects方法标记对象存在关联对象即置isa指针has_asso属性为true
  • 7.用当前 policy 和 value 组成了一个ObjcAssociation替换原来 BucketT 中的空
  • 8.标记一下 ObjectAssociationMa第一次false

try_emplace实现源码

image.png

所以到目前为止,关联属性涉及的map结构为:

image.png 总结:

  • AssociationsManager可以有多个,通过AssociationsManagerLock锁可以得到一个AssociationsHashMap类型的map
  • map中有很多的关联对象map,类型是ObjectAssociationMap,其中key为DisguisedPtr<objc_object>,例如Person会对应一个ObjectAssociationMap,Teacher也会对应一个ObjectAssociationMap
  • ObjectAssociationMap哈希表中有很多key-value键值对,其中key的类型为const void *,value的类型为ObjcAssociation
  • 其中ObjcAssociation是用于包装policy和value的一个类

image.png

objc_getAssociatedObject

源码实现

image.png _object_get_associative_reference方法源码

  • 1:创建一个 AssociationsManager 管理类
  • 2:获取唯一的全局静态哈希Map:AssociationsHashMap
  • 3:通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 4:如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (policyvalue)
  • 5:通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 6:返回 value

image.png

_object_remove_assocations

移除方法 _object_remove_assocations(id object, bool deallocating) 中可以看到有一个 deallocating 参数,这是因为 runtime 在销毁对象的 objc_destructInstance 方法中会主动调用移除该对象所有关联对象的方法,如果关联对象为系统对象 SYSTEM_OBJECT 则仅会在 deallocating 时销毁,主动调用时系统对象会被保留:

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);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            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);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}

总结

关联对象的底层调用流程为: 374aabb48e747ed4d738839b375d661f.png 总的来说,关联对象主要就是两层哈希map的处理,即存取时都是两层处理,类似于二维数组

Weak Associated Object

其实思路比较简单,通过增加一个中间层的方式来实现。上面的源码看到,当拥有 Associated Object 的对象被析构,policy 为 RETAIN 的 Associated Object 会被发送 objc_release 消息。这样这个 Associated Object 的 weak reference 便可正常的置为 nil,具体如下 :

一个 weak Associated Object 代理类,拥有一个 weak 修饰的属性

@interface WeakAssociatedObjectProxy : NSObject

@property (nonatomic, weak) id object;

@end

想要类的 category 中进行对象关联:

@implementation NSObject (Weak)

-(NSString *) myObject {
    WeakAssociatedObjectProxy * proxy = objc_getAssociatedObject (self,  @selector (myObject));
    return proxy.object;
}

-(void) setMyObject:(NSString *) myObject {
    WeakAssociatedObjectProxy *proxy = [[WeakAssociatedObjectProxy alloc]init];
    proxy.object = myObject;
    objc_setAssociatedObject (self, @selector (myObject), proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

用 RETAIN 关联的 proxy 对象在 self 被析构时会被发送 release 消息,进而会将其 weak 属性设为 nil 。

另一种方式可以通过 block 来作为中间层,通过 COPY 的 policy 让 block 持有传入的 myObject ,这种方式更加简洁:

@implementation NSObject (Weak)
-(NSString *) myObject {     
    id (^block)(void) = objc_getAssociatedObject (self, @selector (myObject));    
    return (block ? block () : nil); 
} 
-(void) setMyObject:(NSString *) myObject {     
id __weak weakObject = myObject;     
id (^block)(void) = ^{ return weakObject; };     
objc_setAssociatedObject (self, @selector (myObject), block, OBJC_ASSOCIATION_COPY); 
} 
@end