前言
iOS类的加载原理(上)以及iOS类的加载原理(下)这两篇文章比较全面的介绍了类的加载原理,这篇文章主要是对以上进行一些补充,并对类的扩展和关联对象进行分析。
分类加载的补充
methodList数据结构
methodList本身是array_t的
首先主类RoPerson的Load注释掉,RoPerson+RoA和RoPerson+RoB分类的Load开启,
运行项目,发现在load_categories_nolock这个函数断点卡住了,如图:
我们继续运行,在attachCategories这个函数停住,如图:
2
在这里我们分析下methodList,如图:
3
这是methodList的结构,这时有method_t,method_list_t。
在图2当中method_list_t存储的是指针,接下来我们打印下method_list_t的数据,如图:
4
这里的$1是数组结构,数组结构可以用下标的方示获取,但这里不是,而是用get方法获取是拿到的指址地址,这里的指针地址是指向method_list_t它是继续entsize_list_tt的一个结构体,通过分析,它是有get方法的,这里不再赘述。我把它的get方法截图:
5
从这里可以看出get方法调用了getOrEnd方法,这里是通过获取当前地址指针不断的通过位移this,再强制转换PointerModifier类型。
从上可以分析中,methodList存的是指针,而不是method_t的数据结构。
主类没有实现load,分类实现load,数据加载的问题
iOS类的加载原理(下)这篇文章有介绍过,主类没有实现load,分类实现load,它的流程是:
_read_image -> realizeClassWithoutSwift -> methodsizeClass -> attachToClass -> 没有走attachCategories。
我们再测试一下,得到如下图:
从上图中可以看出这里调用了attachCategories函数。
现在是A分类和B分类的的load都是打开的,如果我们关闭其中一个分类的load方法,发现不调用attachCategories函数,我们再创建一个分类C,再把C的load方法打开,把A的分类关闭,发现也是调用了attachCategories函数。
从上分析可以得到,多个分类时,主类的load方法关闭,分类有一个以上的Load方法打开,就会调用attachCategories函数。
我们看下堆栈,如图:
7
Load_image会调用prepare_load_methods这个方法,准备load的方法,它里面调用了_getObjc2NonlazyCategoryList(获取非懒加载分类列表),同时也会调用realizeClassWithoutSwift这个函数。同时我们还可以发现,如果分类里面什么时候都没实现,不会进行加载的。
分类加载是否需要排序
我们先把C和D分类删除,从methods_list的分析来看,methods_list应该存的是RoA数组指针(会进行排序),RoB数组指针(会进行排序),主类的数组指针(会进行排序)(iOS类的加载原理(下)这里有介绍)。
我们在getMethodNoSuper_nolock这个函数的测试代码中打断点并运行项目,如图:
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
这里就是遍历array_t,我们再来看下methods.beginLists(),如图:
从上分析分类加载是不需要排序
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);
};
以上是我们从llvm的源码中找到的class_ro_t的结构,
这里的bool Read(Process process, lldb::addr_t addr);这行代码读取当前的address,然后经过相关处理,然后再通过extractor读取到方法列表,经过分析,read函数是在Read_class_row和Read_objc_class调用,在* Read_objc_class(读取类)**对当前数据结构赋值(编译时期)。
类的扩展分析
我们先介绍类别,分类,类扩展的概念,如下:
10
贴下main.m代码
@interface RoStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface RoStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation RoStudent
- (void)instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)classMethod{
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
其中
@interface RoStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
就是为RoStudent类添加了一个类扩展。
然后我们通过clang命令翻译成C++文件。
打开这个C++文件,搜索ext_instanceMethod这个方法,如图:
这里找到了RoStudent添加的方法,还有属性(被注释),再次搜索,如图:
12
这里可以看到加载进去了,ext_instanceMethod会随着主类的加载而加载。
我们再为RoPerson添加一个类扩展(RoPerson_Ro.h文件名),代码如下:
@interface RoPerson ()
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
我们在realizeClassWithoutSwift这个函数打断点运行项目,如图:
然后执行如下图的命令:
14
我们找到了** ext_instanceMethod**这个方法。
类的扩展数据加载是做为类的一部分加载进来了。
关联对象
我们先看下图:
15
在分类A中,这两个警告,因为我们添加的两个属性导致,那么为什么会这样,我们接着分析。
我们先把RoA分类中的关联对象的代码贴出来,如下:
- (void)setCate_name:(NSString *)cate_name{
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");
}
我们进入objc_setAssociatedObject这个函数的实现,如图:
我们再看下_object_set_associative_reference这个函数的代码:
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));
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();
}
isFirstAssociation这时的代码块是这个函数的重点, 我们来分析下,它到底做了什么(关联对象的目的,是存储)。
我们先分析下它的参数void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t )
key是键的key。
value是键值。
policy缓存策略,retain或者copy。
object是被关联者。
DisguisedPtr<objc_object> disguised{(objc_object *)object};是ptr的处理,就是value的处理,包装成统一数据结构。
ObjcAssociation association{policy, value};进行了存储。
我们打个断点,并运行项目,开始调试。
断点执行到_object_set_associative_reference这个函数的184行代码,停住。
执行终端命令,如图:
说明是正确的,因为我们传的值就是Ro。
AssociationsManager manager;这个会自动执行构造函数和析构函数,源码如下:
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
在这代码块中间会进行锁上和打开。
AssociationsManagerLock是spinlock_t,而** spinlock_t又是mutex_tt<LOCKDEBUG>就是互斥锁,这里是AssociationsHashMap这个哈希表的单例,因为AssociationsManager有一个static Storage _mapStorage;表局静态变量,AssociationsHashMap是全局唯一的。
我们打印下AssociationsHashMap数据结构,如图:
$3才是它的数据。
我们看下try_emplace**这个函数的代码,如下:
template <typename... Ts>
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.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
Bucket在之前的catche的分析中有介绍,这里** Key就是disguised是个地址,LookupBucketFor是寻找Bucket。
** try_emplace函数中找到Bucket返回,如果没找到,会添加一个新的Bucket(没有值),并返回。
auto &refs = refs_result.first->second;拿到refs_result.first的second的值。
auto result = refs.try_emplace(key, std::move(association));
这里并没有找到Bucket,因为Bucket还是一个空的筒子。
接着走到这里
if (isFirstAssociation)
object->setHasAssociatedObjects();
如果isFirstAssociation为true,所以会调有这个函数setHasAssociatedObjects。
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);
}
}
}
}
如果value不存在的话,会执行以上代码,清空这个数据。
关联对象会在objc_removeAssociatedObjects 这个函数进行移除,我们来分析下。
关联对象的生命周期跟着object一起。
objc_removeAssociatedObjects这个函数是在dealloc发起的,对象的生命周期释放的时候。
从以上几幅图中可能得到答案。
关联对象取值以及设值流程图:
23结语
这篇文章我们做了对类的加载的一些补充,对类的扩展做了介绍了,同时分析了关联对象的原理,如果有错误或者遗漏,请指正。
网友评论