《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理15-类的加载下》详细介绍了类和分类的懒加载和非懒加载搭配情况下,方法的加载流程,本文介绍类的扩展和关联对象底层原理
LLVM源码下载地址
1.方法排序中类中的方法的name的内存地址排序,内存地址从哪儿得来,有什么规则?
我们都知道方法经过prepareMethodLists
--> fixupMethodList
排序后变得有序,排序是通过name的内存地址进行排序的,name地址从哪儿取值呢?
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
meth.name = sel_registerNameNoLock(name, bundleCopy);
}
}
// sel - imp
// Sort by selector address.
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// Mark method list as uniqued and sorted
mlist->setFixedUp();
}
SEL sel_registerNameNoLock(const char *name, bool copy) {
return __sel_registerName(name, 0, copy); // NO lock, maybe copy
}
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) selLock.assertUnlocked();
else selLock.assertLocked();
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
auto it = namedSelectors.get().insert(name);
if (it.second) {
// No match. Insert.
*it.first = (const char *)sel_alloc(name, copy);
}
return (SEL)*it.first;
}
static SEL search_builtins(const char *name)
{
#if SUPPORT_PREOPT
if (builtins) {
SEL result = 0;
if ((result = (SEL)builtins->get(name)))
return result;
if ((result = (SEL)_dyld_get_objc_selector(name)))
return result;
} else if (useDyldSelectorLookup) {
if (SEL result = (SEL)_dyld_get_objc_selector(name))
return result;
}
#endif
return nil;
}
method.name从result = search_builtins(name)中取值,条件为useDyldSelectorLookup,查看条件的初始化发现map_images_nolock
-->sel_init(selrefCount)
-->useDyldSelectorLookup = true
,程序进入(SEL)_dyld_get_objc_selector(name)
,走入dyld源码_dyld_get_objc_selector
-->gAllImages.getObjCSelector(selName)
--> _objcSelectorHashTable->getString(selName, _objcSelectorHashTableImages.array())
,name的地址来源于段的基本地址+相应偏移量,name的内存地址是随机变化的,根据编译器调试处理的,新增库和方法会导致selectionBaseAddress地址变化和偏移量变化
const char* _dyld_get_objc_selector(const char* selName)
{
// Check the shared cache table if it exists.
if ( gObjCOpt != nullptr ) {
if ( const objc_opt::objc_selopt_t* selopt = gObjCOpt->selopt() ) {
const char* name = selopt->get(selName);
if (name != nullptr)
return name;
}
}
if ( gUseDyld3 )
return dyld3::_dyld_get_objc_selector(selName);
return nullptr;
}
const char* _dyld_get_objc_selector(const char* selName)
{
log_apis("dyld_get_objc_selector()\n");
return gAllImages.getObjCSelector(selName);
}
2.MachO文件格式,读到相应的data(),read data()中的地址怎么直接变成class_ro_t格式的?那么什么时候编译成class_ro_t的格式编译到MachO中的呢?在MachO中是地址指针的形式存在
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
- 1.查看
class_ro_t
的数据结构
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
MachO文件在编译期完成,肯定有一个方法在编译期能读取data()中内存地址为class_ro_t,查看llvm源码
在llvm中查看 struct class_ro_t源码,llvm通过Read方法读取data()中的内存地址,给class_ro_t中的元素依次按顺序赋值,那么class_ro_t::Read方法在哪里调用的呢?
Read_class_row
中会连续调用class_rw->Read
和class_ro->Read
进而给class_rw_t
和class_ro_t
赋值,那什么时候调用Read_class_row
的呢?Read_class_row
和Read_objc_class
相生相随,Read_class_row
属于模板类,调用Describe方法之前都会调用ClassDescriptorV2进行初始化,初始化后调用Describe,GetClassName,GetInstanceSize都会调用Read_class_row
,从而触发class_ro_t
和class_rw_t
赋值
3.类的扩展在编译期就会作为类的一部分编译进去,和分类的加载过程不一样,分类是为了动态的开辟,类扩展作为类的一部分跟类一起伴随着永生下去
A.category:类别,分类
- I 专门用来给类添加新的方法
- II 不能给类添加成员属性,添加了成员变量,也无法取到
- III 可以通过runtime给分类添加属性
- IV 分类中用@property定义变量,只会生成变量的setter、getter方法的声明,不能生成方法实现和带下划线的成员变量
B.extension:类扩展
- I 可以说是特殊的分类,也称作匿名分类
- II 可以给类添加成员属性,但是是私有变量
- III 可以给类添加方法,也是私有方法
给LGPerson+LGA中增加属性cate_name,在main函数中调用person.cate_name = @"KC"
变色但运行没有实现会崩溃
类扩展,类扩展必须放到类的声明之后,实现之前,即@interface和@implementation中间,否则编译报错,那么类扩展的本质是什么呢?我们通过clang查看类扩展的本质
类扩展中声明的属性会自动生成带下划线的成员变量和setter、getter方法,类扩展中方法和类中方法一模一样,没有实现load方法会在第一次消息发送的时候将方法加载到类中,
realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 LGTeacher
realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 LGTeacher
realizeClassWithoutSwift: 这个是我要研究的 LGTeacher
methodizeClass: 这个是我要研究的 LGTeacher
prepareMethodLists: 这个是我要研究的 LGTeacher
attachToClass: 这个是我要研究的 LGTeacher
实现了load方法,类的扩展在编译期就会作为类的一部分编译进MachO文件中,方法在编译时期就进入ro文件中了,直接从MachO文件的data()中读取出来,过程和上一篇文章《iOS-底层原理15-类的加载下》分析的一致,分类是为了动态的开辟,类的扩展不需要动态开辟,作为类的一部分跟类一起加载
4.类的扩展可以填加load方法变为非懒加载类吗?不能,没有.m文件和@implementation实现类
关联对象底层原理:分类无法添加属性,要添加属性的话,需要重写setter、getter方法添加关联对象,关联对象传入4个参数,对象,标识符,value值,关联策略
objc_setAssociatedObject --> SetAssocHook.get()(object, key, value, policy) --> _base_objc_setAssociatedObject
--> _object_set_associative_reference(object, key, value, policy)
SetAssocHook.get()为一层接口模式,整个对外暴露的objc_setAssociatedObject永远不变,中间的这一层是有处理的,可以增加接口拦截之类做一些其他处理,类似于reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy)
调用SetAssocHook.get()(object, key, value, policy)相当于_base_objc_setAssociatedObject(object, key, value, policy)从而进入_object_set_associative_reference(object, key, value, policy)方法
acquireValue对value的值进行处理,传入的策略是retain或copy做相应的操作,其他策略不做处理
程序继续往下之心,构造函数和析构函数
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
类似于在main.m中写
struct LGObjc {
LGObjc() { printf("来了");}
~LGObjc() { printf("走了"); }
};
关联对象存在一张大的哈希表,AssociationsHashMap里面存储LGPerson和LGTeacher等的,此哈希表唯一,方便查找,AssociationsHashMap是静态变量获取出来的,全场唯一,但是AssociationsManager不唯一,加锁代表防止多线程重复创建,并不是代表不能创建
查看refs_result的格式存在五个键值对
根据对象去AssociationsHashMap总表中查找关联对象LGPerson桶子,若找到,直接返回LGPerson桶子和bool值为false,代表桶子已经存在,不是第一次进入,若没找到,插入一个新的空的LGPerson桶子和bool值为true,如果第一次执行object->setHasAssociatedObjects()
,标记为nonpointerisa
若value传值为nil,则从AssociationsHashMap中移除桶子,关联对象也消除
查看第一次时,TheBucket的值,和refs_result的键值对中最后一个键值对DenseMapPair完全一样, refs_result存在五个属性,TheBucket桶子藏在detail中
首先去安放关联对象,查找关联对象作为key对应的桶子是否已经存在,若存在返回找到的关联对象LGPerson桶子,若不存在,返回一个空的关联对象LGPerson桶子并给默认值,空桶子赋值前后对比
关联对象桶子和属性桶子赋值前后对比,属性桶子objc::detail::DenseMapPair
作为关联对象桶子(objc::detail::DenseMapPair, objc::DenseMap, objc::DenseMapInfo, objc::detail::DenseMapPair > > *)
的最后一个属性存在于关联对象桶子中,知道属性桶子的结构为DenseMapPair后,通过方法获取存入的值
取返回值的refs_result.first->second,此时安放key也就是"cate_name"作为key,association{policy, value}(3,"KC")作为Value到属性cate_name桶子中,若"cate_name"对应的桶子已经存在,则直接返回属性桶子,若不存在,再次返回一个以"cate_name"为key的空属性桶子
此时将key("cate_name"),policy(3),value("KC")插入到返回的以cate_name为key的属性桶子中,对象的属性就和对象产生了关联,返回std::make_pair( makeIterator(TheBucket, getBucketsEnd(), true), true)
,则result.second为true