iOS-类扩展和分类关联对象

212 阅读7分钟

问题先行

1、类扩展和分类的区别是什么?
2、关联对象策略objc_AssociationPolicy为什么没有weak

资源准备

1、objc源码下载opensource.apple.com/

类扩展

类扩展extension又称作匿名的分类,为了给当前类增加属性方法

实现有两种形式:

  • 直接在.m文件中新增类扩展
  • 新建类扩展的.h文件(通过command+N新建->Objective-C File->选择Extension)

image.png

C++源码探索

通过clang底层编译(xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp)生成cpp文件 一些核心代码如下

image.png

image.png

总结
查看ASSon类扩展的属性、方法,在编译过程中,属性就直接添加到了ASSon_IMPL结构体内,方法就直接添加到了methodlist中,作为类的一部分即编译时期直接添加到本类里面

  • 类的扩展在编译期会作为类的一部分,和类一起编译进来
  • 类的扩展只是声明,依赖于当前的主类,没有.m文件,可以理解为一个·h文件

分类

关联对象

由于分类给类添加的成员属性,不会自动生成对应成员变量,因此也无法取到对应值。为解决此问题,可以通过runtime给分类添加属性即属性关联重写settergetter方法。 image.png

关联对象-设值流程 image.png image.png 其中objc_setAssociatedObject方法有四个参数,分别表示:

  • 参数1:要关联的对象,即给谁添加关联属性
  • 参数2:标识符,查找依据
  • 参数3:value
  • 参数4:属性的策略,即nonatomicatomicassign等 通过调用路径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;
  /// 代码省略
 }

从源码中我们可以看出KeyTValueT也就是前两个参数对应着map中的key_typemapped_type
AssociationsHashMapKeyT传入的是DisguisedPtrValueT中传入的值则为ObjectAssociationMap
ObjectAssociationMapKeyT传入的是ObjcAssociationValueT中传入的值则为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函数传入的值,也就是说我们传入的valuepolicy这两个值最终是存储在ObjcAssociation中的。
传入的value经过acquireValue函数处理获取new_valueacquireValue函数内部其实是通过对策略的判断返回不同的值

_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管理类,获取唯一全局静态哈希MapAssociationsHashMap
  • 判断是否插入的关联值value是否存在,如果不存在则移除关联
  • 通过try_emplace方法,并创建一个空的ObjectAssociationMap去取查询的键值对
  • 如果发现没有这个key就插入一个空的BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象即置isa指针的has_assoc属性为true
  • 用当前policyvalue组成了一个ObjcAssociation替换原来BucketT中的空
  • 标记一下 ObjectAssociationMap的第一次为false 关系图如下 image.png 一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的valuepolicy策略。

关联对象-取值流程 image.png image.png 通过调用路径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管理类,获取唯一的全局静态哈希MapAssociationsHashMap
  • 通过find方法根据DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
  • 如果这个迭代查询器不是最后一个获取:ObjectAssociationMap(policy和value)
  • 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 根据key查找ObjectAssociationMap,即获取bucket
  • 获取ObjcAssociation返回 value

总结:

关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个MapAssociationsHashMap中,如果设置关联对象为nil,就相当于是移除关联对象。

问题先行解答

1、类扩展和分类的区别是什么?
类扩展特殊的分类,也可称作匿名分类,可以给类添加成员属性方法
分类是专门用来给类添加新的方法,不能给类添加成员变量,添加了成员属性,但是不能自动生成对应的成员变量和set、get方法。但是可以通过runtime给分类添加属性,即属性关联重写settergetter方法
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,本质上是保存了对象的地址而不是真正的弱引用,在一些情定的情况下,属性对象释放时再调用方法会出现野指针异常