在上篇文章 中,我们探索了load_images的作用和map_images的部分原理,我们知道了类的加载实际是通过realizeClassWithoutSwift进行的加载,接下来我们继续进行探索。
在realizeClassWithoutSwift最下面是进行处理分类methodizeClass,我们继续查看其实现
static void methodizeClass(Class cls, Class previously) {
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
......
}
我们看到,其主要就是将ro的属性、方法、协议赋值给rwe的操作。我们运行进去后其实rwe是nil,那rwe在哪里进行的初始化呢?我们之前也说过,有分类的时候可能会创建rwe,我们先在源码中全局搜索rwe =来查看其创建位置,我们可以查到类似rwe = cls->data()->extAllocIfNeeded()的的方法。所以我们可以确定rwe是由extAllocIfNeeded进行的创建,我们搜索其创建位置:
- 运行时方法:
_class_addProperty、class_addProtocol、class_setVersion、addMethod及addMethods调用的addMethods_finish - 其他方法:
attachCategories、objc_class::demangledName、objc_duplicateClass。 在源码中对这些方法进行断点WTPerson,if (strcmp(cls->nonlazyMangledName(), "WTPerson") == 0 && !isMeta) { },我们发现创建了分类可是并没有走任何的断点,也就是说并没有创建rwe。那添加分类后什么时候会创建
rwe呢?我们在分类里添加+load方法,然后发现只有同时实现类的+load和分类的+load方法后才会在attachCategories中命中调试。所以我们可以得出结论 - 创建的分类和本类都是
非懒加载类时才会初始化rwe。 我也也从断点的堆栈信息中可以得到attachCategories是load_images->loadAllCategories->load_categories_nolock中进行的调用。
分类初始化
首先我们来查看一下分类中都可以存储哪些数据
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
我们可以看到其内部包含实例方法、类方法,类的属性及协议、元类的属性及协议,所以我们可以得出分类是没有元类的,分类的数据结构中没有ivars,所以分类无法存储成员变量。
我们将类和分类都写在main.m文件中,然后用clang编译一下main.m文件,我们发现我们可以获取到属性和我们自己写的方法,可是属性的setter和getter方法我们无法在cpp文件中查询到,也就是说分类中没有实现属性的setter和getter方法,需要我们在分类中自己去写。
我们在类中写setter和getter方法是直接对类的成员变量进行的操作,分类中没有成员变量,所以我们需要用到runtime的关联对象进行赋值及取值操作
objc_setAssociatedObject和objc_getAssociatedObject
在源码中我们可以看到objc_setAssociatedObject就是调用的_object_set_associative_reference函数,我们查看一下其实现
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) {
......
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;
AssociationsHashMap &associations(manager.get());
if (value) {
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 */
auto &refs = refs_result.first->second;
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)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
第一个操作DisguisedPtr<objc_object> disguised{(objc_object *)object};,其将我们需要关联的对象object(self)封装成DisguisedPtr的类型,统一格式方便后面使用。
第二个操作ObjcAssociation association{policy, value};, 基于policy将value进行封装,ObjcAssociation的方法acquireValue、releaseHeldValue会根据policy策略对value进行retain和release操作。
- OBJC_ASSOCIATION_ASSIGN assgin
- OBJC_ASSOCIATION_RETAIN_NONATOMIC strong nonatomic
- OBJC_ASSOCIATION_COPY_NONATOMIC copy nonatomic
- OBJC_ASSOCIATION_RETAIN strong atomic
- OBJC_ASSOCIATION_COPY copy atomic
AssociationsManager
看到这个我们的第一想法就是单例,因为manager标识我们最常用的就是单例, AssociationsManager其实并不是单例类,只是构造和析构函数,执行AssociationsManager manager;会调用构造函数,出了作用域会调用析构函数,也就是加锁及解锁的操作,为了保证同一时间只有一个线程操作AssociationsHashMap
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap
AssociationsHashMap是一个存储DisguisedPtr和ObjectAssociationMap的hash表,ObjectAssociationMap存储的就是ObjcAssociation
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
其获取方式是通过_mapStorage.get()获取的,_mapStorage是一个静态变量,说明AssociationsHashMap是在程序的整个内存存储中只有一份。
接下来会通过value是否有值进行判断,没有值的话会使用erase()删除hash和bucket中的关联关系,有值会通过associations.try_emplace去获取迭代器(遍历整个集合的一种方法)
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair( makeIterator(TheBucket, getBucketsEnd(), true), false); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true), true);
}
熟悉的bucketT,也就是说先查询缓存中是否已经有一份object的关联对象缓存,没有找到会插入到缓存中,然后返回迭代器,然后根据这个迭代器进行数据更新。
以上是objc_setAssociatedObject,objc_getAssociatedObject就是根据上面的迭代器根据key去进行查找
id _object_get_associative_reference(id object, const void *key) {
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
最下面会将对象放入自动释放池中,所以我们不需要考虑内存管理问题。在对象dealloc的时候,会通过objc_rootDealloc -> object_dispose -> objc_destructInstance -> _object_remove_assocations,在_object_remove_assocations内部调用releaseHeldValue进行release操作。
attachLists
我们之前也了解了分类和本类都是非懒加载类时才会初始化rwe,同时在methodizeClass和attachCategories中都是调用attachLists进行的数据插入,methodizeClass比attachCategories先执行,我们来看一下插入的源码
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--) //旧数据方法数组放在后面
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++) //新数据方法数组放在前面
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
} else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
} else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList; //旧数据方法(本类的方法)数组放在后面
for (unsigned i = 0; i < addedCount; i++) //新数据方法数组放在前面
array()->lists[i] = addedLists[i];
validate();
}
}
我们根据代码可以看到,其内部是存储的二维数组,每次都是将旧数据的方法列表放在后面,后面插入的方法数组会插入到最前面,分类在本类后调用,所以后编译的分类方法会放在最前面。
总结
rwt的初始化
- 分类和本类都是
非懒加载类时才会初始化 - runtime动态添加属性、协议、方法时才会初始化
分类的属性需要使用runtime的关联对象处理setter及getter方法,其将关联对象放在全局唯一的
AssociationsHashMaphash表中,根据key进行更新、读取和移除,同时将assocations放在自动释放池中,进行内存管理。
分类在本类后调用,所以后编译的分类方法会放在最前面。