类加载原理补充-关联对象底层原理

分类加载的补充

objc4-818.2源码中创建LGPerson类以及LGPerson+LGALGPerson+LGB分类,其中LGPerson类中未实现+ (void)load{}方法,分类中实现了load方法。源码load_categories_nolockattachCategories方法中分别添加了如下代码

const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "LGPerson") == 0)
{
     printf("%s -LGPerson....\n",__func__);
}
  • 在添加的代码printf("%s -LGPerson....\n",__func__);处添加断点
  • main方法中添加如下代码,运行源码进行调试
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // class_data_bits_t
        LGPerson * person = [LGPerson alloc];
        [person saySomething];
    }
    return 0;
}


(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x00000001000044e8
  Fix-it applied, fixed expression was: 
    ro->baseMethods()
(lldb) p *$0 
(method_list_t) $1 = {
  entsize_list_tt = (entsizeAndFlags = 27, count = 10)
}
(lldb) p $1.get(0)   // 指针地址指向的就是method_t
(method_t) $2 = {}
  • 由源码可知method_list_t继承自entsize_list_tt,查看其源码
struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;

    uint32_t entsize() const {
        return entsizeAndFlags & ~FlagMask;
    }
    uint32_t flags() const {
        return entsizeAndFlags & FlagMask;
    }

    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        // 获取方法实际上调用的getOrEnd
        return getOrEnd(i);
    }

    size_t byteSize() const {
        return byteSize(entsize(), count);
    }
    
    static size_t byteSize(uint32_t entsize, uint32_t count) {
        return sizeof(entsize_list_tt) + count*entsize;
    }
  ......
  • 由上面工程调试可知执行了源码attachCategories方法

如果我们把分类LGPerson+LGA中的load方法屏蔽掉,再运行工程,发现没有执行attachCategories方法,打印信息如下

readClass LGPerson....
_read_images LGPerson....
realizeClassWithoutSwift LGPerson....
prepareMethodLists - LGPerson....
realizeClassWithoutSwift LGPerson....
methodizeClass -LGPerson....
prepareMethodLists - LGPerson....
attachToClass -LGPerson....
getMethodNoSuper_nolock -LGPerson....
2021-08-28 17:27:22.788465+0800 KCObjcBuild[79133:10109465] -[LGPerson(LGB) saySomething]

如果再创建分类LGPerson+LGCLGPerson+LGD,其中分类LGPerson+LGC实现load方法,运行工程发现执行了attachCategories方法,打印信息如下

readClass LGPerson....
operator() -LGPerson....
operator() -LGPerson....
operator() -LGPerson....
operator() -LGPerson....
realizeClassWithoutSwift LGPerson....
prepareMethodLists - LGPerson....
prepareMethodLists - LGPerson....
realizeClassWithoutSwift LGPerson....
methodizeClass -LGPerson....
prepareMethodLists - LGPerson....
attachToClass -LGPerson....
attachToClass -LGPerson....
attachCategories - LGPerson....
prepareMethodLists - LGPerson....
getMethodNoSuper_nolock -LGPerson....
2021-08-28 17:41:17.662930+0800 KCObjcBuild[79229:10118616] -[LGPerson(LGB) saySomething]

得出结论:

  • 超过一个分类实现了load方法,就会执行attachCategories方法

load_images方法添加断点,重新运行工程进行调试

image.png

主类中没有实现load方法,迫使执行prepare_load_methods方法,继续调试执行到realizeClassWithoutSwift -> attachCategories

image.png

疑问:明明有四个分类,这里为什么之后两个?

