- 给类添加属性会自动生成带下划线的成员变量,也会生成setter和getter方法的声明及实现
- 给分类添加属性不会生成带下划线的成员变量,只会生成setter和getter方法的声明
- 不能直接给Category添加成员变量,但是可以通过关联对象间接实现Category有成员变量的效果
一、给分类添加属性方法一:用字典存储属性的值
#import "Person.h"
@interface Person (Test)
@property (assign, nonatomic) int weight;
@end
#import "Person+Test.h"
#define WeightKey [NSString stringWithFormat:@"%p", self]
@implementation Person (Test)
NSMutableDictionary *weight_;
+ (void)load {
weight_ = [NSMutableDictionary dictionary];
}
- (void)setWeight:(int)weight {
weight_[WeightKey] = @(weight);
}
- (int)weight {
return [weight_[WeightKey] intValue];
}
@end
缺点:太麻烦,需考虑线程安全等问题
二、给分类添加属性方法二:通过关联对象实现
方式一:用NameKey的内存地址值作为key
static const void *NameKey = &NameKey; //占8个字节
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, NameKey, name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, NameKey);
}
方式二:
#define NameKey @"name"
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, NameKey, name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, NameKey);
}
方式三:
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
//_cmd = @selector(name)
return objc_getAssociatedObject(self, @selector(name));
//或者
//return objc_getAssociatedObject(self, _cmd);
}
方式四:
static const char NameKey; //占1个字节
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, &NameKey, name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &NameKey);
}
三、关联对象的原理
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象存储在全局的统一的一个AssociationManager中
- 设置关联对象为nil,就相当于是移除关联对象
源码 objc-runtime.mm
// 取值
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
// 赋值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
源码 objc-references.mm
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
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) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
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);
}