所用版本:
- 处理器: Intel Core i9
- MacOS 12.3.1
- Xcode 13.3.1
- objc4-838
先看下分类和类扩展区别
分类category
- 专门给类添加新的方法
- 不能给
成员属性
添加成员变量
也无法获取到(可以通过runtime给分类添加属性) - 分类中定义
@property
, 只会生产set
,get
方法, 不会生成员变量
扩展extension
- 可以说特殊分类, 也称作匿名分类
- 可以给类添加
成员变量
(私有变量) - 可以给类添加
方法
(私有方法)
其实我们常用的@interface XXX()
就是类扩展, 没想到吧, 用的频率比分类都多 :)。
同时留意下:
-
@interface XXX()类扩展
位置写在声明之后
,实现之前
, 不然会报错。 -
@interface XXX()
里面属性不能写进+load之中, 不然会报错。
类扩展extension
现在在类扩展中加个属性, 方法
例子Clang
一下看下底层内部实现
// 命令
clang -rewrite-objc main.m -o main.cpp
cpp
可看到, 扩展 方法和属性的set
, get
方法就直接添加到 method_list
中,作为类的一部分。其实类扩展
是在编译时
直接添加到本类里面, 与分类不同, 分类会生成一个category_t
结构体会影响主类, 但是类扩展不会。我们可以在realizeClassWithoutSwift
中读一下ro
验证下:
留意下: 要先有个调用, 不然不会加载. 挺好的, 省着浪费内存空间
验证可发现类扩展方法已经在ro
中, 从而也可验证类扩展直接添加到本类中
当然类扩展也可以单独创建, 看下extension
创建
Objctive-C File
→ File Type
选择Extension
即可
但是只会生成一个.h 不会生成.m, 实现还是要再本类.m
中进行
总结:
- 类扩展不会影响类的编译和加载。(会作为类的一部分,和类一起编译, 但是分类会影响 !!!)
- 类扩展可以写多个,最后也都是合并进主类了
- 类扩展必主类搭配使用,实现要在
本类
中实现
关联对象
错误例子 错误例子之前写分类也说过, 分类是不能添加属性的(只声明 get & setter无法存取), 但是可以通过关联对象方法添加属性。关键方法:
objc_setAssociatedObject
objc_getAssociatedObject
先看下效果:
例子 例子 例子
可看到能给属性正常赋值, 看一下底层
// 第一个参数: 对象
// 第二个参数: 标识符
// 第三个参数: value
// 第四个参数: 策略
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
其中:
第一个参数::要关联的对象,即给谁添加关联属性
第二个参数:标识符,方便下次查找
第三个参数:value
第四个参数:属性的策略,nonatomic, atomic, retain, copy 等
/**********************************************************************
* Associative Reference Support
**********************************************************************/
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
其实以前版本与现在版本底层变化很大, 但是 objc_setAssociatedObject
, objc_getAssociatedObject
名字不会变, 即API稳定
_object_set_associative_reference
看下_object_set_associative_reference
内部实现, 看下关联对象做了哪些事情
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// 如果object && value 直接return 话说你不调不就行了么 :)
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
// 因为传入的可能是: 类名1, 类名2, 类名3 ... 转成一个统一类型方便后面处理
// disguised 方法是对ptr 一个处理
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 面向对象的处理
// 包装 {policy, value} 为 ObjcAssociation
ObjcAssociation association{policy, value};
bool isFirstAssociation = false;
{
// 定义一个构造函数manager, 非单例
AssociationsManager manager;
// AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap。它是在`map_images`的时候初始化。
AssociationsHashMap &associations(manager.get());
// 传入的value
if (value) {
// 如果 value 存在
// 将之前 包装disguised 插入 TheBucket,
// TheBucket = { first : second } = { void * : ObjectAssociation }
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
// 如果second存在即value存在
if (refs_result.second) {
/* it's the first association we make */
// refs_result.second = true代表第一次 即首次关联,
isFirstAssociation = true;
}
/* establish or replace the association */
// 建立或取代 关联 留意下 这个时候associations还没有插入值
auto &refs = refs_result.first->second;
// 第二次 try_emplace 插入
// key 与 association关联
// 这时候的key就是成员变量的key,将 association 插入桶中。有值的情况下没有插入,没有值的情况下才插入。
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
// 第二次, 值会发生变化做新旧交换
association.swap(result.first->second);
}
} else {
// 如果 value 不存在
// 值不存在或者空这种直接清除 refs.erase(it);
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
// 找到对应的associations
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);
}
}
}
}
}
// 第一次时候标记对象是否有关联对象
if (isFirstAssociation)
object->setHasAssociatedObjects();
// 释放
association.releaseHeldValue();
}
关联对象设计图
关联对象是运行时创建用于存储值 value, 看下关联对象的设计图
关联对象关联对象流程分析
-
AssociationsManagerAssociationsManager
: 是一个构造函数, 内部进行了加锁和解锁(不是单例)。 构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager
变量
最下面的 加个static
, 相当于类方法调用, 令mapstorage
做初始化 -
AssociationsHashMap
: 获取唯一的全局静态HashMap, 通过manager获取,是单例。是在map_images的时候进行的初始化。我们也可以读下他的结构
-
value存在
:- try_emplace创建空的
TheBucket
( { first : second } = { void * : ObjectAssociation } ) - 用当前 policy 和 value 组成了一个 association 替换空的
TheBucket
- 首次关联标记。
-
refs_result.first->second
获取object对应的map,此时map中还没有存储构造的association( policy - value )。 - 第二次
try_emplace
将key与association传入TheBucket, 即传入map - 如果value不存在, association.swap将新旧值交换出来,存储新值进去, 后面会释放旧值。例如:
- try_emplace创建空的
-
value不存在
:- associations 根据对象包装disguised 找到编号: ref。
- 根据key, 找到ref数据it, 并清除
- 如果该对象已经没有关联对象了,则清除关联对象map
-
try_emplace会走2次try_emplace
:
当value
有情况下try_emplace
会走2次
-
第一次参数传入: DisguisedPtr<objc_object> disguised{(objc_object *)object} 闭包
-
第二次参数传入: key,
objc_setAssociatedObject
传入的第二个参数自定义的key
-
InsertIntoBucket
插桶操作看一下内部
InsertIntoBucket
InsertIntoBucketImpl
可看出内部实际是进行了查找,返回对应的 TheBucket。
-
LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)
: 满足 负载因子 ( 3 / 4 ) 进行 2倍扩容。 -
LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()
: ( 总容量 - 已有元素) <= 总容量 / 8, 相当于 7 / 8 判断。 -
setHasAssociatedObjectssetHasAssociatedObjects
标记关联对象
-
releaseHeldValuereleaseHeldValue
释放旧对象
关联对象实测流程
关联对象赋值test_property
是我们设置的关联对象
关联对象打印
打印一下
association
中value
, 对应我们传的值(传入参数)
result
打印
result
, 初始second
为true, first
里面是其数据, 接下来走try_emplace
进行插值
try_emplace
try_emplace
中由于第一次 BucketT 没有值, 所以走下面插入新值
读result
之后走第一次标记关联对象setHasAssociatedObjects
以及释放旧value
方法releaseHeldValue
关联对象释放
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false);
}
}
内部调用_object_remove_assocations
, 看一下
// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
// 与设置/获取关联引用不同
// 此函数对性能敏感,因为原始isa对象(如OS对象)无法跟踪它们是否具有关联对象。
void
_object_remove_assocations(id object, bool deallocating)
{
// 创建 空ObjectAssociationMap refs
ObjectAssociationMap refs{};
{
// 创建 AssociationsManager
AssociationsManager manager;
// 创建 AssociationsHashMap
AssociationsHashMap &associations(manager.get());
// associations 中找到 iterator 迭代查询器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
// 迭代查询器 不等于 associations最后一个
if (i != associations.end()) {
// 将对象对应的 ObjectAssociationMap 存入refs临时空间
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
// 如果我们没有取消分配,那么将保留 SYSTEM_OBJECT 关联对象。
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)
// 清理迭代器i
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();
}
}
// 循环释放 laterRefs
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
关联对象总结
设值流程
- 创建一个
AssociationsManager
管理类 - 获取唯一全局静态哈希Map
- 判断是否存在关联对象值
-
存在 :
- 创建一个空的
ObjectAssociationMap
去取查询的键值对 - 如果发现没有这个 key 就先插入一个
空的 BucketT
- 标记对象存在关联对象
- 用当前
策略 policy
和值 value
组成了一个ObjcAssociation
替换之前空的BucketT
- 标记
ObjectAssociationMap
为 第二次
- 创建一个空的
-
不存在 :
- 根据
DisguisedPtr
找到AssociationsHashMap
中的 iterator 迭代查询器 - 清理迭代器
- 根据
-
存在 :
取值流程
- 创建一个
AssociationsManager
管理类 - 获取唯一的全局静态
哈希Map
- 根据
DisguisedPtr
找到AssociationsHashMap
中的 iterator 迭代查询器 - 如果这个
迭代查询器 != associations.end()
, 即不是最后一个, 那么获取 :ObjectAssociationMap
(这里有策略 policy
和值 value
) -
ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value - 返回
value
网友评论