分类添加属性
回顾之前分类的结构:
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; };
从结构可知,里面存储着属性列表,可以给分类添加属性:
创建Person类,并创建分类,然后添加属性:
@interface Person (Test) @property(strong, nonatomic) NSString *name; @end @implementation Person (Test) @end 调用: Person *person = [Person new]; person.name = @"zhangsan";
运行结果:
-[Person setName:]: unrecognized selector sent to instance 0x1005b1c20 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x1005b1c20' *** First throw call stack: ( 0 CoreFoundation 0x00007fff204fb6af __exceptionPreprocess + 242 1 libobjc.A.dylib 0x00007fff202333c9 objc_exception_throw + 48 2 CoreFoundation 0x00007fff2057dc85 -[NSObject(NSObject) __retain_OA] + 0 3 CoreFoundation 0x00007fff2046307d ___forwarding___ + 1467 4 CoreFoundation 0x00007fff20462a38 _CF_forwarding_prep_0 + 120 5 OCTest 0x0000000100003f10 main + 80 6 libdyld.dylib 0x00007fff203a4621 start + 1 7 ??? 0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x1005b1c20' terminating with uncaught exception of type NSException
发现name默认并没有实现setter方法,getter方法也未实现;
分类如何添加属性?
可以通过以下几种方式: 1. 可以使用全局变量,自己实现getter和setter,利用给全局变量的赋值方式实现
2. 可以使用字典,在load方法里面进行初始化字典,通过setter和getter对字典进行设置值和取值(注意线程安全问题)
3. 关联对象
关联对象相关函数以及参数说明
- 导入头文件
#import <objc/runtime.h>
- 设置值
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
- Object:当前对象;value:要设置的值;
key:可以使用以下几种方式:
1 |
static void *Key = &Key; |
2 | static char Key; |
3 |
属性名作为key; |
4 |
getter的方法名 @selector 作为key(_cmd); |
- objc_AssociationPolicy对应关系:
• typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { • OBJC_ASSOCIATION_ASSIGN = 0, //assign • OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //strong, nonatomic • OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy, nonatomic • OBJC_ASSOCIATION_RETAIN = 01401, //strong, atomic • OBJC_ASSOCIATION_COPY = 01403 //copy, atomic • };
取值
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull
- Object:当前对象
key:同上
关联对象的使用
测试代码:
@implementation Person (Test) - (void)setName:(NSString *)name { objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)name { return objc_getAssociatedObject(self, _cmd); } @end
调用以及运行结果:
Person *person = [Person new]; person.name = @"zhangsan"; NSLog(@"%@", person.name); //打印结果zhangsan
关联对象的实现原理?
源码分析objc_setAssociatedObject源码实现:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { _object_set_associative_reference(object, key, value, policy); }
接下来调用_object_set_associative_reference:
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) { // This code used to work when nil was passed for object and key. Some code // probably relies on that to not crash. Check and handle it explicitly. // rdar://problem/44094390 if (!object && !value) return; //将object封装成为了DisguisedPtr DisguisedPtr<objc_object> disguised{(objc_object *)object}; //将policy以及value封装成为了ObjcAssociation ObjcAssociation association{policy, value}; // 根据policy确定策略对value的处理是retain还是copy association.acquireValue(); bool isFirstAssociation = false; //局部作用域 { //初始化AssociationsManager AssociationsManager manager; //获取全局的AssociationsHashMap AssociationsHashMap &associations(manager.get()); //如果value有值 if (value) { //try_emplace函数,disguised(将Object传入后封装的结果)作为key查找 //如果在associations map中,就把TheBucket作为参数对DenseMapIterator进行初始化,接着调用make_pair; //key没在associations map中就把disguised作为key,ObjectAssociationMap 作为value存入TheBucket中 auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) {//判断第二个存不存在 /* it's the first association we make */ isFirstAssociation = true; } /* establish or replace the association */ auto &refs = refs_result.first->second; //查找当前的key是否有association关联对象 auto result = refs.try_emplace(key, std::move(association)); if (!result.second) {//如果结果不存在交换policy和value association.swap(result.first->second); } } else {//value为空 //利用disguised查找ObjectAssociationMap,返回一个迭代器 auto refs_it = associations.find(disguised); //如果找到了 if (refs_it != associations.end()) { //拿到ObjectAssociationMap auto &refs = refs_it->second; //ObjectAssociationMap表中查找key对应的association auto it = refs.find(key); //如果找到了 if (it != refs.end()) { //交换 association.swap(it->second); //从表中移除关联 refs.erase(it); if (refs.size() == 0) {//没有关联的值了,从associations表中擦除ObjectAssociationMap表 associations.erase(refs_it); } } } } } if (isFirstAssociation) object->setHasAssociatedObjects(); //isa对应的符号位设置为存在关联对象 // release the old value (outside of the lock). association.releaseHeldValue(); }
根据代码可知(可参考注释),调用关系总结如下:1. AssociationsManager中有
AssociationsHashMap class AssociationsManager { AssociationsHashMap &get() { return _mapStorage.get(); } };
2. AssociationsHashMap中将当前类的对象object封装成的DisguisedPtr作为
AssociationsHashMap的key,ObjectAssociationMap作为value,进行存储: typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
3. ObjectAssociationMap中将我们传入的key作为key,ObjcAssociation作为value进行存储:
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
4. ObjcAssociation中存储着对应的策略和值:
class ObjcAssociation { uintptr_t _policy; id _value; };
调用示意图:
总结
能否给分类添加属性或者成员变量?
- 可以添加属性,由分类的结构可知,分类中存储的有属性列表,但是不能够为分类添加成员变量,因为不存在成员变量列表;
- 添加属性的时候,当声明属性之后,系统并未为分类的属性实现setter和getter方法,故需要自己手动实现属性的getter和setter,由于不存在_开头的成员变量,需自己维护设置值和取值对应的操作,可以通过全局变量、全局字典(考虑线程安全问题)、关联对象等方式进行添加;
关联对象相关函数?
- objc_setAssociatedObject(设置值):传入key和策略(根据属性的类型选择对应的策略)等参数
- objc_getAssociatedObject(取值):传入Object和key
关联对象实现原理?
- 关联对象并不是存储在被关联对象本身内存中;
- 关联对象存储在全局的统一的一个AssociationsManager管理的HashMap中;
- 如果设置关联对象为nil,就相当于是移除关联对象;
本文部分内容来可能来源于网络,发布的内容如果侵犯了您的权益,请联系我们尽快删除!