欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS底层探索(十三) 类的加载(下)

程序员文章站 2024-03-24 14:08:46
...

iOS底层探索(十三) 类的加载(下)

在上一篇文章iOS底层探索(十二) 类的加载(中)中了解到分类的加载,今天继续探索。

类的地址内容

readClass添加断点,并运行到断点。
iOS底层探索(十三) 类的加载(下)
打印cls内容以及地址内容。
iOS底层探索(十三) 类的加载(下)
根据内存打印内容可知,根据类的结构,鼠标选中的位置应该是bits的内容,但是为什么这个地方为0呢,因为类中是有内容的。

bits的内容什么时候存在。为0时是否为空

我们继续断点进行查看。进入realizeClassWithoutSwift方法,如下图:
iOS底层探索(十三) 类的加载(下)
从上图可以看出。我使用了cls来获取data()。那么这个时候的cls是什么呢?我们执行打印。
iOS底层探索(十三) 类的加载(下)
从打印结果来看,这个时候的bits仍然为0,那么我获取的data是什么呢?如果说是我自己写错了代码,那么在该函数的下部仍然有对data()的获取位置。如下:
iOS底层探索(十三) 类的加载(下)
我们查看一下data()源码,如下

    class_rw_t *data() const {
        return bits.data();
    }

可知,data是通过bits获取的,可是bits0
x/4gx的含义
x/4gx cls命令是读取cls的内存情况。如果当前的data没有进行初始化时,在内存中是没有值的,也就是说,为0是正常的。
那么既然没有内存内容,我们该怎么探索呢?
在内存中没有值得时候,我们需要从另一个角度去探索内容–指针,如果存在指针,则说明bits并不是为nil,而是没有内容而已。
iOS底层探索(十三) 类的加载(下)
从打印结果可知,当前的bits并不为空,是有值的,只是因为当前的内存数据还没有完善,因此在内存中表现为0

bits内容什么时候完善的呢

当执行到realizeClassWithoutSwift函数中的cls->setInstanceSize(ro->instanceSize);代码时,内存出现了变化。
iOS底层探索(十三) 类的加载(下)
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方法。
iOS底层探索(十三) 类的加载(下)
在这里需要查看一下是否在dyldbootstrapstart函数中做了什么操作。因为在objc源码中操作时跟到这里之前,内存都是0x0000002400000000,再向下就跟不到了,而且,当LGPerson实例创建完成时的内存为0x0000802400000000,也就是说0x00008的添加后,内存为最后结果了。
iOS底层探索(十三) 类的加载(下)
iOS底层探索(十三) 类的加载(下)

  • 下载dyld源码,搜索_dyld_start方法,发现是汇编语言。只看下面一小段就可以了,因为再向下就是执行dyldbootstrapstart函数了。
    iOS底层探索(十三) 类的加载(下)
    查看汇编常用指令理解汇编含义。汇编我不是特别懂,但是我看到有movandsub,即移动、与运算、减法,觉得赋值是在这里进行的。
  • 搜索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这个函数,该函数中有个方法如下图所示:
iOS底层探索(十三) 类的加载(下)
在该方法中有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数组。
  • 第二种情况,如下图。
    iOS底层探索(十三) 类的加载(下)
  • 第三种情况,当已经添加过一个或者多个数组后,再进行添加数组时,进行的操作,如下图:
    iOS底层探索(十三) 类的加载(下)

进行断点调试

  • 通过类名的判断,在该方法中执行断点。发现执行了else if代码块中,打印当前addedLists内容。
    iOS底层探索(十三) 类的加载(下)
    那么由此也可知,list为一维数组。
  • 执行完该方法后继续运行,发现又回到了该方法,此时执行了else的代码块,打印当前的addedLists内容。查看打印结果
    iOS底层探索(十三) 类的加载(下)
    发现addedLists中存在1个内容,为LGPerson的分类LGA。打印一下array()的内容。
    iOS底层探索(十三) 类的加载(下)
    那么array()的结构如下图所示:
    iOS底层探索(十三) 类的加载(下)
    继续运行,再次进入这个函数时,发现addedLists中存在一个内容,为LGPerson的分类LGB,打印当前的addedLists,如下:
    iOS底层探索(十三) 类的加载(下)
    读取array()的内容,结果如下:
    iOS底层探索(十三) 类的加载(下)
    iOS底层探索(十三) 类的加载(下)
    从结果来看,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方法呢,查看方法堆栈
iOS底层探索(十三) 类的加载(下)
发现方法执行顺序为:

  • load_images
  • loadAllCategories
  • load_categories_nolock
    那么方法执行的顺序为:
    iOS底层探索(十三) 类的加载(下)

load的几种情况

