iOS 关联对象的原理实现
2018-02-07 本文已影响162人
Scott丶Wang
前一段时间恶补了一些C++的相关知识,看的越多、看得越深发现iOS相关实现也越来越简单,昨天看了一下关于关联对象存取的实现原理,现整理出来供以后查阅。
ps:用到的是objc4-709.tar.gz源码包
先贴一张图,再结合源码,其中原理实现自当一目了然~
大致实现图.png
以下贴出来的源码,是经过轻微简化、小范围宏展开之后的代码,源码中关键地方做了简单的注释,不太严谨或者不正确的地方还请斧正,大家一起学习进步。
objc-references.h 头文件
1.包含了必要的引用文件
2.关联对象存取的三个方法:
①_object_set_associative_reference设置关联对象,②_object_get_associative_reference获取关联对象,
③_object_remove_assocations删除关联对象。
/*************************Begin***************************/
#ifndef _OBJC_REFERENCES_H_
#define _OBJC_REFERENCES_H_
#include "objc-api.h"
#include "objc-config.h"
extern "C" {
extern void _object_set_associative_reference(id object, void *key, id value, unsigned long policy);
extern id _object_get_associative_reference(id object, void *key);
extern void _object_remove_assocations(id object);
}
#endif
/*************************End*****************************/
objc-references.m 实现
1.包含头文件中声明的三个方法的实现
2.相关的辅助方法
/*************************Begin***************************/
#include "objc-private.h"
#include <objc/message.h>
#include <map>
include <unordered_map>
// 将所有隐藏的C++细节包装在一个命名空间中
namespace objc_references_support {
// 这是一个函数对象,用于比较两个对象指针值(已按位取反,转化为unsigned long类型)
// 可以将这个函数对象理解为一个二元谓词(返回值bool,两个参数)
struct DisguisedPointerEqual {
bool operator()(unsigned long p1, unsigned long p2) const {
return p1 == p2;
}
};
struct DisguisedPointerHash {
unsigned long operator()(unsigned long k) const {
// borrowed from CFSet.c
#if __LP64__
unsigned long a = 0x4368726973746F70ULL;
unsigned long b = 0x686572204B616E65ULL;
#else
unsigned long a = 0x4B616E65UL;
unsigned long b = 0x4B616E65UL;
#endif
unsigned long c = 1;
a += k;
#if __LP64__
a -= b; a -= c; a ^= (c >> 43);
b -= c; b -= a; b ^= (a << 9);
c -= a; c -= b; c ^= (b >> 8);
a -= b; a -= c; a ^= (c >> 38);
b -= c; b -= a; b ^= (a << 23);
c -= a; c -= b; c ^= (b >> 5);
a -= b; a -= c; a ^= (c >> 35);
b -= c; b -= a; b ^= (a << 49);
c -= a; c -= b; c ^= (b >> 11);
a -= b; a -= c; a ^= (c >> 12);
b -= c; b -= a; b ^= (a << 18);
c -= a; c -= b; c ^= (b >> 22);
#else
a -= b; a -= c; a ^= (c >> 13);
b -= c; b -= a; b ^= (a << 8);
c -= a; c -= b; c ^= (b >> 13);
a -= b; a -= c; a ^= (c >> 12);
b -= c; b -= a; b ^= (a << 16);
c -= a; c -= b; c ^= (b >> 5);
a -= b; a -= c; a ^= (c >> 3);
b -= c; b -= a; b ^= (a << 10);
c -= a; c -= b; c ^= (b >> 15);
#endif
return c;
}
};
// 容器排列从小到大
struct ObjectPointerLess {
bool operator()(const void *p1, const void *p2) const {
return p1 < p2;
}
};
// 获取一个对象的哈希值
struct ObjcPointerHash {
unsigned long operator()(void *p) const {
return DisguisedPointerHash()(unsigned long(p));
}
};
// STL allocator that uses the runtime's internal allocator.
template <typename T> struct ObjcAllocator {
typedef T value_type;
typedef value_type* pointer;
typedef const value_type *const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
template <typename U> struct rebind { typedef ObjcAllocator<U> other; };
template <typename U> ObjcAllocator(const ObjcAllocator<U>&) {}
ObjcAllocator() {}
ObjcAllocator(const ObjcAllocator&) {}
~ObjcAllocator() {}
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const {
return x;
}
pointer allocate(size_type n, const_pointer = 0) {
return static_cast<pointer>(::malloc(n * sizeof(T)));
}
void deallocate(pointer p, size_type) { ::free(p); }
size_type max_size() const {
return static_cast<size_type>(-1) / sizeof(T);
}
void construct(pointer p, const value_type& x) {
new(p) value_type(x);
}
void destroy(pointer p) { p->~value_type(); }
void operator=(const ObjcAllocator&);
};
template<> struct ObjcAllocator<void> {
typedef void value_type;
typedef void* pointer;
typedef const void *const_pointer;
template <typename U> struct rebind { typedef ObjcAllocator<U> other; };
};
typedef unsigned long disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~unsigned long(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
class ObjcAssociation {
unsigned long _policy;
id _value;
public:
ObjcAssociation(unsigned long policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
unsigned long policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
#if TARGET_OS_WIN32
typedef hash_map<void *, ObjcAssociation> ObjectAssociationMap;
typedef hash_map<disguised_ptr_t, ObjectAssociationMap *> AssociationsHashMap;
#else
typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
// 地址值从小到大
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
// key唯一
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
#endif
}
using namespace objc_references_support;
// AssociationsManager类 管理了着一个锁还有一个全局的哈希键值对表.
// 初始化一个AssociationsManager实例对象的时候,会获取一个分配了内存的锁,并且调用它的assocations()方法来懒加载获取哈希表
spinlock_t AssociationsManagerLock;
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsHashMap *AssociationsManager::_map = NULL;
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3,
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};
// 根据一个给定的对象以及一个键值来获取它所绑定过的关联对象
id _object_get_associative_reference(id object, void *key) {
id value = nil;//关联对象
unsigned long policy = OBJC_ASSOCIATION_ASSIGN;//缓存策略
{//安全释放
AssociationsManager manager;//初始化AssociationsManager管理类
/*
这里用到了C++中引用的知识点,对这一块不太了解的同学可以自行查阅
int a = 10;
int &b = a;
b变量只是a的别名,两者访问的是同一块内存空间
为了更好的理解这里我们拆分一下
AssociationsHashMap &associations(manager.associations());
manager.associations();manager的associations()方法返回的是引用,是一个全局哈希键值对表的引用,我们一个临时变量去接这个返回值
*/
AssociationsHashMap tmp = manager.associations();
AssociationsHashMap &associations(tmp);//AssociationsHashMap引用初始化方法
/*
http://blog.csdn.net/coder__cs/article/details/79186677
阐述了计算机能够识别的二进制串是一种补码,我们理解的原码、反码只是我们自己定义的,计算机并不能识别。
二进制按位取反 x的按位取反结果为-(x+1)
*/
unsigned long disguised_object = DISGUISE(object);
/*
ObjectAssociationMap stl中map的子类、保存的是键值对
绑定关联对象时用到的key作为键,ObjcAssociation(policy,new_value)作为值,这样一对键值对存储在ObjectAssociationMap中
AssociationsHashMap stl中unordered_map的子类,这里是全局的
以对象指针16进制按位取反获得的补码作为键,以上述ObjectAssociationMap作为值,存储在AssociationsHashMap中
*/
/*
unordered_map容器中的迭代器,在AssociationsHashMap中查找给定对象所有绑定对象的键值对
查找到之后返回容器中所在的位置,用迭代器指向所在的位置。
i->first; //key
i->second; // value
*/
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
// 根据缓存策略来对关联对象进行内存管理
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
static id acquireValue(id value, unsigned long policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
static void releaseValue(id value, unsigned long policy) {
if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
}
}
struct ReleaseValue {
void operator() (ObjcAssociation &association) {
releaseValue(association.value(), association.policy());
}
};
// 给某个object绑定关联对象
void _object_set_associative_reference(id object, void *key, id value, unsigned long policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
// value对象时简单的技术加一还是copy
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
/*
i->first; key disguised_object
i->second;value ObjectAssociationMap
*/
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
/*
i->first; key 就是绑定关联对象时用到的key作为键值
i->second;value 就是ObjcAssociation(policy, new_value)
*/
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
// 设置当前对象已经绑定了关联对象
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
// 删除object对象绑定的关联对象
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
// 搭建舞台,生命周期完整
{
// AssociationsManager无参构造函数以及析构函数分别实现了加锁、解锁操作,避免了资源竞争
// AssociationsManager内部有一个共享的AssociationsHashMap
AssociationsManager manager;
// AssociationsHashMap集成unordered_map 无序map
// unordered_map(const unordered_map& __u);
// manager.associations()返回值AssociationsHashMap引用
AssociationsHashMap tmp = manager.associations();
AssociationsHashMap &associations(tmp);
if (associations.size() == 0) return;
/*
inline
disguised_ptr_t DISGUISE(id value) {
return ~unsigned long(value);
}
unsigned long DISGUISE(id value) {
return ~(unsigned long)(value);
}
*/
disguised_ptr_t disguised_object = DISGUISE(object);
// 迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
/*
struct ReleaseValue {
void operator() (ObjcAssociation &association) {
releaseValue(association.value(), association.policy());
}
}
*/
// 遍历对象,对容器中的所有关联对象进行内存释放
for_each(elements.begin(), elements.end(), ReleaseValue());
}
/*************************End*****************************/
测试代码,一个很简单的老师类,里面没有任何属性,方法。我们就是想给这个老师绑定姓名、年龄、任职学校,如何做呢?用关联对象。
//
// main.m
// debug_objc
//
// Created by wwh on 19/09/2017.
//
#import <Foundation/Foundation.h>
#import "objc-runtime.h"
static char *kTeacherAgeKey = "kTeacherKey";
static char *kTeacherNameKey = "kTeacherNameKey";
static char *kTeacherSchoolKey = "kTeacherSchoolKey";
@interface Teacher: NSObject
@end
@implementation Teacher
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Teacher *teacher = [Teacher new];
objc_setAssociatedObject(teacher, kTeacherAgeKey, @(28), OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(teacher, kTeacherNameKey, @"王某某", OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(teacher, kTeacherSchoolKey, @"北京大学", OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(teacher, kTeacherSchoolKey, @"清华大学", OBJC_ASSOCIATION_COPY);
NSString *name = (NSString *)objc_getAssociatedObject(teacher, kTeacherNameKey);
NSString *school = (NSString *)objc_getAssociatedObject(teacher, kTeacherSchoolKey);
NSNumber *age = (NSNumber *)objc_getAssociatedObject(teacher, kTeacherAgeKey);
NSLog(@"name=%@,age=%@,school=%@", name, age, school);
}
return 0;
}
如果你讨厌看源码的话,直接看总结吧:
关联对象的实现其实很简单的,对于一个给定的对象,设置关联对象时的存储结构是这样子的:
存储结构图.png
解释:
“给定对象1的内存地址的按位取反获得的补码作为键” 这句话可以理解成“Teacher实例对象对象”;
“ObjectAssociationMap“ 可以理解成一个字典;
而这个字典保存的内容就是:
年龄=28、姓名=王某某、任职学校=清华大学三个键值对。
实际用处
这里有一篇不错的文章来自杨萧玉 Associated Object 与 Dealloc