// lldb进行调试查看
(lldb) p cats_list[0]
(const locstamped_category_t) $0 = {
  cat = 0x0000000100004438
  hi = 0x0000000100936c90
}
(lldb) p $0.cat 
(category_t *const) $1 = 0x0000000100004438
(lldb) p *$1 
(category_t) $2 = {
  name = 0x0000000100003af6 "LGA"
  cls = 0x0000000100004988
  instanceMethods = {
    ptr = 0x0000000100004388
  }
  classMethods = {
    ptr = 0x00000001000043d8
  }
  protocols = 0x0000000000000000
  instanceProperties = 0x0000000100004410
  _classProperties = 0x0000000000000000
}
(lldb) p cats_list[1]
(const locstamped_category_t) $3 = {
  cat = 0x0000000100004830
  hi = 0x0000000100936c90
}
(lldb) p $3.cat 
(category_t *const) $4 = 0x0000000100004830
(lldb) p *$4
(category_t) $5 = {
  name = 0x0000000100003b07 "LGB"
  cls = 0x0000000100004988
  instanceMethods = {
    ptr = 0x0000000100004768
  }
  classMethods = {
    ptr = 0x00000001000047b8
  }
  protocols = 0x0000000000000000
  instanceProperties = 0x0000000100004808
  _classProperties = 0x0000000000000000
}

通过上面调试发现只有LGA、LGB,因为创建的LGC、LGD没有添加任何属性与方法,所以只有两个

LLVM读取class_ro和分类

分类的加载需要排序。由attachList可知methodList中存储的是LGA的数组指针(排序) 、LGB的数组指针(排序)、主类的数组指针(排序)

  • 查看attachToClass源码,最终执行到attachCategories
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
    
    auto rw = cls->data();
    auto ro = rw->ro();
    const char *mangledName = cls->nonlazyMangledName();
    if (strcmp(mangledName, "LGPerson") == 0)
    {
        if (!isMeta) {
            printf("%s - LGPerson....\n",__func__);
        }
    }

    for (uint32_t i = 0; i < cats_count; i++) {
        // 分类列表cats_list
        auto& entry = cats_list[i];
        // 读取分类中的方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        // 对methodList进行排序
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
  • 主类LGPerson、分类LGALGB里面都有saySomething方法,按照之前二分查找逻辑查找saySomething方法,如果没有找到,执行--(减减操作)。疑问?LGA、LGB会--几次?
  1. main函数[person saySomething];处添加断点
  2. 查看lookUpImpOrForward源码
// 慢速查找
// 方法调用 objc_msgSend
// 1 —— 发送消息objc_msgSend 缓存快速查找(cache_t)
// 2 —— 没有命中,lookUpImpOrForward慢速查找
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 省略代码
 ......
    //    加锁,目的是保证读取的线程安全
    runtimeLock.lock();

    //  是否是已知类:判断当前类是否是已经被认可的类,即已经加载的类
    checkIsKnownClass(cls);

    // 判断类是否实现,如果没有,需要先实现
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    //    递归
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // 在当前的类的方法列表中查找方法(采用二分查找算法),如果找到,则返回,将方法缓存到cache中
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            // 未找到,superclass找到父类或者父元类继续查找,如果父类是nil,默认赋值forward_imp
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                // 父类为nil,即继承链都未找到方法实现,跳出循环
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass找到父类,在父类的cache中查找.
        // 从父类缓存中查找 - 再次进入汇变查找
        // - 如果查找到done
        // - 查找不到,循环superclass,再查找父类
        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
...... //省略代码
}
  1. 查看getMethodNoSuper_nolock源码,在printf("%s -LGPerson....\n",__func__);添加断点
  2. 运行工程,断点执行到printf("%s -LGPerson....\n",__func__);,执行调试
image.png
// lldb调试信息
(lldb) p methods.beginLists()
(const method_list_t_authed_ptr *) $0 = 0x00007ffeefbfeea0
(lldb) p *$0 
(const method_list_t_authed_ptr) $1 = {
  ptr = 0x00000001000041c8
}
(lldb) p $1.ptr 
(method_list_t *const) $2 = 0x00000001000041c8
(lldb) p *$2 
(method_list_t) $3 = {
  entsize_list_tt = (entsizeAndFlags = 27, count = 16)
}
  1. 查看search_method_list_inline方法源码 -> findMethodInUnsortedMethodList方法源码