我们现在研究的是类与分类中都实现了+load方法,那么这样就出现了一共4中情况,即类与分类中分别实现或不实现+load方法。从readClass方法中进行断点查看ro

  • 非懒加载类 + 非懒加载分类
    • 我们一直研究的就是这种情况,主类与分类都在运行时加载,即提前加载
  • 懒加载类 + 非懒加载分类
    iOS底层探索(十三) 类的加载(下)
    • 当主类没有实现load而分类实现load时,在readClass中可以查看到只有主类的8个方法。而没有分类的方法。
    • 分类会迫使懒加载类变为非懒加载类样式来提前加载数据
  • 懒加载类 + 懒加载分类
    iOS底层探索(十三) 类的加载(下)
    • 当主类与分类都没有实现load方法时,那么这个类都不会加载。在编译时期就完成data(),在消息第一次调用时进行加载数据。
  • 非懒加载类 + 懒加载分类
    iOS底层探索(十三) 类的加载(下)
    • 当主类实现了load方法而分类没有实现load方法时,当前类会变为非懒加载类样式来提前加载数据,并且当前类在编译时期就已经完成了data(),在_read_images时就加载数据。
  • 特殊情况,如果有两个分类,主类为懒加载类,其中一个是非懒加载分类,另一个是懒加载分类.
    iOS底层探索(十三) 类的加载(下)
    • 其中一个分类实现load方法时,该分类仍然会迫使主类以及其他分类变为非懒加载样式来提前加载数据。

分类的加载顺序

在iOS系统中并没有对分类的加载做顺序加载,但是分类却是一个一个的加载到内存的,那么它必然就有一个顺序,不然当两个分类中有重名的方法时,就不能保证调用的方法是同一个方法,那么他的顺序是什么呢?

  • 我们查看Compile Sources中的文件如下图:
    iOS底层探索(十三) 类的加载(下)
    运行他们两个的同名方法,结果如下:
    iOS底层探索(十三) 类的加载(下)
  • 将两个文件交换位置,如下图:
    iOS底层探索(十三) 类的加载(下)
    运行后的结果如下:
    iOS底层探索(十三) 类的加载(下)
    总结
    从打印结果可以知道,当Complie Sources中的文件顺序不同是,加载分类的顺序也不同,并且加载的顺序与Complie Sources文件顺序倒叙,即最下面的文件,在后面加载,即它的方法会在内存的最前面。

关联对象

我们知道在分类中无法直接添加属性的,如果想要添加属性,就必须用到关联对象进行处理。
mian.m文件中添加LGPerson(LG)的分类,定义属性并且通过关联对象的方式进行属性的settergetter方法的实现。源码如下:

@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方法,源码如下:
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`修饰的。
  • 打印查看该方法的数据结构
    iOS底层探索(十三) 类的加载(下)
    继续运行,继续打印。
    iOS底层探索(十三) 类的加载(下)
    继续运行
    iOS底层探索(十三) 类的加载(下)
    associations中内容为空的原因是还没有查找到相应的递归查找域。继续运行
    iOS底层探索(十三) 类的加载(下)
    研究小括号中的内容。复制内容,内容如下:
(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的值。
    iOS底层探索(十三) 类的加载(下)
    • 查看可知,当前的桶子藏在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`的值,即标记位。

关联对象:设值流程

  1. 创建一个AssociationsManager管理类
  2. 获取唯一的全局静态HashMap
  3. 判断插入的关联值是否存在
    1. 存在走第4步
    2. 不存在走:关联对象插入空流程
  4. 创建一个空的ObjectAssociationMap去取查询的键值对。
  5. 如果发现没有这个key,就插入一个空的BucketT进去返回。
  6. 标记对象存在关联对象
  7. 用当前修饰策略policy和值value组成一个ObjcAssociation替换原来BucketT中的空。
  8. 标记一下ObjectAssociationMap的第一次为false
    关联对象插入空流程
  9. 根据DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
  10. 清理迭代器
  11. 其实如果插入空值,相当于清除。

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

关联对象:取值流程

  1. 创建一个AssociationsManager管理类
  2. 获取唯一的全局静态HashMap
  3. 根据DisguisedPtr找到AssociationsHashMap中的iterator迭代查询器
  4. 如果这个迭代查询去不是最后一个 获取ObjectAssociationMap中的iterator迭代查询器。这里有策略policy和值value
  5. 如果第四步中的迭代查询器不是最后一个,判断是否需要对值进行retain处理。
  6. 找到ObjectAssociationMap的迭代器获取一个经过属性修饰符修饰的value
  7. 返回value

关联对象总结

关联对象的结构:
iOS底层探索(十三) 类的加载(下)

类扩展 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;
};
  • 查看是否存在settersetter方法。并且在方法列表中也能找到。代码如下:
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定义变量,只会生成变量的settergetter方法的声明,不能生成方法的实现和带下划线的成员变量

extension:类扩展

  • 必须写在声明之后,实现之前。
  • 可以说成是特殊的分类,也称作匿名分类
  • 可以给类添加成员属性,如果是在.m文件中的extension内定义成员属性,则该成员属性为私有属性
  • 可以给类添加方法,如果是在.m文件中的extension内定义方法,则该方法为私有方法

结尾

至此,类的加载已经讲完了,你学废了吗????