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

it2024-08-14  39

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_imagesloadAllCategoriesload_categories_nolock 那么方法执行的顺序为:

load的几种情况

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

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

分类的加载顺序

在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方法,源码如下: 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", "v16@0:8", (void *)_I_LGTeacher_ext_instanceMethod}, {(struct objc_selector *)"ext_classMethod", "v16@0:8", (void *)_I_LGTeacher_ext_classMethod}, {(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_LGTeacher_instanceMethod}, {(struct objc_selector *)"classMethod", "v16@0:8", (void *)_I_LGTeacher_classMethod}, {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LGTeacher_ext_name}, {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LGTeacher_setExt_name_}, {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LGTeacher_ext_name}, {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LGTeacher_setExt_name_}} };

总结

类扩展中的属性、方法在编译期间就装载到类中,不需要动态添加方法。与分类不同。类扩展没有类的实现,类的实现依赖于当前的类,可以理解为类扩展只有.h文件,没有.m文件.

分类与类扩展的区别

category:分类、类别

专门用来给类添加新的方法不能给类添加成员变量,添加了成员变量也无法取到注意:其实可以通过runtime给分类添加属性分类中用@property定义变量,只会生成变量的setter、getter方法的声明,不能生成方法的实现和带下划线的成员变量

extension:类扩展

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

结尾

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

最新回复(0)