分类加载的补充
objc4-818.2源码
中创建LGPerson
类以及LGPerson+LGA
、LGPerson+LGB
分类,其中LGPerson类中未实现+ (void)load{}
方法,分类中实现了load
方法。源码load_categories_nolock
、attachCategories
方法中分别添加了如下代码
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+LGC
、LGPerson+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
方法添加断点,重新运行工程进行调试
主类中没有实现load
方法,迫使执行prepare_load_methods
方法,继续调试执行到realizeClassWithoutSwift
-> attachCategories
疑问:明明有四个分类,这里为什么之后两个?
// 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
、分类LGA
、LGB
里面都有saySomething
方法,按照之前二分查找逻辑
查找saySomething
方法,如果没有找到,执行--(减减操作)
。疑问?LGA、LGB
会--几次?
-
main
函数[person saySomething];
处添加断点 - 查看
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;
}
}
...... //省略代码
}
- 查看
getMethodNoSuper_nolock
源码,在printf("%s -LGPerson....\n",__func__);
添加断点 - 运行工程,断点执行到
printf("%s -LGPerson....\n",__func__);
,执行调试
// 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)
}
- 查看
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;
}
- 上面代码
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
中断住
(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_name
的set、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";
赋值处
- 进入
_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
源码中添加断点进行调试
// 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调试
(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
方法
- 断点运行至
try_emplace
方法中的获取bucket部分TheBucket = InsertIntoBucket(TheBucket, Key, std::forward
(Args)...);
(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
方法
(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
插进去的。
第一次执行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
, 存取的时候两层处理(类似二位数组)