iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

iOS底层之类的加载
iOS底层之分类的加载

序文

在前面文章中,从底层源码探究了分类的加载流程,今天从源码层面探究一下类扩展及相关目标的本质。

类扩展

经过cpp文件查看类扩展

界说类LGStudent的特点办法,以及类扩展完结

@interface LGStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
// 类扩展
@interface LGStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGStudent
- (void)instanceMethod{
  NSLog(@"%s", __func__ );
}
+ (void)classMethod{
  NSLog(@"%s", __func__ );
}
- (void)ext_instanceMethod{
  NSLog(@"%s", __func__ );
}
+ (void)ext_classMethod{
  NSLog(@"%s", __func__ );
}
@end

经过clang然后生成cpp文件,查看类扩展特点

iOS底层之类扩展和关联对象

在类扩展中界说的特点ext_nameext_age,在cpp文件和主类的特点nameage一样处理生成带下划线成员变量

iOS底层之类扩展和关联对象

iOS底层之类扩展和关联对象

这儿对比能够看到,

  • 在成员列表_ivar_list_t中是有_age_ext_age_name_ext_name4个成员变量;
  • 在特点列表_prop_list_t中只有nameage两个特点,阐明在类扩展中的特点为私有;

看一下类扩展中的办法

iOS底层之类扩展和关联对象

类扩展中界说的实例办法ext_instanceMethod以及特点ext_nameext_agesettergetter办法,都编译在主类实例办法中。

iOS底层之类扩展和关联对象

类扩展中界说的类办法ext_classMethod和主类的类办法classMethod在编译在主类的类办法中。

经过类的加载查看类扩展

界说类LGPerson和类扩展LGPerson+LG

iOS底层之类扩展和关联对象

在《iOS底层之类的加载》中咱们知道了非懒加载类加载到内存是在启动时完结的。

为方便调试在主类完结+load办法,类扩展办法以ext开头,在LGperson.m中不完结类扩展办法ext_saySomthing

iOS底层之类扩展和关联对象

methodizeClass办法中对LGPerson准确下断点,运转调试

iOS底层之类扩展和关联对象

先来到的为LGPerson的元类,在从ro读取的method_list_t中,包括主类中的类办法+load和类扩展中的类办法ext_classMethod

过掉断点会再次进来,这次是对类LGPerson的加载

iOS底层之类扩展和关联对象

LGPerson类加载时,从ro读取的method_list_t中,包括主类中的实例办法sayHellosayByeBye,一起还有分类中的实例办法ext_instanceMethod,特点ext_namesettergetter办法,并没有未完结的办法ext_saySomthing

类扩展总结

  • 类扩展能够给类增加成员特点,可是是私有变量;
  • 类扩展能够给类增加办法,也是私有办法;

相关目标

分类的效果是给类增加新的办法,可是不能增加特点,即便增加了特点,也只会生成特点的settergetter办法声明,不能生成办法完结和带下划线的成员变量

咱们能够经过Runtime给分类增加特点,就是经过相关目标的方式完结,今天从源码层面实例探究一下相关目标是怎样完结的给分类增加特点的。

源码剖析

相关目标存储

相关目标存储值时运用的apiobjc_setAssociatedObject,参数有4个

  • object: 相关目标;
  • key:标识符;
  • value:相关值;
  • policy:相关战略;
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
  _object_set_associative_reference(object, key, value, policy);
}

