前言
上一篇 iOS 底层原理:类的加载原理下(分类),对分类加载进行了分析,今天将进行类扩展和关联对象的探索。
准备工作
一、类扩展分析
什么是类扩展
- 这就是
类扩展,我们平时用的是非常多的,它也叫没有名字的分类。
底层 C++ 分析类扩展
在main.m中添加如下代码:
@interface SSLPerson : NSObject
@property (nonatomic, copy) NSString *name;
- (void)say1;
@end
@interface SSLPerson ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_say1;
@end
@implementation SSLPerson
+ (void)load {}
- (void)say1
{
NSLog(@"SSLPerson-----say1");
}
- (void)ext_say1
{
NSLog(@"SSLPerson-----ext_say1");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
类扩展要放在声明之后,实现之前,否则会报错。
打开terminal,clang -rewrite-objc main.m -o main.cpp,得到main.cpp。
...
struct SSLPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSString *_ext_name;
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[10];
} _OBJC_$_INSTANCE_METHODS_SSLPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
10,
{{(struct objc_selector *)"say1", "v16@0:8", (void *)_I_SSLPerson_say1},
{(struct objc_selector *)"ext_say1", "v16@0:8", (void *)_I_SSLPerson_ext_say1},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_SSLPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_SSLPerson_setName_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_SSLPerson_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_SSLPerson_setExt_name_},
};
...
- 可以看到,
类扩展并没有像分类一样,生成类似category_t那种数据结构。 - 类扩展的成员变量
_ext_name和主类的_name是放在一起的,类扩展的方法和主类的方法也是被放在了同一个列表内,那么这些方法是在什么时机加入进去的呢,接下来继续分析。
类扩展 加载分析
为方便调试,重新创建下类:
非懒加载类时会调用realizeClassWithoutSwift函数,我们在其中添加断点进行测试,运行程序进入断点:
lldb打印:
- 可以看到,
ext_say1、setName、ext_name这个时候已经存在了,说明类扩展是伴随这类的内容一起加载进来了,可以说是属于类的一部分。
类扩展 VS 分类
1. category:分类
- 专门用来给类添加
新的方法。 - 不能给类添加成员属性,添加了成员变量,也无法取到。
- 注意:其实可以通过
runtime给分类添加属性。 - 分类中用
@property定义变量,只会生成变量的getter、setter方法的声明,不能生成方法实现和带下划线的成员变量。
2. extension:类扩展
- 可以说成是特殊的分类,也称作
匿名分类。 - 可以给类添加成员变量,但是是
私有变量。 - 可以给类添加方法,也是
私有方法。 - 在编译期,和主类中的内容
一同加载。
二、关联对象 引入
我们知道分类中的属性是不会自动生成成员变量的,这个问题可以通过关联对象来解决。
创建SSLPerson+SSL1分类:
@interface SSLPerson (SSL1)
@property (nonatomic, copy) NSString *cate_name;
@end
#import <objc/runtime.h>
@implementation SSLPerson (SSL1)
- (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
点击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进行详细分析。
三、关联对象底层分析 初探
_object_set_associative_reference
进入_object_set_associative_reference:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{ // object 是关联对象,key 是一个标识 key,value 是值,policy 是存储策略。
// 将 object 统一包装成 DisguisedPtr 这种数据结构
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 将 policy、value 包装成 ObjcAssociation 这种数据结构
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
// 关键代码
{
AssociationsManager manager;
...
}
}
template <typename T>
class DisguisedPtr {
uintptr_t value;
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;
...
}
DisguisedPtr<objc_object>将object进行了包装,ObjcAssociation将policy、value进行了包装。- 函数中关于
AssociationsManager网上有很多文章说它是单例,那它到底是不是单例呢,下面进行分析。
AssociationsManager 分析
1. 构造函数、析构函数 知识补充
创建SSLObjc结构体,实现它的构造函数和析构函数:
struct SSLObjc {
SSLObjc() { printf("SSL 构造函数调用 \n");}
~SSLObjc() { printf("SSL 析构函数调用 \n"); }
};
接下来断点打印调试:
- 这时候
SSLObjc objc;刚刚被执行,我们发现构造函数自己就被调用了。
断点向下走一步:
- 这时候作用域结束,
objc销毁,析构函数也被自动调用。
2. AssociationsManager 源码分析
查看AssociationsManager源码:
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, 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是一个全局静态变量,放在任何地方都是唯一的。
关联对象 数据结构图
关联对象相关的数据结构:
源码断点调试
在main函数中添加代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
SSLPerson *person = [SSLPerson alloc];
person.cate_name = @"SSL";
}
return 0;
}
断点进入_object_set_associative_reference:
- 根据打印结果,可以看到
disguised、association、associations的数据结构,其中_value = SSL,证明了目前的调试是正确的。
向下走:
- 打印
refs_result,发现数据结构非常的长,其实上面那么长只是类型,不用太关注。真实数据就下面那几行,还有就是refs_result.second这块代码用到了second。
四、关联对象底层分析 设值流程
重新运行,继续断点调试:
- 接下来调用
associations.try_emplace函数,associations是AssociationsHashMap类型的。 - 传入
disguised它是object的封装,和一个新建的ObjectAssociationMap,将它两作为键值对进行关联存到associations中。
try_emplace
断点进入try_emplace:
BucketT *TheBucket,创建一个空的BucketT。- 调用
LookupBucketFor(Key, TheBucket)),传入key是disguised,和TheBucket。
LookupBucketFor
进入LookupBucketFor:
- 有两个
LookupBucketFor函数,传入的是没有const的BucketT *&FoundBucket参数,所以先调用下面的。 - 函数内部又调用了上面的
LookupBucketFor,FoundBucket是指针传递,有值是可以被带回去的,所以关键代码在上面的函数。
进入上面的LookupBucketFor函数:
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
// Val 是 disguised(包装的对象), hash 函数得到下标
// 这是个 hash 函数,计算出下标
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
// while 死循环 查找位置
// 找到, 对FoundBucket进行赋值,返回 true
// 没找到,再 hash 查找
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// 找到所在下标位置有值,FoundBucket = 这个地址,函数返回
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// 找到所在下标位置没有值,FoundBucket = 这个地址,函数返回
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
- 通过
getHashValue(Val) & (NumBuckets-1)哈希函数,计算出BucketNo下标。 while循环,根据下标找位置,找到位置后将地址赋给FoundBucket,我们这里是没有赋值过的地址即isEqual(ThisBucket->getFirst(), EmptyKey),所以返回false。
返回 try_emplace
函数LookupBucketFor走完后,回到try_emplace:
- 返回的
false,所以走到了下面。TheBucket现在是存储在AssociationsHashMap中有位置了,但还没有东西,所以要为它插入内容。 - 调用
InsertIntoBucket,传入TheBucket,传入key也就是disguised,传入Args也就是ObjectAssociationMap{}。
InsertIntoBucket
进入InsertIntoBucket:
template <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
// 进行扩容等操作
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
// 将Key也就是disguised 赋值到 TheBucket 的 first
TheBucket->getFirst() = std::forward<KeyArg>( Key 也就是 );
// 将 ObjectAssociationMap{} 赋值到 TheBucket 的 second
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
进入InsertIntoBucketImpl:
template <typename LookupKeyT>
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
unsigned NewNumEntries = getNumEntries() + 1;
unsigned NumBuckets = getNumBuckets();
// 大于等于 3/4 时
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
// 进行2倍扩容
this->grow(NumBuckets * 2);
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
// 增加数量
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
// 增加数量
incrementNumEntries();
decrementNumTombstones();
} else {
// we should be purging a zero. No accounting changes.
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
- 当数量大于等于
3/4时,进行2倍扩容,已经增加num数量+1的操作。
const void *、association 设置
回到try_emplace:
- 可以看到
TheBucket已经有值了。 makeIterator中的iterator是DenseMapIterator类型。makeIterator(TheBucket, getBucketsEnd(), true)的最后一个参数为定值true。
回到_object_set_associative_reference函数:
- 现在已经可以看到
const void *, objc::ObjcAssociation这种,属于ObjectAssociationMap的数据结构。 - 这个时候
value、policy还没有存,还在association里。
继续调试:
refs_result.first获取到的是bucket,bucket.second获取到的是上面创建的ObjectAssociationMap,也就是说refs是上面创建的ObjectAssociationMap。- 接下来又是调用
try_emplace函数,传入key(这里是"cate_name"),和association。将它们以键值对的方式存储到refs的bucket中,到此两层哈希存储完成。
关联对象 设值流程总结
- 创建一个
AssociationsManager管理类 。 - 获取唯一的全局静态哈希
Map。 - 判断是否插入的关联值是否存在:
- 存在走第
4步。 - 不存在就走 : 关联对象插入空流程。
- 存在走第
- 创建一个空的
ObjectAssociationMap去取查询的键值对。 - 如果发现没有这个
key就插入一个 空的BucketT进去返回。 - 标记对象存在关联对象。
- 用当前修饰策略和值组成了一个
ObjcAssociation替换原来BucketT中的空 。 - 标记一下
ObjectAssociationMap的第一次为false。
五、关联对象底层分析 插入空流程
当value没有值时,代码走到else:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{ // object 是关联对象,key 是一个标识 key,value 是值,policy 是存储策略。
// 将 object 统一包装成 DisguisedPtr 这种数据结构
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 将 policy、value 包装成 ObjcAssociation 这种数据结构
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
// 关键代码
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
...
} 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表中查找disguised,如果没有找到进行erase清空操作。
六、关联对象 取值流程
- 创建一个
AssociationsManager管理类。 - 获取唯一的全局静态哈希
Map。 - 根据
DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器。 - 如果这个迭代查询器不是最后一个获取:
ObjectAssociationMap(这里有策略和value) 。 - 找到
ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value。 - 返回
_value。
七、关联对象释放
关联对象是什么时候释放的呢,object的生命周期决定了关联对象的生命周期,我们以object的释放为切入点进行分析。
看下面源码:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false);
}
}
- 我们看到有
objc_removeAssociatedObjects函数,可以通过反向思维来推导,看什么时候调用的这个函数。 - 它应该是在对象释放的时候被调用的。
搜索dealloc {,进入dealloc:
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
进入_objc_rootDealloc:
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
进入rootDealloc:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
进入object_dispose:
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
进入objc_destructInstance:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
进入_object_remove_assocations:
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
associations.erase(i);
}
}
// Associations to be released after the normal ones.
SmallVector<ObjcAssociation *, 4> laterRefs;
// release everything (outside of the lock).
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released
if (deallocating)
laterRefs.append(&i.second);
} else {
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
- 创建一个
AssociationsManager管理类,获取唯一的全局静态哈希Map,定义为associations。 - 以
object为key,在associations中取出iterator定义为i。 - 经过一些判断后,通过
associations.erase(i);删除i。