ALWAYS_INLINE static method_t *
findMethodInUnsortedMethodList(SEL key, const method_list_t *list)
{
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            // 二分查找
            return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}

/***********************************************************************
 * search_method_list_inline
 **********************************************************************/
template
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    // base相当于low地址,count是max地址,probe是middle地址
    for (count = list->count; count != 0; count >>= 1) {
        // 指针平移至中间位置
        // 从首地址 + 下标 --> 移动到中间位置(count >> 1)
        probe = base + (count >> 1);
        // 获取该位置的sel名称
        uintptr_t probeValue = (uintptr_t)getName(probe);
        // 如果查找的key的keyvalue等于中间位置(probe)的probeValue,则直接返回中间位置
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            // 分类方法同名- while 平移 -- 向前在查找,判断是否存在相同的方法,保证调用的是分类的
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        // 如果keyValue 大于 probeValue,就往probe即中间位置的右边查找,即中间位置再右移
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
  1. 上面代码while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { probe--; }为什么要执行probe--
    原因是本类方法分类方法都整合在一块了,这里一定找的是最前面(最先加载)的分类方法saySomething

class_ro_t的数据结构是怎么通过一个指针得到的?

  • 打开llvm查看class_ro_t源码
  struct class_ro_t {
    uint32_t m_flags;
    uint32_t m_instanceStart;
    uint32_t m_instanceSize;
    uint32_t m_reserved;

    lldb::addr_t m_ivarLayout_ptr;
    lldb::addr_t m_name_ptr;
    lldb::addr_t m_baseMethods_ptr;
    lldb::addr_t m_baseProtocols_ptr;
    lldb::addr_t m_ivars_ptr;

    lldb::addr_t m_weakIvarLayout_ptr;
    lldb::addr_t m_baseProperties_ptr;

    std::string m_name;

    bool Read(Process *process, lldb::addr_t addr);
  };

// 读取当前address
bool ClassDescriptorV2::class_ro_t::Read(Process *process, lldb::addr_t addr) {
  size_t ptr_size = process->GetAddressByteSize();

  size_t size = sizeof(uint32_t)   // uint32_t flags;
                + sizeof(uint32_t) // uint32_t instanceStart;
                + sizeof(uint32_t) // uint32_t instanceSize;
                + (ptr_size == 8 ? sizeof(uint32_t)
                                 : 0) // uint32_t reserved; // __LP64__ only
                + ptr_size            // const uint8_t *ivarLayout;
                + ptr_size            // const char *name;
                + ptr_size            // const method_list_t *baseMethods;
                + ptr_size            // const protocol_list_t *baseProtocols;
                + ptr_size            // const ivar_list_t *ivars;
                + ptr_size            // const uint8_t *weakIvarLayout;
                + ptr_size;           // const property_list_t *baseProperties;

  DataBufferHeap buffer(size, '\0');
  Status error;

  process->ReadMemory(addr, buffer.GetBytes(), size, error);
  if (error.Fail()) {
    return false;
  }

  DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(),
                          process->GetAddressByteSize());

  lldb::offset_t cursor = 0;
  // 对class_ro_t中相应的成员变量进行赋值
  m_flags = extractor.GetU32_unchecked(&cursor);
  m_instanceStart = extractor.GetU32_unchecked(&cursor);
  m_instanceSize = extractor.GetU32_unchecked(&cursor);
  if (ptr_size == 8)
    m_reserved = extractor.GetU32_unchecked(&cursor);
  else
    m_reserved = 0;
  m_ivarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
  m_name_ptr = extractor.GetAddress_unchecked(&cursor);
  m_baseMethods_ptr = extractor.GetAddress_unchecked(&cursor);
  m_baseProtocols_ptr = extractor.GetAddress_unchecked(&cursor);
  m_ivars_ptr = extractor.GetAddress_unchecked(&cursor);
  m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
  m_baseProperties_ptr = extractor.GetAddress_unchecked(&cursor);

  DataBufferHeap name_buf(1024, '\0');

  process->ReadCStringFromMemory(m_name_ptr, (char *)name_buf.GetBytes(),
                                 name_buf.GetByteSize(), error);

  if (error.Fail()) {
    return false;
  }

  m_name.assign((char *)name_buf.GetBytes());

  return true;
}
  • llvm中寻找哪里调用了Read方法?最终查找到在Read_class_row方法中调用