objc_setAssociatedObject直接调用_object_set_associative_reference,这样做是维持最上层的api安稳,不论底层怎样更新,咱们做相关目标运用的就是objc_setAssociatedObject

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.
  // 相关目标为nil切相关值也为nil
  if (!object && !value) return;
    // object不允许相关目标,直接崩
  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));
  // 包装相关目标成统一目标类型,
  DisguisedPtr<objc_object> disguised{(objc_object *)object};
  // 将value和policy包装成ObjcAssociation类型
  ObjcAssociation association{policy, value};
  // retain the new value (if any) outside the lock.
  association.acquireValue();
  bool isFirstAssociation = false; // 初次相关目标标识
  {
    AssociationsManager manager; // 实例化相关目标处理manager
    AssociationsHashMap &associations(manager.get()); // 获取相关目标表
    if (value) { // 相关值判别
      // 从相关目标表中,依据disguised获取相关目标存储地址,没有就创立刺进
      auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
      if (refs_result.second) { // 是否为第一次相关值
        /* it's the first association we make */
        isFirstAssociation = true;
      }
      /* establish or replace the association */
      // 获取disguised的相关目标存储地址
      auto &refs = refs_result.first->second;
      // 以key-value的方式,存储association
      auto result = refs.try_emplace(key, std::move(association));
      if (!result.second) { 
            // 假如之前存储过相关值,用之前的值first替换调value,后边将association开释
        association.swap(result.first->second);
      }
    } else { // 相关值位nil,假如之前有key对应相关值,则铲除
      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()) { // 判别key对应的相关值,有的话就铲除开释掉
          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.
  // 给相关目标object的isa中的has_assoc设置值true,表明有相关目标
  if (isFirstAssociation)
    object->setHasAssociatedObjects();
  // release the old value (outside of the lock).
  association.releaseHeldValue(); // 假如有新值替换则开释旧值
}

_object_set_associative_reference中的工作处理流程:
1.value有值

  • 实例化大局相关表associations
  • associations调用try_emplace将相关目标disguised作为key,获取disguised对应的相关目标表ObjectAssociationMap的地址,假如没有就创立一个ObjectAssociationMap,回来为refs_result
  • 假如refs_result.second有值,则disguised为第一次做相关值;
  • 经过refs_result.first->second获取相关目标表的存储地址refs
  • refs调用try_emplace以标识符key存储相关值association,回来为result
  • 假如result.second为false,阐明之前已经做过key对应的相关值,调用association.swap办法存储旧值地址存入association预备开释旧值;
  • 假如为第一次相关值,调用setHasAssociatedObjectsisahas_assoc设置为true,表明object有相关目标,在dealloc时将相关表开释。
  • 假如不是第一次相关,此时association存储的是旧值,调用releaseHeldValue开释掉旧值

2.value为空值

  • 假如相关值为空,获取disguised的相关表,铲除掉key对应的相关值,假如disguised相关表中没有相关值,则将disguised相关表开释。

相关目标取值

相关目标取值时运用的apiobjc_getAssociatedObject,参数为相关目标object和标识符key

id
objc_getAssociatedObject(id object, const void *key)
{
  return _object_get_associative_reference(object, key);
}

同样是一层封装过渡,直接调用_object_get_associative_reference

id
_object_get_associative_reference(id object, const void *key)
{
  ObjcAssociation association{};
  { 
    AssociationsManager manager;
    AssociationsHashMap &associations(manager.get());
        // 经过相关目标object获取相关表
    AssociationsHashMap::iterator i = associations.find((objc_object *)object);
    if (i != associations.end()) {
           // 获取相关表的地址
      ObjectAssociationMap &refs = i->second;
            // 经过key值获取相关值
      ObjectAssociationMap::iterator j = refs.find(key);
      if (j != refs.end()) {
        association = j->second;
        association.retainReturnedValue(); // retain处理
      }
    }
  }
  return association.autoreleaseReturnedValue(); // 回来value
}
  • 经过相关目标object获取相关表;
  • 获取相关表的地址,经过key值获取相关值

相关目标毁掉

目标毁掉时会调用dealloc办法

- (void)dealloc {
  _objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
  ASSERT(obj);
  obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
  // TaggedPointer小目标直接回来
  if (isTaggedPointer()) return; // fixme necessary?
  /**
  * nonpointer & !weakl & !has_*assoc & !has_cxx_dtor & !has_sidetable_rc
  * nonpointer类型
  * 无弱引证
  * 无相关目标
  * 无C++析构函数
  * 无类的C++析构函数
  * 无引证计数
  */ 
  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);
  }
}
id
object_dispose(id obj)
{
  if (!obj) return nil;
  objc_destructInstance(obj);  
  free(obj);
  return nil;
}
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_associations(obj, /*deallocating*/true);
    obj->clearDeallocating();
  }
  return obj;
}
inline bool
objc_object::hasAssociatedObjects()
{
  if (isTaggedPointer()) return true;
  if (isa().nonpointer) return isa().has_assoc;
  return true;
}
  • dealloc中调用_objc_rootDealloc
  • _objc_rootDealloc中调用目标的rootDealloc办法;
  • rootDealloc中判别目标为TaggedPointer不必处理;
  • 不是TaggedPointer,假如为nonpointer指针且无弱引证无相关目标无C++析构函数无类的C++析构函数无引证计数则直接free,不然就调用object_dispose
  • object_dispose中先调用objc_destructInstance,然后再free
  • objc_destructInstance中依据hasAssociatedObjects判别有无相关目标,假如有就调用_object_remove_associations移除相关目标;
  • hasAssociatedObjectsnonpointer类型的isa,依据值has_assoc判别有无相关目标;

