这篇文们来讲解下扩展和关联对象
扩展分析
- 首先我们先在
main.m
文件中实现扩展
- 然后
clang -rewrite-objc main.m -o main.cpp
生成main.cpp文件
,来查看下底层实现
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSString *_age;
NSString *_ext_name;
};
static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
static NSString * _I_Person_age(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, NSString *age) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _age), (id)age, 0, 1); }
static NSString * _I_Person_ext_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_ext_name)); }
static void _I_Person_setExt_name_(Person * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _ext_name), (id)ext_name, 0, 1); }
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[14];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
14,
{{(struct objc_selector *)"hello", "v16@0:8", (void *)_I_Person_hello},
{(struct objc_selector *)"hello_ext", "v16@0:8", (void *)_I_Person_hello_ext},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
{(struct objc_selector *)"age", "@16@0:8", (void *)_I_Person_age},
{(struct objc_selector *)"setAge:", "v24@0:8@16", (void *)_I_Person_setAge_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_Person_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_Person_setExt_name_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_},
{(struct objc_selector *)"age", "@16@0:8", (void *)_I_Person_age},
{(struct objc_selector *)"setAge:", "v24@0:8@16", (void *)_I_Person_setAge_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_Person_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_Person_setExt_name_}}
};
- 由
main.cpp
文件我们看出,扩展中实现的属性方法
是和主类
放在一起的,并没有像分类
那样,生成类似于category_t
的结构体
加载分析
准备工作
首先声明一个扩展文件,并在realizeClassWithoutSwift
方法中打个断点
由上图的
count
可以看出有8
个方法,我们大体猜测到扩展中的方法是和本类的方法存放在一起的,都存放在ro中
,接下来我们继续打印来验证我们的猜测
这打印出来的方法就是我们实现的方法和属性的
setter、getter
方法
【结论】:扩展是在编译期就已经加载好的,和本类是存放在一起的
扩展和分类对比
分类:
- 用来给类添加新方法或者重写本类中的方法
- 不能给类添加
成员变量
,需要使用runtime
中的关联对象来添加 - 分类中用
@property
定义变量,只会生成变量的getter、setter方法的声明,不能生成方法实现和带下划线的成员变量。 - 分类的加载发生在
运行时
扩展
- 是一个特殊的分类,也称作
匿名分类
- 可以给
本类
添加成员变量、属性、方法
,不过添加的这些都是私有的
- 扩展的加载发生在
编译期
关联对象
我们知道分类声明属性
添加属性
,是不会生成 成员变量的
,于是这个问题我们来通过关联对象解决
我们先创建一个分类
@interface Person (SH)
@property (nonatomic,copy)NSString *cat_name;
@end
#import "Person+SH.h"
#import
@implementation Person (SH)
- (void)setCat_name:(NSString *)cat_name{
objc_setAssociatedObject(self, @"cat_name", cat_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cat_name{
return objc_getAssociatedObject(self, @"cat_name");
}
进入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;
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));
//object:关联对象。 key:标记。value:关联对象的值 policy:策略(就是strong copy什么的)
// 将object 统一包装成 DisguisedPtr 这种数据结构
DisguisedPtr disguised{(objc_object *)object};
// 将policy 和 value 包装成 ObjcAssociation数据结构
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
....
}
}
}
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
ObjcAssociation(const ObjcAssociation &other) = default;
ObjcAssociation &operator=(const ObjcAssociation &other) = default;
....
}
我们在这给
cat_name赋值
我们看到这个
ptr
就是我们的Person类的对象
,这里统一将person对象
统一包装成DisguisedPtr这个数据结构
这里看到将
value和policy
统一封装成ObjcAssociation
数据结构
AssociationsManager和AssociationsHashMap
在讲这里之前,先补充一个小知识点
- 当我们调用
SHObjc
的时候,会自动调用构造函数
- 当
autoreleasepool
的作用域
结束时,会自动嗲用析构函数
接下来我们来分析AssociationsManager
源码
class AssociationsManager {
using Storage = ExplicitInitDenseMap, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
- 在这我们发现
AssociationsManager
并不是一个单例,而是通过构造函数加锁、析构函数解锁
,来达到线程安全。 -
AssociationsManager
只是用来调用AssociationsHashMap
的而已,AssociationsHashMap
是一个单例,因为它通过_mapStorage.get()
获取,_mapStorage
是一个全局静态变量,放在任何地方都是唯一的。
接下来我们来验证下到底哪一个是全局单例
- 由上图的
lldb
打印我们得出结论AssociationsManager
不是单例 -
AssociationsHashMap
是单例
流程分析
分析下这个数据结构
$1
,这里面有两个数据first
和 second
,second
是一个布尔类型
,first
是个对值
,包含Ptr和End
接下来我们来研究下这段代码
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
首先传入的是disguised
也就是我们的object
,还有个ObjectAssociationMap
进入try_emplace
函数
-
BucketT *TheBucket;
创建一个空的BucketT
- 调用
LookupBucketFor(Key, TheBucket)
,传入key
是disguised
,和TheBucket
。
进入LookupBucketFor
函数
- 注意这里有两个
LookupBucketFor
函数,我们这里先调用是第二个参数没有const 修饰
的,这里先调用下面的函数 - 函数内部又去调用上面的
LookupBucketFor
,FoundBucket
是指针传递,也就是说在函数内部改变,函数外部的参数也会跟着改变
进入上面的LookupBucketFor
函数
template
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
// Val 是 disguised(包装的对象), hash 函数得到下标
// 根据对象进行hash下标的计算
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
//while 是死循环 查找位置
//找到了 对FoundBucket赋值,返回true
//找不到 再hash查找
while (true) {
//开启循环,下标平移,根据下标寻找bucket
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
// 若果说bucket和val(object),如果说键值比对相等,则返回true,并将形参赋值
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
//如果没有找到 同样讲形参赋值,返回false
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
//小标平移 再hash
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
- 这里通过
getHashValue(Val) & (NumBuckets-1)
哈希函数,计算出bucketNo的下标 -
while
循环,根据下标找位置,找到位置将地址赋值给FoundBucket
,当前我们这个地方返回的false,没有找到位置
回到try_emplace
函数
- 当前
TheBucket
是存放在AssociationsHashMap
中,但是TheBucket
没有东西,所以这时我们往里面插入内容 - 调用
InsertIntoBucket
,传入TheBucket
,传入key也就是disguised
,传入Args也就是ObjectAssociationMap{}
。
进入InsertIntoBucket
函数
template
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
//进行扩容
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
// 将Key也就是disguised 赋值到 TheBucket 的 first
TheBucket->getFirst() = std::forward(Key);
// 将 ObjectAssociationMap{} 赋值到 TheBucket 的 second
::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);
return TheBucket;
}
回到_object_set_associative_reference
- 这个时候已经看到了
const void *, objc::ObjcAssociation
数据结构,这个属于ObjectAssociationMap
的数据结构 - 这个时候value、policy还没有存,还在association里。
- 这里的
refs_result.first
是获取bucket
,bucket.second
获取的是上面创建的ObjectAssociationMap
- 接下来又是调用
try_emplace
函数,传入key
(这里是属性名称),和association
。将它们以键值对的方式存储到refs的bucket中,到此两层哈希存储完成。
关联对象插入nil 流程
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);
}
}
}
- 在
associations
也就是AssociationHashMap
中查找disguised
- 如果没有则进行
erase 清空操作
关联对象:设值流程
1.创建一个AssociationsManager
管理类
2.获取唯一的全局静态哈希Map
3.判断插入关联值是否存在
3.1:如果存在走第4步
3.2:不存在:走关联对象插入空流程
4.创建一个空的ObjectAssoctionsMap去取查询的键值对
5.如果发现没有这个key,就插入一个空的bucketT并且返回false
6.标记对象存在关联对象
7.将当前的修饰策略和值 组成一个ObjectAssoctions替换原来的BucketT中的空值
8.标记ObjectAssoctionsMap的第一次为false