在分类中声明属性的时候,只会自动生成方法的声明,并不会生成方法的实现和成员变量,举例说明:
#import "Person.h"
@interface Person (Test)
@property (assign, nonatomic) int weight;
- (void)setWeight:(int)weight;
- (int)weight;
@end
即通过上述代码声明 weight
属性时,其只会声明 setWeight:
方法和 weight
方法,却并不会自动生成这两个方法的实现和 _weight
变量,如果直接在头文件里声明成员变量则编译不通过。
手动实现属性效果
如果想自己实现类似属性的用法,可以用下面的方式来实现效果:
#import "Person.h"
@interface Person (Test)
@property (assign, nonatomic) int weight;
@end
@implementation Person (Test)
NSMutableDictionary *weights_;
+ (void)load
{
weights_ = [NSMutableDictionary dictionary];
}
- (void)setWeight:(int)weight
{
NSString *key = [NSString stringWithFormat:@"%p", self];
weights_[key] = @(weight);
}
- (int)weight
{
NSString *key = [NSString stringWithFormat:@"%p", self];
return [weights_[key] intValue];
}
@end
这种方案的缺点是: weights_
这个字典存在内存泄漏的问题。
关联对象
分类不能直接添加成员变量,但可以通过关联的方式间接添加成员变量。将传入的属性值和对象关联起来,所以叫关联对象,我们先来看下关联对象的实现方式
#import "Person.h"
@interface Person (Test)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end
#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
static const void *nameKey = &nameKey;
static const void *weightKey = &weightKey;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, nameKey);
}
- (void)setWeight:(int)weight
{
objc_setAssociatedObject(self, weightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight
{
return [objc_getAssociatedObject(self, weightKey) intValue];
}
@end
此时我们即可像正常访问其他属性一样对 name
和 weight
属性进行取值和赋值
添加关联对象
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
/* 参数解读
* object : 给哪个对象添加属性。这里要给自己添加属性,用self。
* key : 属性名,根据key获取关联对象的属性的值。传入一个指针即可。在objc_getAssociatedObject中通过次key获得属性的值并返回。
* value : 关联的值,也就是通过set方法传入的值给属性。
* policy : 策略,属性以什么形式保存。
*/
其中参数policy对应的枚举值如下:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
// 指定一个弱引用相关联的对象
OBJC_ASSOCIATION_ASSIGN = 0,
// 指定相关对象的强引用,非原子性
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
// 指定相关的对象被复制,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
// 指定相关对象的强引用,原子性
OBJC_ASSOCIATION_RETAIN = 01401,
// 指定相关的对象被复制,原子性
OBJC_ASSOCIATION_COPY = 01403
};
分别对应的修饰符为:
获取关联对象
objc_getAssociatedObject(id object, const void *key);
/* 参数解读
* object : 获取哪个对象里面的关联的属性。
* key : 属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
移除所有关联对象
- (void)removeAssociatedObjects
{
// 移除所有关联对象
objc_removeAssociatedObjects(self);
}
通过关联对象添加的成员变量在内存中不是存储在实例对象中,也不是存储在类对象中,原理后面有解读
key的设置方式
其中设置 key
可以有多种方式,一种是上面所示的那样通过如下方式:
static const void *nameKey = &nameKey;
static const void *weightKey = &weightKey;
这种方式的缺点一是比较繁琐,二是如果不加 static
关键字修饰,则可以在其他文件中通过 extern const void *nameKey
这种方式来访问 nameKey
,static
在这里的作用是表示仅限当前文件
第二种,可以通过下面这种
static const char *nameKey;
- (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));
}
关联对象的原理
实现关联对象技术的核心:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
其中Map同平时使用的字典相似,我们用下面这张图来演示这四者之间的关系:
我们分别进入这4个对象内部查看它们之间的关系:
AssociationsManager
通过 AssociationsManager
内部源码发现,其内部有一个 AssociationsHashMap
对象:
AssociationsHashMap
我们继续查看 AssociationsHashMap
内部的源码,发现 AssociationsHashMap
继承自 unordered_map
:
我们看到里面还包括黄框标注的 ObjectAssociationMap
,我们先继续查看unordered_map
内部源码:
从 unordered_map
源码中我们可以看出 _Key
和 _Tp
也就是前两个参数对应着map中的 Key
和 Value
,那么对照上面 AssociationsHashMap
内源码发现_Key
中传入的是 disguised_ptr_t
,_Tp
传入的值则为 ObjectAssociationMap*
( AssociationsHashMap
图中用红线标注)。
同样我们来到黄框标注 ObjectAssociationMap
中,发现 ObjectAssociationMap
中同样也是将 ObjcAssociation
以参数形式传入,并以 key
、Value
的方式存储着 ObjcAssociation
( AssociationsHashMap
图中用黄线标注)。
我们继续来到 ObjcAssociation
源码中查看:
我们发现 ObjcAssociation
存储着 _policy
、_value
,而这两个值我们发现正是我们调用添加关联对象 objc_setAssociatedObject
函数传入的值。也就是说我们在调用 objc_setAssociatedObject
函数中传入的 value
和 policy
这两个值最终是存储在 ObjcAssociation
中的。
现在我们已经对 AssociationsManager
、 AssociationsHashMap
、 ObjectAssociationMap
、ObjcAssociation
四个对象之间的关系有了简单的认识,那么接下来我们回到 objc_setAssociatedObject
源码,具体查看一下objc_setAssociatedObject
函数中传入的四个参数究竟做了哪些操作:
首先根据传入的 value
经过 acquireValue
函数处理获取 new_value
,acquireValue
函数内部其实是通过对策略的判断返回不同的值:
创建 AssociationsManager
类型的 manager
,拿到 manager
内部的AssociationsHashMap
,即 Associations
。
传入的 object
参数经过 DISGUISE
函数被转化为了 disguised_ptr_t
类型的disguised_object
。
其实 DISGUISE
函数内部只是对 object
做了位运算:
我们看到被处理成 new_value
的 value
,和 policy
一起被存入了ObjcAssociation
中,而 ObjcAssociation
对应我们传入的 key
被存入了ObjectAssociationMap
中,disguised_object
和 ObjectAssociationMap
则以key-value
的形式对应存储在 Associations
中也就是 AssociationsHashMap
中。
总结
- 一个实例对象就对应一个
ObjectAssociationMap
,而ObjectAssociationMap
里面存储着多个此实例对象的关联对象的key
以及ObjcAssociation
,ObjcAssociation
中存储着关联对象的value和policy。 - 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个
AssociationsManager
中 - 如果设置关联对象为nil,就相当于是移除关联对象。
- 这种毕竟不是属性,没有弱引用
源码的解读在objc4的objc-references.mm
中
网友评论