bool ClassDescriptorV2::Read_class_row(
    Process *process, const objc_class_t &objc_class,
    std::unique_ptr &class_ro,
    std::unique_ptr &class_rw) const {
  class_ro.reset();
  class_rw.reset();

  Status error;
  uint32_t class_row_t_flags = process->ReadUnsignedIntegerFromMemory(
      objc_class.m_data_ptr, sizeof(uint32_t), 0, error);
  if (!error.Success())
    return false;

  if (class_row_t_flags & RW_REALIZED) {
    class_rw = std::make_unique();

    if (!class_rw->Read(process, objc_class.m_data_ptr)) {
      class_rw.reset();
      return false;
    }

    class_ro = std::make_unique();

    if (!class_ro->Read(process, class_rw->m_ro_ptr)) {
      class_rw.reset();
      class_ro.reset();
      return false;
    }
  } else {
    class_ro = std::make_unique();

    if (!class_ro->Read(process, objc_class.m_data_ptr)) {
      class_ro.reset();
      return false;
    }
  }
  return true;
}

类扩展分析

面试题:类扩展分类的区别
1、category 类别、分类

  • 专门用来给类添加新的方法
  • 不能给类添加成员属性,添加了成员属性,也无法取到
  • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写setter、getter方法
  • 分类中用@property定义变量,只会生成变量的setter、getter方法的声明,不能生成方法实现带下划线的成员变量

2、extension 类扩展

  • 可以说成是特殊的分类 ,也可称作匿名分类
  • 可以给类添加成员属性,但是是私有变量
  • 可以给类添加方法,也是私有方法

类的扩展有两种创建方式

  • 直接在类中书写:永远在声明之后,在实现之前(需要在.m文件中书写)
  • 通过 command+N 新建 -> Objective-C File -> 选择Extension

下面写一个类扩展

@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
  • 通过clang -rewrite-objc main.mm -o main.cpp命令生成cpp文件,打开cpp文件,搜索ext_name属性
struct LGStudent_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _ext_age;
    NSString *_name;
    NSString *_ext_name;
};
  • 查看成员变量列表_ivar_list_t,发现有两个成员变量_name_ext_name
static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[4];
} _OBJC_$_INSTANCE_VARIABLES_LGStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    4,
    {{(unsigned long int *)&OBJC_IVAR_$_LGStudent$_age, "_age", "i", 2, 4},
     {(unsigned long int *)&OBJC_IVAR_$_LGStudent$_ext_age, "_ext_age", "i", 2, 4},
     {(unsigned long int *)&OBJC_IVAR_$_LGStudent$_name, "_name", "@\"NSString\"", 3, 8},
     {(unsigned long int *)&OBJC_IVAR_$_LGStudent$_ext_name, "_ext_name", "@\"NSString\"", 3, 8}}
};
  • 查看对象方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[18];
} _OBJC_$_INSTANCE_METHODS_LGStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    18,
    {{(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_LGStudent_instanceMethod},
    {(struct objc_selector *)"ext_instanceMethod", "v16@0:8", (void *)_I_LGStudent_ext_instanceMethod},
    {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGStudent_name},
    {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGStudent_setName_},
    {(struct objc_selector *)"age", "i16@0:8", (void *)_I_LGStudent_age},
    {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LGStudent_setAge_},
    {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LGStudent_ext_name},
    {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LGStudent_setExt_name_},
    {(struct objc_selector *)"ext_age", "i16@0:8", (void *)_I_LGStudent_ext_age},
    {(struct objc_selector *)"setExt_age:", "v20@0:8i16", (void *)_I_LGStudent_setExt_age_},
    {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGStudent_name},
    {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGStudent_setName_},
    {(struct objc_selector *)"age", "i16@0:8", (void *)_I_LGStudent_age},
    {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LGStudent_setAge_},
    {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LGStudent_ext_name},
    {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LGStudent_setExt_name_},
    {(struct objc_selector *)"ext_age", "i16@0:8", (void *)_I_LGStudent_ext_age},
    {(struct objc_selector *)"setExt_age:", "v20@0:8i16", (void *)_I_LGStudent_setExt_age_}}
};

