RenderCore/Utilities/RCAssociatedObject.mm (93 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "RCAssociatedObject.h"
#import <objc/runtime.h>
#import <unordered_map>
#import <vector>
#import <RenderCore/RCAssert.h>
#import <RenderCore/CKCollection.h>
/**
Since the only way to get notified when an object is deallocated is through associated object from
objc/runtime, we need this observer to be associated with the object so that we can remove all
associations when the object is deallocated.
*/
@interface CKObjectDeallocationObserver : NSObject
- (instancetype)initWithAddress:(uintptr_t)address;
@end
using KeyValue = std::pair<const void *, id>;
using AssociatedObjectMap = std::unordered_map<uintptr_t, std::vector<KeyValue>>;
static AssociatedObjectMap *CKMainThreadAffinedAssociatedObjectMap()
{
RCCAssertMainThread();
static AssociatedObjectMap *associatedObjectMap = new AssociatedObjectMap {};
return associatedObjectMap;
}
id _Nullable RCGetAssociatedObject_MainThreadAffined(__unsafe_unretained id object,
const void *key)
{
RCCAssertMainThread();
const auto map = CKMainThreadAffinedAssociatedObjectMap();
const auto it = map->find(uintptr_t(object));
if (it == map->end()) {
return nil;
}
const auto &keyValues = it->second;
const auto rs = CK::find_if(keyValues, [&](const auto &pair) {
return std::get<0>(pair) == key;
});
if (rs == keyValues.end()) {
return nil;
}
return std::get<1>(*rs);
}
static char CKObjectDeallocationObserverKey = ' ';
void RCSetAssociatedObject_MainThreadAffined(__unsafe_unretained id object,
const void *key,
__unsafe_unretained id _Nullable value)
{
RCCAssertMainThread();
const auto map = CKMainThreadAffinedAssociatedObjectMap();
const auto address = (uintptr_t)object;
const auto it = map->find(address);
if (it == map->end()) {
if (value != nil) {
map->emplace(address, std::vector<KeyValue> {{key, value}});
// Set associated object from objc/runtime so that we will get notified when `object` is deallocated.
objc_setAssociatedObject(object,
&CKObjectDeallocationObserverKey,
[[CKObjectDeallocationObserver alloc] initWithAddress:address],
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
} else {
auto &keyValues = it->second;
const auto rs = CK::find_if(keyValues, [&](const auto &pair) {
return std::get<0>(pair) == key;
});
if (rs == keyValues.end()) {
if (value != nil) {
keyValues.push_back({key, value});
}
} else {
if (value != nil) {
*rs = {key, value};
} else {
keyValues.erase(rs);
}
}
}
}
static void removeAllAssociatedObjects(uintptr_t address)
{
RCCAssertMainThread();
CKMainThreadAffinedAssociatedObjectMap()->erase(address);
}
@implementation CKObjectDeallocationObserver
{
uintptr_t _address;
}
- (instancetype)initWithAddress:(uintptr_t)address
{
if (self = [super init]) {
_address = address;
}
return self;
}
- (void)dealloc
{
RCAssert([NSThread isMainThread],
@"Object that has `RCAssociatedObject` must be deallocated on main thread");
removeAllAssociatedObjects(_address);
}
@end