此文实际成于 2015/08/03
Objective-C 给我们提供了为已有类添加属性的方式。即 Associated Object. 这是 iOS 4 引入的新特性。
为已有的类添加方法,添加属性,在增强已有类的场景下非常强大好用。但是值得注意的一点就是,
它有时可能会让运行时混乱,比如添加了冲突的属性。或者让使用者混乱,哪一些是增强的属性或者方法。
参考: http://nshipster.com/associated-objects/
为 NSObject 类添加 author 属性。
代码如下:
@interface NSObject (MyObject)
@property (nonatomic,strong) NSString * author;
@end
@implementation NSObject (MyObject)
- (NSString*)author{
return objc_getAssociatedObject(self, "author");
}
- (void )setAuthor:(NSString*)author{
objc_setAssociatedObject(self, "author", author, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
使用的时候像平常一样即可:
NSObject * myObject = [[NSObject alloc] init];
myObject.author = @"banxi";
NSLog(@"myObject author is %@\n",myObject.author);
objc_getAssociatedObject(self, "author");
这样的取值方式,让人觉得每一个 Objective-C 的对象都自带了
一个 Key-Value 容器。上面容器是 self
, key 是"author"
. 不过这里 Key 不必是字符串。
因为其类型声明是 const void *key
只要setter 与 getter 一致即可。
一般推荐的做法是声明 key 为static char
类型,或者某一个指针。基本上满足是常量,唯一,并且其 setter,getter 能访问到到即可。 一种不错的实现是使用selector
Since SELs are guaranteed to be unique and constant, you can use _cmd as the key for objc_setAssociatedObject(). #objective-c #snowleopard
— Bill Bumgarner (@bbum) August 28, 2009
Associative Object Behaviors
在 objc_setAssociatedObject(self, "author", author, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
上面存储关联值的最后一个参数,指定了存储值时一些属性相关选项。
如下:
enum {
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. */
};
这些策略选项,与@property
声明的等价声明如下:
ASSIGN -> `@property (assign) or @property (unsafe_unretained)`
RETAIN_NONATOMIC -> `@property (nonatomic,strong)`
COPY_NONATOMIC -> `@property (nonatomic,copy)`
RETAIN -> `@property (atomic, strong)`
COPY -> `@property (atomic,copy)`
更进一步
下面来看下其代码的实现:
// 1) 声明
void
objc_setAssociatedObject(id object, const void *key, id value,
objc_AssociationPolicy policy)
{
objc_setAssociatedObject_non_gc(object, key, value, policy);
}
// 2)特定方法
void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
// 3) 具体实现
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
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
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
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);
}
针对我们上面的代码 objc_setAssociatedObject(self, "author", author, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
在上面堆复杂代码中,真正的执行代码如下 :
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value){
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
大的数据结构就是: Objective-C 的运行时,
- 维护一张全局的 从 object -> ObjectAssociationMap 名为 AssociationsHashMap 的 Hash 表。
- 而 ObjectAssociationMap 是一张属于某一个对象的 从 Key 对 Value 的 Hash 映射表。
- 为也记录 Value 的存储的 引用处理策略,使用 ObjcAssociation 来包装 Value.
class ObjcAssociation {
uintptr_t _policy;
id _value;
}
保存中指定的 Policy 会在 存储值或者取值时使用,主要有以下使用:
// 1)保存时
static id acquireValue(id value, uintptr_t 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;
}
// 2) 释放老的关联值
static void releaseValue(id value, uintptr_t policy) {
if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
}
}
// 3) 从 Hash 表中取出值时
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
// 4)同上
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
Policy 的位标志声明:
// expanded policy bits.
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)
};
对比:
enum {
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. */
};
其二进制表示:
policy
COPY 1403 10101111011
RETAIN 1401 10101111001
COPY_NONATOMIC 3 00000000011
RETAIN_NONATOMIC 1 00000000001
ASSIGN 0 00000000000
---------------------------------------
SETTER_ASSIGN 0 00000000000
SETTER_RETAIN 1 00000000001
SETTER_COPY 3 00000000011
GETTER_READ (0 << 8) 00000000000
GETTER_RETAIN (1 << 8) 00100000000
GETTER_AUTORELEASE(2 << 8) 01000000000