iOS底层探索(十三) 类的加载(下)
iOS底层探索(十三) 类的加载(下)
在上一篇文章iOS底层探索(十二) 类的加载(中)中了解到分类的加载,今天继续探索。
类的地址内容
给readClass
添加断点,并运行到断点。
打印cls
内容以及地址内容。
根据内存打印内容可知,根据类的结构,鼠标选中的位置应该是bits
的内容,但是为什么这个地方为0
呢,因为类中是有内容的。
bits的内容什么时候存在。为0时是否为空
我们继续断点进行查看。进入realizeClassWithoutSwift
方法,如下图:
从上图可以看出。我使用了cls
来获取data()
。那么这个时候的cls
是什么呢?我们执行打印。
从打印结果来看,这个时候的bits
仍然为0
,那么我获取的data
是什么呢?如果说是我自己写错了代码,那么在该函数的下部仍然有对data()
的获取位置。如下:
我们查看一下data()
源码,如下
class_rw_t *data() const {
return bits.data();
}
可知,data
是通过bits
获取的,可是bits
为0
啊
x/4gx的含义x/4gx cls
命令是读取cls
的内存情况。如果当前的data
没有进行初始化时,在内存中是没有值的,也就是说,为0
是正常的。
那么既然没有内存内容,我们该怎么探索呢?
在内存中没有值得时候,我们需要从另一个角度去探索内容–指针
,如果存在指针,则说明bits
并不是为nil
,而是没有内容而已。
从打印结果可知,当前的bits
并不为空,是有值的,只是因为当前的内存数据还没有完善,因此在内存中表现为0
。
bits内容什么时候完善的呢
当执行到realizeClassWithoutSwift
函数中的cls->setInstanceSize(ro->instanceSize);
代码时,内存出现了变化。bits
变为0x0000002000000000
了,查看setInstanceSize
方法源码。如下:
void setInstanceSize(uint32_t newSize) {
ASSERT(isRealized());
ASSERT(data()->flags & RW_REALIZING);
auto ro = data()->ro();
if (newSize != ro->instanceSize) {
ASSERT(data()->flags & RW_COPIED_RO);
*const_cast<uint32_t *>(&ro->instanceSize) = newSize;
}
cache.setFastInstanceSize(newSize);
}
为设置ro
的大小
继续断点。当执行cls->setHasCxxDtor();
方法时,内存变为0x0000002400000000
,查看setHasCxxDtor
函数,源码如下:
void setHasCxxDtor() {
cache.setBit(FAST_CACHE_HAS_CXX_DTOR);
}
该函数为设置bit
。
继续断点。当执行完dyldbootstrap::start
函数后,内存变为0x0000802400000000
,查看堆栈,执行过_dyld_start
方法。
在这里需要查看一下是否在dyldbootstrap
的start
函数中做了什么操作。因为在objc
源码中操作时跟到这里之前,内存都是0x0000002400000000
,再向下就跟不到了,而且,当LGPerson
实例创建完成时的内存为0x0000802400000000
,也就是说0x00008
的添加后,内存为最后结果了。
- 下载
dyld
源码,搜索_dyld_start
方法,发现是汇编语言。只看下面一小段就可以了,因为再向下就是执行dyldbootstrap
的start
函数了。
查看汇编常用指令理解汇编含义。汇编我不是特别懂,但是我看到有mov
、and
、sub
,即移动、与运算、减法,觉得赋值是在这里进行的。 - 搜索
dyldbootstrap
文件,并搜索到start
函数。源码如下:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
// Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
rebaseDyld(dyldsMachHeader);
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
该代码中并没有任何对内存的操作,那么我就有理由怀疑上方的汇编是造成内存的修改。
attachLists
在上一篇文章中,我们看到过attachCategories
这个函数,该函数中有个方法如下图所示:
在该方法中有mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
,首先mlist
是一个数组,而又将这个数组赋值给mlists
最后一个元素。那么mlists
相当于一个二维数组。
并且在if
语句内执行了rwe->methods.attachLists(mlists, mcount);
语句,那么现在就要重点查看attachLists
方法了,源码如下:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// 当前已经为多个数组,又增加了多个数组
// 获取原来数组的个数
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
// 开辟新的数组
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 内存平移到新数组的最后位置
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将要添加的数组添加到首位。
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 第一种情况。
// 直接赋值
list = addedLists[0];
}
else {
// 存在list时,向list中增加多个list
// 获取之前的list
List* oldList = list;
// 判断之前的list是否有值,如果有则为 1 否则为0
uint32_t oldCount = oldList ? 1 : 0;
// 重新计算容量,老的list的个数 + 要添加的个数。
uint32_t newCount = oldCount + addedCount;
// 根据容量开辟新的数组
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
// 如果存在oldList,则将oldList插入到lists的最后一个位置上。
if (oldList) array()->lists[addedCount] = oldList;
// 将addedLists 拷贝到lists上,(内存平移)
// memcpy参数解释:
// 1: copy的位置。即从什么位置开始。这个函数是从数组首位开始
// 2: copy的内容
// 3: copy的大小。即size
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
- 第一种情况,很简单,直接赋值给lists数组。
- 第二种情况,如下图。
- 第三种情况,当已经添加过一个或者多个数组后,再进行添加数组时,进行的操作,如下图:
进行断点调试
- 通过类名的判断,在该方法中执行断点。发现执行了
else if
代码块中,打印当前addedLists
内容。
那么由此也可知,list
为一维数组。 - 执行完该方法后继续运行,发现又回到了该方法,此时执行了
else
的代码块,打印当前的addedLists
内容。查看打印结果
发现addedLists
中存在1个内容,为LGPerson
的分类LGA
。打印一下array()
的内容。
那么array()
的结构如下图所示:
继续运行,再次进入这个函数时,发现addedLists
中存在一个内容,为LGPerson
的分类LGB
,打印当前的addedLists
,如下:
读取array()
的内容,结果如下:
从结果来看,LGPerson(LGB)
的方法确实插入到了所有方法的前面。
类加载的时机
即什么时候加载的类。
我们已知attachCategories
为加载分类的方法,即只要有分类,就会调用该方法,因此全局搜索attachCategories
方法,查看哪里进行了调用。
-
attachToClass
在这个方法中存在该方法,由上篇文章可知,该函数中的方法只有在类加载两遍的时候才会调用,其余时候不会调用。 -
load_categories_nolock
方法,这个方法之前没有接触过,因此给当前方法加个判断并且打个断点。
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
// 自己定义的。
Class cls = remapClass(cat->cls);
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
// bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
// end
}
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {...}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(_getObjc2CategoryList(hi, &count));
processCatlist(_getObjc2CategoryList2(hi, &count));
}
断点执行后发现,这个方法确实执行了,那么是谁调用了load_categories_nolock
方法呢,查看方法堆栈
发现方法执行顺序为:
load_images
loadAllCategories
-
load_categories_nolock
那么方法执行的顺序为:
load的几种情况
我们现在研究的是类与分类中都实现了+load
方法,那么这样就出现了一共4中情况,即类与分类中分别实现或不实现+load
方法。从readClass
方法中进行断点查看ro
。
- 非懒加载类 + 非懒加载分类
- 我们一直研究的就是这种情况,主类与分类都在运行时加载,即提前加载
- 懒加载类 + 非懒加载分类
- 当主类没有实现
load
而分类实现load
时,在readClass
中可以查看到只有主类的8
个方法。而没有分类的方法。 - 分类会迫使懒加载类变为
非懒加载类
的样式
来提前加载数据
- 当主类没有实现
- 懒加载类 + 懒加载分类
- 当主类与分类都没有实现
load
方法时,那么这个类都不会加载。在编译时期就完成data()
,在消息第一次调用时进行加载数据。
- 当主类与分类都没有实现
- 非懒加载类 + 懒加载分类
- 当主类实现了
load
方法而分类没有实现load
方法时,当前类会变为非懒加载类
的样式
来提前加载数据,并且当前类在编译时期就已经完成了data()
,在_read_images
时就加载数据。
- 当主类实现了
- 特殊情况,如果有两个分类,主类为
懒加载类
,其中一个是非懒加载分类
,另一个是懒加载分类
.- 其中一个分类实现
load
方法时,该分类仍然会迫使主类以及其他分类变为非懒加载
的样式
来提前加载数据。
- 其中一个分类实现
分类的加载顺序
在iOS系统中并没有对分类的加载做顺序加载,但是分类却是一个一个的加载到内存的,那么它必然就有一个顺序,不然当两个分类中有重名的方法时,就不能保证调用的方法是同一个方法,那么他的顺序是什么呢?
- 我们查看
Compile Sources
中的文件如下图:
运行他们两个的同名方法,结果如下:
- 将两个文件交换位置,如下图:
运行后的结果如下:
总结
从打印结果可以知道,当Complie Sources
中的文件顺序不同是,加载分类的顺序也不同,并且加载的顺序与Complie Sources
文件顺序倒叙,即最下面的文件,在后面加载,即它的方法会在内存的最前面。
关联对象
我们知道在分类中无法直接添加属性的,如果想要添加属性,就必须用到关联对象
进行处理。
在mian.m
文件中添加LGPerson(LG)
的分类,定义属性并且通过关联对象的方式进行属性的setter
和getter
方法的实现。源码如下:
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end
@implementation LGPerson (LG)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
NSLog(@"%s",__func__);
}
- (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");
}
@end
objc_setAssociatedObject
这段代码我相信只要了解过分类,并且给分类添加过属性的都会知道。运行程序,查看objc_setAssociatedObject
中的内容。源码如下:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
// 接口模式
SetAssocHook.get()(object, key, value, policy);
}
点击查看get()
方法,源码如下:
Fn get() {
return hook.load(std::memory_order_acquire);
}
发现该方法中并没有底层的实现,这是因为get()
方法使用的是函数指针调用。那么就需要查看SetAssocHook
函数。源码如下:
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
由代码可知SetAssocHook
的方法的底层实现为_base_objc_setAssociatedObject
方法。查看_base_objc_setAssociatedObject
方法的源码如下:
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
给该方法打个断点并运行,检测_object_set_associative_reference
方法是否能够调用。运行后发现确实执行到了该方法。查看_object_set_associative_reference
方法源码如下:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
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));
// 将对象(objc_object)封装成数据结构类型
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 对policy 存储策略以及value进行包装
ObjcAssociation association{policy, value};
// retain 新值
association.acquireValue();
{
// 初始化manager变量,
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 */
object->setHasAssociatedObjects();
}
/* 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 {
// 当没有值时调用。即当给属性的set方法传空值时,执行移除操作
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
refs.erase(it);
if (refs.size() == 0) {
// 消除associations
associations.erase(refs_it);
}
}
}
}
}
// release 旧值
association.releaseHeldValue();
}
-
value
存到了哪里?- 之前的文章有写过,value是存在指针地址中,即
*slot = value
.忘记的可自行搜索查看reallySetProperty
方法,源码如下:
- 之前的文章有写过,value是存在指针地址中,即
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
- 查看
DisguisedPtr
源码如下:
template <typename T>
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
public:
// 初始化方法
DisguisedPtr() { }
// 初始化方法
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }
DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}
operator T* () const {
return undisguise(value);
}
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[i];
}
// pointer arithmetic operators omitted
// because we don't currently use them anywhere
};
- 查看
association.acquireValue();
源码,如下:
inline void acquireValue() {
// 根据策略类型的不同进行不同的处理
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector( copy));
break;
}
}
}
- 查看
AssociationsManager
类的源码,如下:
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
// 构造函数,即在初始化时调用
// 加锁的目的是为了防止多线程访问。
AssociationsManager() { AssociationsManagerLock.lock(); }
// 析构函数,即当出了当前的作用域,即}后执行。这是c++的方法,如果我们使用需要文件的扩展名为 `.mm`
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
* `AssociationsManager`并不是全场唯一。
-
AssociationsHashMap
源码如下:
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
* 由源码可知,该方法也是使用函数指针调用,那么就需要查看的是`manager.get()`,即`AssociationsManager`的`get()`方法。由上方的`AssociationsManager`的源码可以查看,可知`AssociationsHashMap`是全场唯一的,原因是它所使用的`_mapStorage`变量是`static`修饰的。
- 打印查看该方法的数据结构
继续运行,继续打印。
继续运行associations
中内容为空的原因是还没有查找到相应的递归查找域。继续运行
研究小括号中的内容。复制内容,内容如下:
(std::pair<
objc::DenseMapIterator<DisguisedPtr<objc_object>,
objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,
objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
objc::DenseMapInfo<DisguisedPtr<objc_object> >,
objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,
false>,
bool>)
* 通过上面的结果划分可知,在最外层,即`std::pair`调用时需要两个参数,即`objc::DenseMapIterator`与`bool`
* `objc::DenseMapIterator`调用时需要的`5`个参数。
* 通过源码可分析,我们只需要知道`second`的值,即`std::pair`的第二个参数`bool`其余的不需要我们关心,因为只有知道`second`是否为`true`,才能决定`object->setHasAssociatedObjects();`方法是否会调用。
-
try_emplace
查看源码,如下:
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket; // 给一个空的桶子
if (LookupBucketFor(Key, TheBucket)) // 根据传进来的key值来找桶子,如果找到了桶子,说明不是第一次进来,则返回falss.
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);
}
* 读源码,可知需要查看`make_pair`的值,上面的为`false`,下面的是`true`。
-
LookupBucketFor
查看该方法源码,发现源码有两份相同的。读源码可知第二个为重载函数
,如下:
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
// 哈希函数获取下标
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
// 通过递归获取BucketT
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
// 赋值桶子
FoundBucket = ThisBucket;
// 返回跳出循环
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
// 赋值桶子
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
// 返回,跳出函数
return false;
}
// If this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
// 调整hash的下标
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
// 重载函数
template <typename LookupKeyT>
// 通过参数可以看到区别,即BucketT的修饰没有const
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
// 空的桶子
const BucketT *ConstFoundBucket;
// 是否查找到的结果
bool Result = const_cast<const DenseMapBase *>(this)
->LookupBucketFor(Val, ConstFoundBucket);
// 如果查到了,就把内容返回到传入的桶子里。
FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
return Result;
}
* 两个相同的函数肯定是有区别的,如果没有区别,写两个方法就没有任何意义了,细看之后就会发现,第二个函数中与第一个函数中的参数不同,即修饰`BucketT`时第一个参数使用了`const`而第二个参数没有。
* **第二个方法详解**
* 那么根据调用方法中传的参数,会发现,方法中调用的是第二个方法,但是在调用时最终仍然会走到第一个方法中。
* 如果查到桶子,将会返回桶子,好处是直接返回到`ObjectAssociationMap{}`中,即直接返回到表里。
* **第一个方法详解**
* 获取哈希函数的下标
* 根据下标获取`BucketT`
* 如果找到,返回`true`,并返回`BucketT`。
* 如果没有找到,调整`下标`
* 如果查找完成后仍然没有找到,返回`false`,并返回空`桶子`
- 打印
TheBucket
的值。- 查看可知,当前的桶子藏在
refs_result
中的objc::detail
中。
- 查看可知,当前的桶子藏在
-
setHasAssociatedObjects
查看源码:
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
* 该函数为设置`isa`中的`has_assoc`的值,即标记位。
关联对象:设值流程
- 创建一个
AssociationsManager
管理类 - 获取唯一的全局静态
HashMap
- 判断插入的关联值是否存在
- 存在走第4步
- 不存在走:关联对象插入空流程
- 创建一个空的
ObjectAssociationMap
去取查询的键值对。 - 如果发现没有这个key,就插入一个空的
BucketT
进去返回。 - 标记对象存在关联对象
- 用当前修饰策略
policy
和值value
组成一个ObjcAssociation
替换原来BucketT
中的空。 - 标记一下
ObjectAssociationMap
的第一次为false
。
关联对象插入空流程 - 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 清理迭代器
- 其实如果插入空值,相当于清除。
objc_getAssociatedObject
上面我们分析了设置的函数以及流程,接下来查看取值的流程,首先查看objc_getAssociatedObject
的源码,如下:
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
当前函数只接收两个参数,关联的对象,设值时所使用的key
,在函数内部仅有_object_get_associative_reference
函数的调用,查看_object_get_associative_reference
函数的源码。如下:
id
_object_get_associative_reference(id object, const void *key)
{
// 初始化关联策略与值。
ObjcAssociation association{};
{
// 初始化管理变量
AssociationsManager manager;
// 初始化哈希表
AssociationsHashMap &associations(manager.get());
// 获取iterator 迭代查询器
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
// 如果不是最后一个,获取存于map中的策略和值
ObjectAssociationMap &refs = i->second;
// 找到ObjectAssociationMap的iterator 迭代查询器,
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
// 如果不是最后一个,返回一个经过属性修饰符修饰的值。
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
在上方的代码中只有retainReturnedValue()
与autoreleaseReturnedValue()
两个方法在设值中没有见到过,我们分别查看源码,
-
retainReturnedValue()
源码如下:
inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
}
这个函数很简单,就是根据当前的value
以及关联策略policy
进行判断是否需要对当前的值进行retain
。
-
autoreleaseReturnedValue()
源码如下:
inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}
这个函数同样简单,也是根据当前的value
以及关联策略policy
进行判断是否需要对当前的值进行autorelease
。
关联对象:取值流程
- 创建一个
AssociationsManager
管理类 - 获取唯一的全局静态
HashMap
- 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 如果这个迭代查询去不是最后一个 获取
ObjectAssociationMap
中的iterator
迭代查询器。这里有策略policy
和值value
- 如果第四步中的迭代查询器不是最后一个,判断是否需要对值进行
retain
处理。 - 找到
ObjectAssociationMap
的迭代器获取一个经过属性修饰符修饰的value
- 返回
value
关联对象总结
关联对象的结构:
类扩展 extension
想要知道什么是extension
,那么就需要从.cpp
文件中去查看类扩展的源码。
在main.m
文件中定义LGTeacher
类以及类扩展
// 类的声明,定义
@interface LGTeacher : NSObject
- (void)instanceMethod;
- (void)classMethod;
@end
// 类的扩展,类的扩展只能写在 声明之后,实现之前
@interface LGTeacher ()
@property (nonatomic, copy) NSString *ext_name;
- (void)ext_instanceMethod;
- (void)ext_classMethod;
@end
// 类的实现。
@implementation LGTeacher
- (void)ext_instanceMethod{
}
- (void)ext_classMethod{
}
- (void)instanceMethod{
}
- (void)classMethod{
}
@end
使用clang
生成.cpp
文件,并查看该文件。搜索LGTearch
查看。
- 首先查看属性存在。并且存在于
ivar
中,代码如下:
extern "C" unsigned long OBJC_IVAR_$_LGTeacher$_ext_name;
struct LGTeacher_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_ext_name;
};
- 查看是否存在
setter
和setter
方法。并且在方法列表中也能找到。代码如下:
static NSString * _I_LGTeacher_ext_name(LGTeacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGTeacher$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGTeacher_setExt_name_(LGTeacher * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGTeacher, _ext_name), (id)ext_name, 0, 1); }
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[8];
} _OBJC_$_INSTANCE_METHODS_LGTeacher __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
8,
{{(struct objc_selector *)"ext_instanceMethod", "aaa@qq.com:8", (void *)_I_LGTeacher_ext_instanceMethod},
{(struct objc_selector *)"ext_classMethod", "aaa@qq.com:8", (void *)_I_LGTeacher_ext_classMethod},
{(struct objc_selector *)"instanceMethod", "aaa@qq.com:8", (void *)_I_LGTeacher_instanceMethod},
{(struct objc_selector *)"classMethod", "aaa@qq.com:8", (void *)_I_LGTeacher_classMethod},
{(struct objc_selector *)"ext_name", "@aaa@qq.com:8", (void *)_I_LGTeacher_ext_name},
{(struct objc_selector *)"setExt_name:", "aaa@qq.com:aaa@qq.com", (void *)_I_LGTeacher_setExt_name_},
{(struct objc_selector *)"ext_name", "@aaa@qq.com:8", (void *)_I_LGTeacher_ext_name},
{(struct objc_selector *)"setExt_name:", "aaa@qq.com:aaa@qq.com", (void *)_I_LGTeacher_setExt_name_}}
};
总结
-
类扩展
中的属性
、方法
在编译
期间就装载到类中,不需要
动态添加方法。与分类
不同。 -
类扩展
没有类的实现,类的实现依赖于当前的类,可以理解为类扩展只有.h
文件,没有.m
文件.
分类与类扩展的区别
category:分类、类别
- 专门用来给类添加新的方法
- 不能给类添加成员变量,添加了成员变量也无法取到
- 注意:其实可以通过runtime给分类添加属性
- 分类中用
@property
定义变量,只会生成变量的setter
、getter
方法的声明,不能生成方法的实现和带下划线的成员变量
extension:类扩展
- 必须写在声明之后,实现之前。
- 可以说成是特殊的分类,也称作匿名分类
- 可以给类添加成员属性,如果是在
.m
文件中的extension
内定义成员属性,则该成员属性为私有属性
- 可以给类添加方法,如果是在
.m
文件中的extension
内定义方法,则该方法为私有方法
结尾
至此,类的加载已经讲完了,你学废了吗????
上一篇: 阿里巴巴Java开发手册一周年最终版
下一篇: OC底层探索(十五)KVC底层原理