首先我们来简单的描述一下分类的一些基本概念:
1、用来给类添加新方法
2、不能给类添加成员属性,添加了成员变量,也无法取到
3、注意:其实可以通过runtime给分类添加属性
4、分类中用@property定义变量,只会生成变量的getter,setter方法声明,不能生成方法实现和带下划线的成员变量。
那么问题来了, 分类是不能直接添加属性的,那么要给分类添加属性,那就需添加关联对象或者重写,在.m
文件添加setter
方法的实现。
请看下面代码,在main.m中添加了对Person
类的分类,在分类中有两个属性,且在main函数中创建Person
对象并对分类的属性赋值,其中,分类的属性利用runtime
来实现了getter
,setter
方法。
#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "Person+LGEXT.h"
@interface Person (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end
@implementation Person (LG)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
- (void)setCate_name:(NSString *)cate_name{
/**
1: 对象
2: 标识符
3: value
4: 策略
*/
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
return objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
person.cate_name = @"OC";
NSLog(@"%p",person);
}
return 0;
}
我们首先在setter
方法出打上断点,来看看它是否会执行这个方法:
可以看到,会执行这个方法,而当我们点击方法进去之后,你会发现,他们的整个方法实现都在一块,首先SetAssocHook
,然后_base_objc_setAssociatedObject
,之后就会执行_object_set_associative_reference
方法。
而_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;
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 包装了一下 对象
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 包装一下 policy - value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
object->setHasAssociatedObjects();
}
/* establish or replace the association */
auto &refs = refs_result.first->second; // 空的桶子
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
上面的代码很多,将部分代码收起来,可以看到一共就这么几行代码:
iShot2020-10-21 16.06.38.png
从下图可以看到,167行代码中的DisguisedPtr
是对对象进行包装,
而ObjcAssociation
的作用同样是对policy
和value
进行包装,其中value
的值是上面代码对cate_name
赋的值OC
;
看下图,acquireValue()
的作用其实是对_value
和policy
的判断;
接下来就是172-201行的局部作用空间代码,如下图:
iShot2020-10-21 16.23.30.png iShot2020-10-21 16.24.16.png
首先看到AssociationsManager
,看上图,它的重要的地方其实是方框中的c++
代码。至于它的作用,下面我用一个例子来表示:
将main.m
改成main.mm
,在main.m中写一个结构体:
struct Objc {
Objc() { printf("来了");}
~Objc() { printf("走了"); }
};
然后在函数中调用:
iShot2020-10-21 16.27.50.png看最终执行完毕的结果:
iShot2020-10-21 16.28.09.png
这个函数的作用其实就是在程序开始时执行一次,在函数结束完毕后,再执行一次。而程序中是对代码进行加锁和解锁;它可以有多个这样的AssociationsManager
。
而AssociationsHashMap
从名字看,像一个哈希表,里面有很多关联对象,里买呢的类型类似于key-value健值对的形式存在;继续执行内部代码块:
当执行到if (refs_result.second) {
行时,我们在控制台来看一下一些属性的值:
(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069408092480)
(lldb) p association
(objc::ObjcAssociation) $1 = {
_policy = 3
_value = 0x0000000100004038 "OC"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
Buckets = 0x0000000000000000
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $4 = {
first = {
Ptr = 0x0000000101006810
End = 0x0000000101006850
}
second = true
}
(lldb)
看到refs_result
的时候,可以看到$4
的类型很长,我们不用去管它,去看看给它赋值的函数try_emplace
:
看下图,它其实是分两部分,一部分是false
,一部分是true
,从判断中可以看出来,当TheBucket
为空时,就会执行InsertIntoBucket
,并返回true
,当TheBucket
有值之后,就会返回false
;
下图是当value
有值的时候,程序执行的代码块:
根据代码执行的流程分析的结果,程序第一次进入try_emplace
走的是InsertIntoBucket
,而在第二次执行try_emplace
走的同样是InsertIntoBucket
方法:
也就是说,第一次是从哈希表读取refs_result
,而disguised
是传入的值,接着判断refs_result.second
是不是第一次来,当结果为真,调用setHasAssociatedObjects()
函数。
接着引用refs_result.first
的second
值,对引用的refs
进行第二次调用try_emplace
,接着插入ObjcAssociation
的值。
下面是分别进入两次后的TheBucket
的值:
(lldb) p TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $0 = 0x000000010068b5b0
(lldb) p TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $1 = 0x0000000102014cd8
(lldb)
接下来看一下当value
为空时,程序执行else
部分:
上图部分代码的作用其实就是将关联对象
进行消除;
总结: 其实就是两层哈希map , 存取的时候两层处理(类似二位数组)
网友评论