前言
上一篇讲了分类的本质和底层原理,提到分类可以关联对象添加属性,也讲到类扩展和分类的区别,这篇讲一下 深入探索一下,类扩展和关联对象的底层实现原理。
准备:调试代码下载
一 、 类扩展extension
1.1. 类扩展的创建方式
- 通过 command+N 新建 -> Objective-C File -> 选择Extension
- 直接在类中书写:永远在声明之后,在实现之前(需要在.m文件中书写)
1.2. 类扩展的本质
Clang 生成cpp文件:xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc AppDelegate.m
查看被编译后的内容如下:在编译过程中,方法就直接添加到了 methodlist中,作为类的一部分,即编译时期直接添加到本类里面
本想通过源码调试的,结果电脑升级到最新系统版本之后,源码不过了,尴尬。。。。以后补充吧
总结:
类的扩展 在编译器 会作为类的一部分,和类一起编译进来
类的扩展只是声明,依赖于当前的主类,没有.m文件,可以理解为一个·h文件
二 、关联对象
之前分析分类的时候,讲过分类添加属性是无效的,但是可以通过runtime的方式动态添加关联对象。
举个例子:
#import <objc/runtime.h>
// 本类
@interface HJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation HJPerson
@end
// 分类
@interface HJPerson (EXT)
@property (nonatomic, copy) NSString * ext_name; // 属性
@end
@implementation HJPerson(EXT)
- (void)setExt_name:(NSString *)ext_name {
/*给属性`ext_name `,动态添加set方法
1 .对象
2. 标识符
3.value
4.属性的策略,即nonatomic、atomic、assign等
*/
objc_setAssociatedObject(self, "ext_name", ext_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)ext_name { // 给属性`ext_name `,动态添加get方法
return objc_getAssociatedObject(self, "ext_name");
}
@end
其底层原理的实现,主要分为两部分:
动态设置
关联属性: objc_setAssociatedObject
(关联对象,关联属性key,关联属性value,策略)
动态读取
关联属性:objc_getAssociatedObject
(关联对象,关联属性key)
复习:
很明显我们这里看可以看到这个关联属性是通过这两个方法写入读取的。那么回顾一下,我们正常写的属性是如何写入读取的呢??
2.0 正常属性的写入
通过Clang 查看编译代码如下:
static NSString * _I_LGTeacher_ext_name(LGTeacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGTeacher$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
发现常规是调用objc_setProperty完成set方法,我们在源码中检查objc_setProperty的实现:
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
进入reallySetProperty:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) __attribute__((always_inline));
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset); //根据读源码的经验,我们就可以想到+ offset 就是在做内存偏移计算,通过类地址和偏移值读到当前属性的地址,然后读取到值
if (copy) {
newValue = [newValue copyWithZone:nil]; //copy 方法
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];//mutableCopy 方法
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);//如果等于新值 就返回新值
}
if (!atomic) {// 非原子操作直接赋值
oldValue = *slot;
*slot = newValue;
} else {// 原子操作加锁之后再赋值
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue); //旧值release
}
总结:正常属性的读取:1. 通过地址读取属性 -> 2.新值retain -> 3.属性赋值 -> 4.旧值release
2.1. objc_setAssociatedObject设值流程
// 进入源码
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
//继续jump
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
// 继续jump
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
进入_object_set_associative_reference
,查看policy的枚举类型如下,很明显定义了属性的类型assign
,copy
,retain
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
进入_object_set_associative_reference源码实现
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
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封装成一个数组结构类型,类型为DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用
// 包装一下 policy - value
/*
class ObjcAssociation {
uintptr_t _policy;
id _value;
*/
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();// 根据策略类型进行处理 retain一个新值
//局部作用域空间
{
//初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一 内存中独一份。 因为 &associations 操作的是地址
if (value) {
//refs_result :从map表中读取类的buckets (类对:key:为disguised 类标志;value: 为类中关联对象的信息)
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {//判断第二个存不存在,即bool值是否为true ,为什么是second 得从associations.try_emplace 方法内部查看
/* it's the first association we make 第一次建立关联*/
object->setHasAssociatedObjects();//nonpointerIsa ,标记位true 即置isa指针的has_assoc属性为true
}
/* establish or replace the association 建立或者替换关联*/
auto &refs = refs_result.first->second; //得到一个空的桶子,找到引用对象类型,即第一个元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有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();//调用 objc_release 释放旧值
}
其整体流程如下:关键点的分析:
2.1.1 setHasAssociatedObjects()
设置关联
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true; // 这里给isa的has_assoc 做了一个标记 ,要考的
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
这里涉及一个面试题: 问关联对象是否需要手动释放?从这里就可以看出来,在关联对象的时候,isa中记录了是否有关联对象。通过dealloc 释放。
执行流程: objc_object::rootDealloc() -> object_dispose()->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); //就在这里释放啦
obj->clearDeallocating();
}
return obj;
}
2.1.2 AssociationsHashMap
- 关联属性的信息会存在一个
AssociationsHashMap
类型的map中,map中有很多的关联对象key-value键值对,其中key为DisguisedPtr<objc_object>,value类型是ObjectAssociationMap,可以理解为一个类对应一个:ObjectAssociationMap ; - 每个
ObjectAssociationMap
,也有很多key-value键值对,其中key的类型为const void *,value的类型为ObjcAssociation ,ObjcAssociation
是用于包装polic
y和value
的一个类;
2.2 objc_getAssociatedObject 取值流程
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//创建空的关联对象
{
AssociationsManager manager;//创建一个AssociationsManager管理类
AssociationsHashMap &associations(manager.get());//获取全局唯一的静态哈希map
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到迭代器,即获取buckets
if (i != associations.end()) {//如果这个迭代查询器不是最后一个 获取
ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
ObjectAssociationMap::iterator j = refs.find(key);//根据key查找ObjectAssociationMap,即获取bucket
if (j != refs.end()) {
association = j->second;//获取ObjcAssociation
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();//返回value
}
其整体流程如下:本来想验证的。结果我电脑系统升级了。源码编译不了。所以只能等以后再验证了。
2.3 补充: objc_removeAssociatedObjects移除关联对象
// 移除关联的对象
// 使用objc_removeAssociatedObjects函数可以移除某个对象身上的所有关联的对象。
void objc_removeAssociatedObjects(id object)
2.4 总结关联对象:
-
- 关联对象的主要两个方法
- set :
objc_setAssociatedObject
- get:
objc_getAssociatedObject
- 关联对象管理类
AssociationsManager
- 关联对象管理类
- 关联对象的数据结构:
AssociationsHashMap
,ObjectAssociationMap
和ObjcAssociation
- 关联对象的数据结构:
- 不管set还是get方法,都是先通过类信息找到相应的类对,再从类对的value中,通过关联属性key找到对应的关联属性,进行相应操作。
- 关联对象并
不是存储在被关联对象本身内存
中,而是存储在全局的统一
的一个AssociationsManager
中,如果设置关联对象为nil,就相当于是移除关联对象
- 关联对象并