目录
-
面试题
-
伪代码实现
-
Category添加属性的几种用法
-
Category为什么不能添加成员变量
-
底层源码解读
1、面试题
面试题一、Category能否添加成员变量?如果可以,如何给Category添加成员变量?
- 不能直接给Category添加成员变量
因为Category是在运行时决定的。编译时,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局。
Category中使用@property只会生成setter和getter方法的声明,并不会自动生成实例变量以及存取方法
- 可以间接实现Category有成员变量的效果
可以通过RunTime中关联对象在Category中为类添加成员变量
2、伪代码实现
我们知道,如果我们自己手动实现setter/getter方法,剩下的就是一个成员变量的存取了,下面用两种方法实现一下,看看是否存在瑕疵
-
创建全局对象(❌)
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYPerson *p1 = [[LYPerson alloc] init];
p1.name = @"1";
LYPerson *p2 = [[LYPerson alloc] init];
p2.name = @"2";
NSLog(@"p1 name = %@ ; p2 name = %@", p1.name, p2.name);
}
}
@implementation LYPerson (Test)
NSString *name_;
- (void)setName:(NSString *)name {
name_ = name;
}
- (NSString *)name {
return name_;
}
@end
2019-07-24 22:17:32.291885+0800 test[88511:509458] p1 name = 2 ; p2 name = 2
结论:如果创建多个实例对象,那么这些对象将公用一个全局对象,后面的将会把前面的值覆盖掉
-
创建全局字典
@implementation LYPerson (Test)
NSMutableDictionary *names_;
+ (void)load {
names_ = [NSMutableDictionary dictionary];
}
- (void)setName:(NSString *)name {
NSString *key = [NSString stringWithFormat:@"%p", self];
weights_[key] = key;
}
- (NSString *)name {
NSString *key = [NSString stringWithFormat:@"%p", self];
return weights_[key];
}
@end
- 能实现,但会存在问题,例如线程安全,比较麻烦
3、基本用法
-
用法一
#import "LYPerson+Test.h"
#import <objc/runtime.h>
@implementation LYPerson (Test)
static const char LYNameKey;
- (void)setName:(NSString *)name {
/**
对象关联
@param object 关联对象
@param key
@param value 关联值
@param policy 关联策略
objc_AssociationPolicy 对应修饰符
- OBJC_ASSOCIATION_ASSIGN = 0, assign
- OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
- OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
- OBJC_ASSOCIATION_RETAIN strong, atomic
- OBJC_ASSOCIATION_COPY copy, atomic
@return
*/
objc_setAssociatedObject(self, &LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &LYNameKey);
}
@end
-
用法二
#define MJNameKey @"name"
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, LYNameKey);
}
// @"name" 直接写出来的字符串,放在常量区
-
用法三
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @selector(name));
}
//隐式参数:get方法可以用_cmd代替;_cmd = @selector(name)
- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}
- 优点:1、可读性更高,2、有编译器提示
5、为什么不能添加成员变量
// Category在内存中的结构
struct _category_t {
const char *name; // 类名
struct _class_t *cls;
const struct _method_list_t *instance_methods; // 实例方法列表
const struct _method_list_t *class_methods; // 类方法列表
const struct _protocol_list_t *protocols; // 协议列表 (分类里面也能遵守协议)
const struct _prop_list_t *properties; // 属性列表 (分类里能实现属性)
};
- 分类底层结构决定了他不能添加成员变量,没有数组是用来存放成员变量的
- 用关联对象的方式给分类添加属性
不是存储在类对象、实例对象中,所以不会影响类对象、实例对象在内存中的存储结构
6、底层源码解读(待完善)
实现关联对象技术的核心对象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
总结:
- 关联对象并不是存储在被关联对象本身中
- 关联对象存储在全局的统一的一个AssociationManager中
- 设置关联对象为nil,就相当于移除关联对象
// 添加
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 获取
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
// 移除
objc_removeAssociatedObjects(<#id _Nonnull object#>)
objc_setAssociatedObject
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
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);
}
通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。
由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。
通过分析可以得出:一个实例对象会有一个 AssociationsManager 操作类和一个存储Map ObjectAssociationMap,
objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
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;
}
objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
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.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
网友评论