问题先行
1、类扩展和分类的区别是什么?
2、关联对象策略objc_AssociationPolicy为什么没有weak?
资源准备
1、objc源码下载opensource.apple.com/
类扩展
类扩展extension又称作匿名的分类,为了给当前类增加属性和方法
实现有两种形式:
- 直接在
.m文件中新增类扩展 - 新建类扩展的
.h文件(通过command+N新建->Objective-C File->选择Extension)
C++源码探索
通过clang底层编译(xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp)生成cpp文件
一些核心代码如下
总结
查看ASSon类扩展的属性、方法,在编译过程中,属性就直接添加到了ASSon_IMPL结构体内,方法就直接添加到了methodlist中,作为类的一部分即编译时期直接添加到本类里面
- 类的扩展在编译期会作为类的一部分,和类一起编译进来
- 类的扩展只是声明,依赖于当前的主类,没有
.m文件,可以理解为一个·h文件
分类
关联对象
由于分类给类添加的成员属性,不会自动生成对应成员变量,因此也无法取到对应值。为解决此问题,可以通过runtime给分类添加属性即属性关联重写setter、getter方法。
关联对象-设值流程
其中
objc_setAssociatedObject方法有四个参数,分别表示:
- 参数1:要关联的对象,即给谁添加关联属性
- 参数2:标识符,查找依据
- 参数3:value
- 参数4:属性的策略,即
nonatomic、atomic、assign等 通过调用路径objc_setAssociatedObject->_object_set_associative_reference可知_object_set_associative_reference为核心方法
在了解核心方法_object_set_associative_reference之前,先了解下相关知识点。
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初始化的时候进行了_mapStorage的初始化且是一个静态变量(单一性),get()方法生成哈希map。
其主要作用是获取唯一的全局静态哈希Map:AssociationsHashMap
try_emplace
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
/// 查找Bucket,Key为关联对象
/// 如果map中已经存在,则直接返回,其中make_pair的第二个参数bool值为false
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
/// 如果没有找到,则通过InsertIntoBucket插入map,其中make_pair的第二个参数bool值为true
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
通过key查找Bucket,如果存在则直接返回,并做false标记,如果不存在,则通过InsertIntoBucket插入map,并做true标记。
AssociationsHashMap、ObjectAssociationMap
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>,
KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> {
/// 代码省略
}
template <typename DerivedT, typename KeyT, typename ValueT,
typename ValueInfoT, typename KeyInfoT, typename BucketT>
class DenseMapBase {
template <typename T>
using const_arg_type_t = typename const_pointer_or_const_ref<T>::type;
public:
using size_type = unsigned;
using key_type = KeyT;
using mapped_type = ValueT;
using value_type = BucketT;
/// 代码省略
}
从源码中我们可以看出KeyT和ValueT也就是前两个参数对应着map中的key_type和mapped_type。
AssociationsHashMap中KeyT传入的是DisguisedPtr,ValueT中传入的值则为ObjectAssociationMap。
ObjectAssociationMap中KeyT传入的是ObjcAssociation,ValueT中传入的值则为ObjectAssociationMap。\
ObjcAssociation
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
/// 代码省略
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
/// 代码省略
}
ObjcAssociation存储着_policy、_value,而这两个值我们可以发现正是我们调用objc_setAssociatedObject函数传入的值,也就是说我们传入的value和policy这两个值最终是存储在ObjcAssociation中的。
传入的value经过acquireValue函数处理获取new_value。acquireValue函数内部其实是通过对策略的判断返回不同的值
_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被转化为了DisguisedPtr类型的disguised。
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
/// 根据策略获取处理之后新的值
association.acquireValue();
bool isFirstAssociation = false;
{
/// 创建一个AssociationsManager管理类
/// 注意manager并不是单例,里面仅仅是加锁进行避免重复创建
AssociationsManager manager;
/// 获取唯一的全局静态哈希Map:AssociationsHashMap
AssociationsHashMap &associations(manager.get());
if (value) {
/// 通过try_emplace方法,并创建一个空的ObjectAssociationMap去取查询的键值对:
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
/// 判断是否插入的关联值value是否存在
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
/// 得到一个空的Bucket,找到引用对象类型,即第一个元素的second值
auto &refs = refs_result.first->second;
/// 查询当前的key是否有association关联对象
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);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
/// 通过setHasAssociatedObjects方法标记对象存在关联对象即置isa指针的has_assoc属性为true
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
object类型强转,value值根据策略处理- 创建一个
AssociationsManager管理类,获取唯一的全局静态哈希Map:AssociationsHashMap - 判断是否插入的关联值
value是否存在,如果不存在则移除关联 - 通过
try_emplace方法,并创建一个空的ObjectAssociationMap去取查询的键值对 - 如果发现没有这个
key就插入一个空的BucketT进去并返回true - 通过
setHasAssociatedObjects方法标记对象存在关联对象即置isa指针的has_assoc属性为true - 用当前
policy和value组成了一个ObjcAssociation替换原来BucketT中的空 - 标记一下
ObjectAssociationMap的第一次为false关系图如下一个实例对象就对应一个
ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。
关联对象-取值流程
通过调用路径
objc_getAssociatedObject->_object_get_associative_reference可知_object_get_associative_reference为核心方法
_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
/// 创建空的关联对象
ObjcAssociation association{};
{
/// 创建一个AssociationsManager管理类
/// 注意manager并不是单例,里面仅仅是加锁进行避免重复创建
AssociationsManager manager;
/// 获取唯一的全局静态哈希Map:AssociationsHashMap
AssociationsHashMap &associations(manager.get());
/// 通过find方法根据DisguisedPtr找到AssociationsHashMap中iterator迭代查询器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
/// 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
ObjectAssociationMap &refs = i->second;
/// 根据key查找ObjectAssociationMap,即获取bucket
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
/// 获取ObjcAssociation
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
- 创建一个
AssociationsManager管理类,获取唯一的全局静态哈希Map:AssociationsHashMap - 通过
find方法根据DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器 - 如果这个迭代查询器不是最后一个获取:
ObjectAssociationMap(policy和value) - 找到
ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value - 根据
key查找ObjectAssociationMap,即获取bucket - 获取
ObjcAssociation返回value
总结:
关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个Map:AssociationsHashMap中,如果设置关联对象为nil,就相当于是移除关联对象。
问题先行解答
1、类扩展和分类的区别是什么?
类扩展是特殊的分类,也可称作匿名分类,可以给类添加成员属性、方法
分类是专门用来给类添加新的方法,不能给类添加成员变量,添加了成员属性,但是不能自动生成对应的成员变量和set、get方法。但是可以通过runtime给分类添加属性,即属性关联重写setter、getter方法
2、关联对象策略objc_AssociationPolicy为什么没有weak?
通过上面对源码的分析我们知道,object被转化为了DisguisedPtr类型的
DisguisedPtr<objc_object> disguised{(objc_object *)object};
weak修饰的属性,当没有拥有对象之后就会被销毁,并且指针置位nil,那么在对象销毁之后,虽然在map中既然存在值object对应的AssociationsHashMap,但是因为object地址已经被置位nil,会造成坏地址访问而无法根据object对象的地址转化为DisguisedPtr了。
使用OBJC_ASSOCIATION_ASSIGN策略其实等于assign/unsafe_unretained,本质上是保存了对象的地址而不是真正的弱引用,在一些情定的情况下,属性对象释放时再调用方法会出现野指针异常