看一下_object_remove_associations的源码是怎样移除相关目标的

void
_object_remove_associations(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) { // deallocating值为true
        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();
  }
}
  • 大局相关表associations经过目标object获取相关目标表对应的表i
  • 获取表地址空间refs
  • for循环取出所有的相关值,先铲除开释战略不是OBJC_ASSOCIATION_SYSTEM_OBJECT,战略为OBJC_ASSOCIATION_SYSTEM_OBJECT放入laterRefs
  • 再对laterRefs循环铲除。

实例验证

界说主类LGPerson和分类LGPerson+CatA,在分类中设置特点cate_name,并在分类中完结cate_namesettergetter办法

- (void)setCate_name:(NSString *)cate_name{
  /**目标,标识符,value,战略* */
  objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
  return objc_getAssociatedObject(self, "cate_name");
}

mian函数中给cate_name赋值

iOS底层之类扩展和关联对象
_object_set_associative_reference中加断点调试

第一次相关值

iOS底层之类扩展和关联对象

  • 第一次相关值为Hello cat!,这儿isFirstAssociation值为true
  • 此时refs还没有调用try_emplaceBuckets的值为nil

持续Step Over一下

iOS底层之类扩展和关联对象

这儿refs的存储了值,result.secondtrue阐明第一次相关不需要association.swap

setHasAssociatedObjects打断点,进入调试

iOS底层之类扩展和关联对象

这儿对newisahas_assoc赋值为true

releaseHeldValue打断点,进入调试

iOS底层之类扩展和关联对象

这儿_valuenil,且_policy0

第二次相关值

放掉断点,进入第二次相关值流程,断点来到refs.try_emplace

iOS底层之类扩展和关联对象

  • 第二次相关值为Hello dog!,这儿isFirstAssociation值为false
  • refs的存储了值,Buckets的值为相关值的地址;

持续Step Over一下

iOS底层之类扩展和关联对象

iOS底层之类扩展和关联对象

refs.try_emplaceresult.second的值为false,进入swap办法,association的存储值为上一次相关的旧值。

releaseHeldValue打断点,进入调试

iOS底层之类扩展和关联对象

这儿要开释掉的_value为上一次相关的Hello cat!

相关值值nil

过掉断点,进入相关值为nil的流程,断点到erase

iOS底层之类扩展和关联对象

  • 这儿valuenil
  • association.swap的值为second_valueHello dog!,最终存储的相关值,预备开释;
  • refs.size的值为1,有一个相关值;

持续Step Over一下,过掉erase

iOS底层之类扩展和关联对象

refs.size的值为0,没有相关值,调用associationserase办法开释相关目标表refs_it

相关目标取值

注释掉相关目标赋值为nil,打印cate_name

iOS底层之类扩展和关联对象

_object_get_associative_reference加断点调试

iOS底层之类扩展和关联对象
这儿取到的值为ObjectAssociationMapsecond最新值为Hello dog!

相关目标开释

走完main的效果域,person就会进入dealloc流程

iOS底层之类扩展和关联对象
dealloc中依据字符串匹配LGPerson精准下断点,然后进入rootDealloc

iOS底层之类扩展和关联对象
这儿经过isa()输出,has_assoc值为1,进入object_dispose流程,在_object_remove_associations打断点

iOS底层之类扩展和关联对象
看到这儿objectLGPerson目标,相关值有1

iOS底层之类扩展和关联对象
输出值为Hello dog!

相关目标总结

相关目标的存储结构为

  • 总哈希表AssociationsHashMap,存储的是相关目标和相关目标对应的相关表ObjectAssociationMap
  • 相关表ObjectAssociationMap中的Buckets为表的地址值;
  • 相关表中存储的是标识符key,和将valuepolicy包装在一起的ObjcAssociation

iOS底层之类扩展和关联对象

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

iOS底层之类的加载
iOS底层之分类的加载

以上是对类扩展和相关目标的探究总结,难免有缺乏和错误之处,如有疑问请在谈论区留言。