开篇还是放上几道面试题
Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
- 首先大家应该都知道分类可以添加属性,但是不可以添加成员变量,同时自动声明了set方法,get方法,此时获取方法列表是不存在set,fet方法的,必须实现才会在方法列表中出现,大家可能会想那我就手动实现set方法,如下图
分类添加了个属性 @property (nonatomic,copy)NSString *categoryName1;
- 发现分类中根本没有这个成员变量,其实如果看了分类的底层结构category_t应该就知道那里压根就没有存储成员变量的地方,所以想通过正常的方式直接添加成员变量是不可以的,但是可以通过关联对象,来间接添加成员变量
关联对象提供了以下API
添加关联对象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
获得关联对象
id objc_getAssociatedObject(id object, const void * key)
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
关联对象基本用法
1.以全局变量里面存放着自己的地址为key
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
2.以全局变量的地址为key
static char MyKey;
pobjc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
pobjc_getAssociatedObject(obj, &MyKey)
3.使用属性名作为key
objc_setAssociatedObject(obj, @"name", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
pobjc_getAssociatedObject(obj, @"name");
4.使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(setName), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//get方法里面,下面这两种写法都可以
objc_getAssociatedObject(obj, @selector(name))
objc_getAssociatedObject(obj, _cmd)
objc_AssociationPolicy policy的用法
开始分析源码 源码地址
- 搜索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);
}
- 看到
AssociationsManager
,AssociationsHashMap
,ObjectAssociationMap
,ObjcAssociation
这四个类,接下来我们一一在仔细查看对应的类结构 - AssociationsManager结构如下
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;
}
};
- 由上面代码看到此类只有一个成员变量,也就是说这个manager管理着这个map,所有的信息都存在这个map里
static AssociationsHashMap *_map;
- 接下来查看这个
AssociationsHashMap
class AssociationsHashMap : public unordered_map {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
- 由上面代码可以看出这个map的key和value类型结构为:
disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap
- 那么在接着看
ObjectAssociationMap
class ObjectAssociationMap : public std::map {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
- 可以看到key value的结构如下:
void * ObjcAssociation
void * ObjcAssociation
void * ObjcAssociation
- 接着再看
ObjcAssociation
的结构
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
- 从上面代码可以看到ObjcAssociation类里有两个成员变量,一个是_policy策略,一个是_value值
-
再具体点分析如下图所示:
-
最终得到结论:
好了看完了底层结构,在讲讲项目中怎么使用
- 大家可能注意到,policy没有weak'类型,那得怎么实现weak关联对象呢?
我们可以关联个retain对象,让这个对象持有他
恰好系统有提供这个类NSMapTable,这个map可以设置weak引用里面的值,和我们常用的字典类似
@interface NSObject()
@property (nonatomic,strong) NSMapTable *keyMapTable;
@end
@implementation NSObject (Association)
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key
{
[self.keyMapTable setObject:object forKey:key];
}
-(id)weakAssociatedObjectForKey:(NSString *)key
{
return [self.keyMapTable objectForKey:key];
}
#pragma mark - 私有方法
-(NSMapTable *)keyMapTable
{
if (objc_getAssociatedObject(self, _cmd)==nil) {
self.keyMapTable=[NSMapTable weakToWeakObjectsMapTable];
}
return objc_getAssociatedObject(self, _cmd);
}
-(void)setKeyMapTable:(NSMapTable *)keyMapTable
{
objc_setAssociatedObject(self, @selector(keyMapTable), keyMapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 再来看一个问题,我们大家可能在添加retain关联对象的时候,经常用getter方法作为key,但是用起来每增加个成员变量都得写一遍很麻烦,所以大家可能就会想到使用属性名作为key,这样写一遍公共的方法就好了,但是我告诉你这样是存在潜在问题的,那么就来看看下面代码有啥问题:
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
objc_setAssociatedObject(self, &key, object, OBJC_ASSOCIATION_RETAIN);
}
- (id)associatedObjectForKey:(NSString *)key {
return objc_getAssociatedObject(self, &key);
}
从上面的代码可以看出就是使用传进来的字符串为地址,每次传进来的字符串一样,大家可能就以为这个地址就一样了,其实不然,当字符串相同地址不一样时,set完在取值就取不到了,值为nil
- 上图这种情况,虽然字符串的值相同,但是地址值不同,所以导致取出的值为nil,常见场景就是set的时候用的是常量字符串,取值的时候用的是后台返回的数据里的值导致不一样,如果你取值时还是用常量字符串那么就没问题,因为常量字符串地址一样
- 解决方式如下
@interface NSObject()
@property (nonatomic,strong) NSMutableDictionary *strongKeyBuffer;
@end
@implementation NSObject (Association)
-(NSMutableDictionary *)strongKeyBuffer{
if (objc_getAssociatedObject(self, _cmd)==nil) {
self.strongKeyBuffer=[NSMutableDictionary dictionary];
}
return objc_getAssociatedObject(self, _cmd);
}
-(void)setStrongKeyBuffer:(NSMutableDictionary *)strongKeyBuffer{
objc_setAssociatedObject(self, @selector(strongKeyBuffer), strongKeyBuffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
/**
* set 方法,以供以后添加属性时候给这个属性的 set 方法调用
* @param object 要关联的对象,也就是要设置的新的属性值
* @param key 属性名称,传入新增属性的名称
解决常亮字符串地址不一致问题 导致取值为nil
**/
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue]; // 先获取key
if (cKey == NULL) { // 字典中不存在就创建
cKey = key.UTF8String;
self.strongKeyBuffer[key] = [NSValue valueWithPointer:cKey];
}
objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark - get 方法,以供以后添加属性时候给这个属性的 get 方法调用
- (id)associatedObjectForKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue];
if (cKey == NULL) {
return nil;
} else {
return objc_getAssociatedObject(self, cKey);
}
}
用一个可变字典存放添加过哪些key,和真正set时用的地址是啥,这里这么用
[NSValue valueWithPointer:key.UTF8String];
当然你也可以不这么用,主要思想是用个字典存储添加过的key,然后取值的时候通过传进来的key,找到set时用的地址值,然后再用此地址值取值,所以就不会为nil了
下面附上我封装的类.h .m文件
NSObject+Association.h
//
// NSObject+Association.h
// NSObject+Association
//
// Created by liusong on 2018/4/3.
// Copyright © 2018年 liusong. All rights reserved.
//
#import
@interface NSObject (Association)
/** 所有要增加的属性的 set 方法都可以调用这个方法来实现 */
- (void)setAssociatedObject:(id)object forKey:(NSString *)key;
/** 所有要增加的属性的 get 方法都可以调用这个方法来实现 */
- (id)associatedObjectForKey:(NSString *)key ;
//以下为增加获取 weak 属性
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key;
- (id)weakAssociatedObjectForKey:(NSString *)key ;
@end
NSObject+Association.m
//
// NSObject+Association.m
// NSObject+Association
//
// Created by liusong on 2018/4/3.
// Copyright © 2018年 liusong. All rights reserved.
//
#import "NSObject+Association.h"
#import
@interface NSObject()
@property (nonatomic,strong) NSMutableDictionary *strongKeyBuffer;
@property (nonatomic,strong) NSMapTable *keyMapTable;
@end
@implementation NSObject (Association)
/**
* set 方法,以供以后添加属性时候给这个属性的 set 方法调用
* @param object 要关联的对象,也就是要设置的新的属性值
* @param key 属性名称,传入新增属性的名称
解决同一字符串地址不一致问题 导致取值为nil
**/
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue]; // 先获取key
if (cKey == NULL) { // 字典中不存在就创建
cKey = key.UTF8String;
self.strongKeyBuffer[key] = [NSValue valueWithPointer:cKey];
}
objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark - get 方法,以供以后添加属性时候给这个属性的 get 方法调用
- (id)associatedObjectForKey:(NSString *)key {
const char *cKey = [self.strongKeyBuffer[key] pointerValue];
if (cKey == NULL) {
return nil;
} else {
return objc_getAssociatedObject(self, cKey);
}
}
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key
{
[self.keyMapTable setObject:object forKey:key];
}
-(id)weakAssociatedObjectForKey:(NSString *)key
{
return [self.keyMapTable objectForKey:key];
}
#pragma mark - 私有方法
-(NSMapTable *)keyMapTable
{
if (objc_getAssociatedObject(self, _cmd)==nil) {
self.keyMapTable=[NSMapTable weakToWeakObjectsMapTable];
}
return objc_getAssociatedObject(self, _cmd);
}
-(void)setKeyMapTable:(NSMapTable *)keyMapTable
{
objc_setAssociatedObject(self, @selector(keyMapTable), keyMapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end