查看 LGTeacher 类拓展的方法,在编译过程中,方法就直接添加到了methodlist中,作为类的一部分,即编译时期直接添加到本类里面

通过源码调试探索

  • 创建LGPerson+LG.h即类的扩展,并声明两个方法
@interface LGPerson ()
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
  • LGPeron.m中实现这两个方法
- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}
  • 运行objc源码程序,在realizeClassWithoutSwift中断住
image.png
(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x00000001000046f0
  Fix-it applied, fixed expression was: 
    ro->baseMethods()
(lldb) p *$0 
(method_list_t) $1 = {
  entsize_list_tt = (entsizeAndFlags = 24, count = 11)
}
(lldb) p $1.get(0).big()
(method_t::big) $2 = {
  name = "saySomething"
  types = 0x0000000100003c65 "v16@0:8"
  imp = 0x0000000100003870 (KCObjcBuild`-[LGPerson saySomething])
}
(lldb) p $1.get(1).big()
(method_t::big) $3 = {
  name = "sayHello1"
  types = 0x0000000100003c65 "v16@0:8"
  imp = 0x00000001000038a0 (KCObjcBuild`-[LGPerson sayHello1])
}
(lldb) p $1.get(2).big()
(method_t::big) $4 = {
  name = "ext_instanceMethod"
  types = 0x0000000100003c65 "v16@0:8"
  imp = 0x00000001000038d0 (KCObjcBuild`-[LGPerson ext_instanceMethod])
}

得出结论:

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

关联对象初探

关联对象的底层原理的实现,主要分为三部分:

  • 通过objc_setAssociatedObject设值流程
  • 通过objc_getAssociatedObject取值流程
  • 通过objc_removeAssociatedObjects移除关联对象
// 取值流程
id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

// 设值流程
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

// 移除关联对象
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object, /*deallocating*/false);
    }
}
关联对象

关联对象底层分析

关联对象-设值流程
  • 分类LGA中重写属性cate_nameset、get方法,通过runtime的属性关联方法实现
// 分类添加cate_name、cate_age属性
@interface LGPerson (LGA)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, copy) NSString *cate_age;

- (void)saySomething;
- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod2;
+ (void)cateA_classMethod1;
+ (void)cateA_classMethod2;
@end

// LGPerson+LGA.m文件
- (void)setCate_name:(NSString *)cate_name{
    /**
     1: 对象 2: 标识符 3: value 4: 策略
     */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}

- (void)setCate_age:(NSString *)cate_age{
    objc_setAssociatedObject(self, "cate_age", cate_age, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_age{
    return objc_getAssociatedObject(self, "cate_age");
}
  • 运行程序,断点断在main中person.cate_name = @"KC";赋值处
image.png
  • 进入_object_set_associative_reference源码实现,关于关联对象底层原理的探索主要是看value存到了哪里, 以及如何取出value ,以下是源码
// 重磅提示  重点
/**
 关联对象 : 存储 object - cate_name -> value - policy
 */
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
    DisguisedPtr disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用
    // 包装一下 policy - value
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        // 初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
        AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
        AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对
            if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
                /* it's the first association we make  第一次建立关联*/
                isFirstAssociation = true;//标记位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);

                    }
                }
            }
        }
    }

    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();//释放
}
  • _object_set_associative_reference源码中添加断点进行调试
image.png
// lldb调试信息
(lldb) p disguised
(DisguisedPtr) $0 = (value = 18446744069394321632)
(lldb) p association
(objc::ObjcAssociation) $1 = {
  _policy = 3
  _value = 0x0000000100004080 "KC"
}
  • 注意AssociationsManager manager;是个析构函数,并不是单例。
// main.m文件添加如下结构体,进行验证是析构函数
struct LGObjc {
    LGObjc()   { printf("KC 来了 \n");}  //构造函数
    ~LGObjc()  {  printf("大师班 NB \n"); } //析构函数
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGObjc kc;
    }
    return 0;
}

// 运行工程打印如下
KC 来了 
大师班 NB 
  • 查看AssociationsManager源码,定义AssociationsManager类型的变量,相当于自动调用AssociationsManager的析构函数进行初始化。加锁lock,并不代表 唯一,只是为了避免多线程重复创建,其实在外面是可以定义多个AssociationsManager manager;
class AssociationsManager {
    using Storage = ExplicitInitDenseMap, ObjectAssociationMap>;
    static Storage _mapStorage; //静态变量

public://构造加锁的原因是 -- 避免多线程重复创建
    AssociationsManager()   { AssociationsManagerLock.lock(); } //构造函数,自动调用
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }//析构函数,自动调用

    AssociationsHashMap &get() {
        return _mapStorage.get();//从静态变量中获取出来,所以全场唯一
    }

    static void init() {
        _mapStorage.init();
    }
};
  • 定义AssociationsHashMap类型的哈希map是全场唯一的,从哪里可以体现呢?
    通过_mapStorage.get()生成哈希map,其中_mapStorage是一个静态变量,所以哈希map永远是通过静态变量获取出来的,是全场唯一的。AssociationsHashMap哈希表是一个单例

  • 继续上面lldb调试

image.png
(lldb) p associations
(objc::AssociationsHashMap) $0 = {
  Buckets = 0x0000000000000000
  NumEntries = 0
  NumTombstones = 0
  NumBuckets = 0
}
  • associations调用try_emplace方法,传入一个对象disguised和 一个空的关联mapObjectAssociationMap{}
(lldb) p refs_result
(std::pair, objc::DenseMap, objc::DenseMapInfo, objc::detail::DenseMapPair >, objc::DenseMapValueInfo, objc::DenseMapInfo, objc::detail::DenseMapPair > >, objc::DenseMapInfo >, objc::detail::DenseMapPair, objc::DenseMap, objc::DenseMapInfo, objc::detail::DenseMapPair > >, false>, bool>) $1 = {
  first = {
    Ptr = 0x0000000100828a20
    End = 0x0000000100828aa0
  }
  second = true
}
  • 查看try_emplace方法的源码实现
  // it is not moved. 如果key不在map中,则直接构造,否则它不会移动
  template 
  std::pair try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))//LookupBucketFor找桶子,其中key是关联对象
      return std::make_pair(//如果桶子存在则返回,置为false的原因是哈希map中已经存在
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map. 已经存在直接返回false

    // Otherwise, insert the new element. 第一次来则插入 -- true
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...); //往桶里添加值
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);//bool值置为true,表示第一次往哈希map中添加桶子
  }
  • 查看LookupBucketFor源码,有两个同名方法,其中第二个方法属于重载函数,区别于第一个的是第二个参数没有const修饰,通过调试可知,外部的调用是调用的第二个重载函数,而第二个LookupBucketFor方法内部的实现是调用第一个LookupBucketFor方法
image.png
  • 断点运行至try_emplace方法中的获取bucket部分TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...);
image.png
(lldb) p TheBucket
(objc::detail::DenseMapPair *) $2 = 0x0000000000000000
  • 进入查看InsertIntoBucket源码
template 
  BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                BucketT *TheBucket) {
    // If the load of the hash table is more than 3/4, or if fewer than 1/8 of
    // the buckets are empty (meaning that many are filled with tombstones),
    // grow the table.
    //
    // The later case is tricky.  For example, if we had one empty bucket with
    // tons of tombstones, failing lookups (e.g. for insertion) would have to
    // probe almost the entire table until it found the empty bucket.  If the
    // table completely filled with tombstones, no lookup would ever succeed,
    // causing infinite loops in lookup.
    unsigned NewNumEntries = getNumEntries() + 1;
    unsigned NumBuckets = getNumBuckets();
    // 3/4扩容
    if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
      this->grow(NumBuckets * 2);
      LookupBucketFor(Lookup, TheBucket);
      NumBuckets = getNumBuckets();
    } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                             NumBuckets/8)) {
      this->grow(NumBuckets);
      LookupBucketFor(Lookup, TheBucket);
    }
    ASSERT(TheBucket);
......//省略代码
  • 调试断点回到_object_set_associative_reference方法
image.png
(lldb) p refs
(objc::DenseMap, objc::DenseMapInfo, objc::detail::DenseMapPair >) $3 = {
  Buckets = 0x0000000100706680
  NumEntries = 1
  NumTombstones = 0
  NumBuckets = 4
}
(lldb) p result
(std::pair, objc::DenseMapInfo, objc::detail::DenseMapPair, false>, bool>) $4 = {
  first = {
    Ptr = 0x00000001007066b0
    End = 0x00000001007066e0
  }
  second = true
}
(lldb) p $4.first 
(objc::DenseMapIterator, objc::DenseMapInfo, objc::detail::DenseMapPair, false>) $5 = {
  Ptr = 0x00000001007066b0
  End = 0x00000001007066e0
}
// 这就是存进去的bucket
(lldb) p $5.Ptr 
(objc::DenseMapIterator, objc::DenseMapInfo, objc::detail::DenseMapPair, false>::pointer) $6 = 0x00000001007066b0

最终探索到bucket是通过InsertIntoBucket方法的这一段代码TheBucket->getFirst() = std::forward(Key); ::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);插进去的。

第一次执行try_emplace插入的是一个空桶还没有值,第二次执行try_emplace才插入值,即往空桶中插入ObjectAssociationMap(value,policy),返回true
所以关联对象的设值图示如下,有点类似于cache_t中的insert方法插入sel-imp的逻辑

关联对象设值
关联对象: 设值流程
  • 创建一个AssociationsManager管理类
  • 获取唯一的全局静态哈希Map
  • 判断是否插入的关联值是否存在:
    1: 存在走第4步
    2: 不存在就走 : 关联对象插入空流程
  • 创建一个空的ObjectAssociationMap去取查询的键值对
  • 如果发现没有这个 key 就插入一个 空的BucketT进去返回
  • 标记对象存在关联对象
  • 用当前 修饰策略 和 值 组成了一个ObjcAssociation 替换原来 BucketT 中的空
  • 标记一下 ObjectAssociationMap 的第一次为 false
关联对象插入空流程
  • 根据DisguisedPtr找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 清理迭代器
  • 其实如果插入空值 相当于清除
关联对象的取值流程自己探索,这里简单讲述下取值流程
关联对象: 取值流程
  • 创建一个 AssociationsManager 管理类
  • 获取唯一的全局静态哈希Map
  • 根据 DisguisedPtr 找到 AssociationsHashMap中的 iterator 迭代查询器
  • 如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value)
  • 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 返回_value
    总结: 其实就是两层哈希map , 存取的时候两层处理(类似二位数组)

你可能感兴趣的:(类加载原理补充-关联对象底层原理)