广州企业建设网站设计网站做的工作步骤是

news/2025/9/26 10:44:33/文章来源:
广州企业建设网站,设计网站做的工作步骤是,一级a做爰片免费网站中文,吉林天宇建设集团网站前言 关于什么是weak关键字可以去看看我以前的一篇博客#xff1a;【OC】 属性关键字 weak原理 1. SideTable SideTable 这个结构体#xff0c;前辈给它总结了一个很形象的名字叫引用计数和弱引用依赖表#xff0c;因为它主要用于管理对象的引用计数和 weak 表。在 NSOb…前言 关于什么是weak关键字可以去看看我以前的一篇博客【OC】 属性关键字 weak原理 1. SideTable SideTable 这个结构体前辈给它总结了一个很形象的名字叫引用计数和弱引用依赖表因为它主要用于管理对象的引用计数和 weak 表。在 NSObject.mm 中声明其数据结构 struct SideTable { // 保证原子操作的自旋锁spinlock_t slock;// 引用计数的 hash 表RefcountMap refcnts;// weak 引用全局 hash 表weak_table_t weak_table;SideTable() {memset(weak_table, 0, sizeof(weak_table));}~SideTable() {_objc_fatal(Do not delete SideTable.);}void lock() { slock.lock(); }void unlock() { slock.unlock(); }void reset() { slock.reset(); }// Address-ordered lock discipline for a pair of side tables.templateHaveOld, HaveNewstatic void lockTwo(SideTable *lock1, SideTable *lock2);templateHaveOld, HaveNewstatic void unlockTwo(SideTable *lock1, SideTable *lock2); }slock是为了防止竞争选择的自旋锁 refcnts 是协助对象的 isa 指针的 extra_rc 共同引用计数的变量对于对象结果在后文提到 接着我们来看一下SideTable中的这三个成员变量 1.1 spinlock_t slock 自旋锁 自旋锁的效率高于互斥锁。但是我们要注意由于自旋时不释放CPU因而持有自旋锁的线程应该尽快释放自旋锁否则等待该自旋锁的线程会一直在哪里自旋这就会浪费CPU时间。 在操作引用计数的时候对SideTable加锁避免数据错误。 1.2 RefcountMap typedef objc::DenseMapDisguisedPtrobjc_object,size_t,true RefcountMap;其中DenseMap 又是一个模板类: templatetypename KeyT, typename ValueT,bool ZeroValuesArePurgeable false, typename KeyInfoT DenseMapInfoKeyT class DenseMap : public DenseMapBaseDenseMapKeyT, ValueT, ZeroValuesArePurgeable, KeyInfoT, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable {...BucketT *Buckets;unsigned NumEntries;unsigned NumTombstones;unsigned NumBuckets;... }比较重要的成员有这几个 1.ZeroValuesArePurgeable 默认值是 false, 但 RefcountMap 指定其初始化为 true。 这个成员标记是否可以使用值为 0 (引用计数为 1) 的桶. 因为空桶存的初始值就是 0, 所以值为 0 的桶和空桶没什么区别.。如果允许使用值为 0 的桶, 查找桶时如果没有找到对象对应的桶, 也没有找到墓碑桶, 就会优先使用值为 0 的桶。 2.Buckets 指针管理一段连续内存空间, 也就是数组, 数组成员是 BucketT 类型的对象, 我们这里将 BucketT 对象称为桶(实际上这个数组才应该叫桶, 苹果把数组中的元素称为桶应该是为了形象一些, 而不是哈希桶中的桶的意思)。桶数组在申请空间后, 会进行初始化, 在所有位置上都放上空桶(桶的 key 为 EmptyKey 时是空桶), 之后对引用计数的操作, 都要依赖于桶。 桶的数据类型实际上是 std::pair, 类似于 swift 中的元祖类型, 就是将对象地址和对象的引用计数(这里的引用计数类似于isa, 也是使用其中的几个 bit 来保存引用计数, 留出几个 bit 来做其它标记位)组合成一个数据类型。 BucketT 的定义如下 typedef std::pairKeyT, ValueT BucketT;3.NumEntries 记录数组中已使用的非空的桶的个数. 4.NumTombstones, Tombstone 直译为墓碑, 当一个对象的引用计数为0, 要从桶中取出时, 其所处的位置会被标记为 Tombstone. NumTombstones 就是数组中的墓碑的个数. 后面会介绍到墓碑的作用. 5.NumBuckets 桶的数量, 因为数组中始终都充满桶, 所以可以理解为数组大小. inline uint64_t NextPowerOf2(uint64_t A) {A | (A 1);A | (A 2);A | (A 4);A | (A 8);A | (A 16);A | (A 32);return A 1; }这是对应 64 位的提供数组大小的方法, 需要为桶数组开辟空间时, 会由这个方法来决定数组大小. 这个算法可以做到把最高位的 1 覆盖到所有低位. 例如 A 0b10000, (A 1) 0b01000, 按位与就会得到 A 0b11000, 这个时候 (A 2) 0b00110, 按位与就会得到 A 0b11110. 以此类推 A 的最高位的 1, 会一直覆盖到高 2 位、高 4 位、高 8 位, 直到最低位. 最后这个充满 1 的二进制数会再加 1, 得到一个 0b1000…(N 个 0). 也就是说, 桶数组的大小会是 2^n. RefcountMap 的工作逻辑 1.通过计算对象地址的哈希值, 来从 SideTables 中获取对应的 SideTable. 哈希值重复的对象的引用计数存储在同一个 SideTable 里. 2.SideTable 使用 find() 方法和重载 [] 运算符的方式, 通过对象地址来确定对象对应的桶. 最终执行到的查找算法是 LookupBucketFor(). 3.查找算法会先对桶的个数进行判断, 如果桶数为 0 则 return false 回上一级调用插入方法. 如果查找算法找到空桶或者墓碑桶, 同样 return false 回上一级调用插入算法, 不过会先记录下找到的桶. 如果找到了对象对应的桶, 只需要对其引用计数 1 或者 - 1. 如果引用计数为 0 需要销毁对象, 就将这个桶中的 key 设置为 TombstoneKey value_type FindAndConstruct(const KeyT Key) {BucketT *TheBucket;if (LookupBucketFor(Key, TheBucket))return *TheBucket;return *InsertIntoBucket(Key, ValueT(), TheBucket);}4.插入算法会先查看可用量, 如果哈希表的可用量(墓碑桶空桶的数量)小于 1/4, 则需要为表重新开辟更大的空间, 如果表中的空桶位置少于 1/8 (说明墓碑桶过多), 则需要清理表中的墓碑. 以上两种情况下哈希查找算法会很难查找正确位置, 甚至可能会产生死循环, 所以要先处理表, 处理表之后还会重新分配所有桶的位置, 之后重新查找当前对象的可用位置并插入. 如果没有发生以上两种情况, 就直接把新的对象的引用计数放入调用者提供的桶里. 墓碑的作用 如果 c 对象销毁后将下标 2 的桶设置为空桶而不置为墓碑桶的话, 此时为 e 对象增加引用计数, 根据哈希算法查找到下标为 2 的桶时, 就会直接插入, 无法为已经在下标为 4 的桶中的 e 增加引用计数但是我们正常的流程中c 对象销毁后下标 2的桶将会被置为墓碑桶这样的话在对e对象增加引用计数的时候根据哈希算法找到下标为2的桶时就会将2跳过往后继续查找直至找到e对象所对应的桶为止或者直至找到空桶新建一个存e对象的桶如果此时初始化了一个新的对象 f, 根据哈希算法查找到下标为 2 的桶时发现桶中放置了墓碑, 此时会记录下来下标 2. 接下来继续哈希算法查找位置, 查找到空桶时, 就证明表中没有对象 f, 此时 f 使用记录好的下标 2 的墓碑桶而不是查找到的空桶, 就可以利用到已经释放的位置保证哈希表中前面部分都是被利用或者待利用的状态。 查找某对象对应桶的源码如下 bool LookupBucketFor(const LookupKeyT Val,const BucketT *FoundBucket) const {...if (NumBuckets 0) { //桶数是0FoundBucket 0;return false; //返回 false 回上层调用添加函数}...unsigned BucketNo getHashValue(Val) (NumBuckets-1); //将哈希值与数组最大下标按位与unsigned ProbeAmt 1; //哈希值重复的对象需要靠它来重新寻找位置while (1) {const BucketT *ThisBucket BucketsPtr BucketNo; //头指针 下标, 类似于数组取值//找到的桶中的 key 和对象地址相等, 则是找到if (KeyInfoT::isEqual(Val, ThisBucket-first)) {FoundBucket ThisBucket;return true;}//找到的桶中的 key 是空桶占位符, 则表示可插入if (KeyInfoT::isEqual(ThisBucket-first, EmptyKey)) { if (FoundTombstone) ThisBucket FoundTombstone; //如果曾遇到墓碑, 则使用墓碑的位置FoundBucket FoundTombstone ? FoundTombstone : ThisBucket;return false; //找到空占位符, 则表明表中没有已经插入了该对象的桶}//如果找到了墓碑if (KeyInfoT::isEqual(ThisBucket-first, TombstoneKey) !FoundTombstone)FoundTombstone ThisBucket; // 记录下墓碑//这里涉及到最初定义 typedef objc::DenseMapDisguisedPtrobjc_object,size_t,true RefcountMap, 传入的第三个参数 true//这个参数代表是否可以清除 0 值, 也就是说这个参数为 true 并且没有墓碑的时候, 会记录下找到的 value 为 0 的桶if (ZeroValuesArePurgeable ThisBucket-second 0 !FoundTombstone) FoundTombstone ThisBucket;//用于计数的 ProbeAmt 如果大于了数组容量, 就会抛出异常if (ProbeAmt NumBuckets) {_objc_fatal(...);}BucketNo ProbeAmt; //本次哈希计算得出的下表不符合, 则利用 ProbeAmt 寻找下一个下标BucketNo (NumBuckets-1); //得到新的数字和数组下标最大值按位与}}向某对象的引用计数桶插入代码如下 BucketT *InsertIntoBucketImpl(const KeyT Key, BucketT *TheBucket) {unsigned NewNumEntries getNumEntries() 1; //桶的使用量 1unsigned NumBuckets getNumBuckets(); //桶的总数if (NewNumEntries*4 NumBuckets*3) { //使用量超过 3/4this-grow(NumBuckets * 2); //数组大小 * 2做参数, grow 中会决定具体数值//grow 中会重新布置所有桶的位置, 所以将要插入的对象也要重新确定位置LookupBucketFor(Key, TheBucket);NumBuckets getNumBuckets(); //获取最新的数组大小}//如果空桶数量少于 1/8, 哈希查找会很难定位到空桶的位置if (NumBuckets-(NewNumEntriesgetNumTombstones()) NumBuckets/8) {//grow 以原大小重新开辟空间, 重新安排桶的位置并能清除墓碑this-grow(NumBuckets);LookupBucketFor(Key, TheBucket); //重新布局后将要插入的对象也要重新确定位置}assert(TheBucket);//找到的 BucketT 标记了 EmptyKey, 可以直接使用if (KeyInfoT::isEqual(TheBucket-first, getEmptyKey())) {incrementNumEntries(); //桶使用量 1}else if (KeyInfoT::isEqual(TheBucket-first, getTombstoneKey())) { //如果找到的是墓碑incrementNumEntries(); //桶使用量 1decrementNumTombstones(); //墓碑数量 -1}else if (ZeroValuesArePurgeable TheBucket-second 0) { //找到的位置是 value 为 0 的位置TheBucket-second.~ValueT(); //测试中这句代码被直接跳过并没有执行, value 还是 0} else {// 其它情况, 并没有成员数量的变化(官方注释是 Updating an existing entry.)}return TheBucket;}2. weak部分——weak_table_t weak_table_t在SideTable结构体中储存对象弱引用指针的Hash表weak功能实现的核心数据结构 首先我们来看一下weak_table_t结构体的源码 struct weak_table_t {weak_entry_t *weak_entries;//连续地址空间的头指针数组size_t num_entries;//数组中已占用位置的个数uintptr_t mask;//数组下标最大值即数组大小 -1uintptr_t max_hash_displacement;//最大哈希偏移值 };weak_table 是一个哈希表的结构, 根据 weak 指针指向的对象的地址计算哈希值, 哈希值相同的对象按照下标 1 的形式向后查找可用位置, 是典型的闭散列算法. 最大哈希偏移值即是所有对象中计算出的哈希值和实际插入位置的最大偏移量, 在查找时可以作为循环的上限。 weak_table结构图 2.1 weak_entry_t 的成员 struct weak_entry_t {DisguisedPtrobjc_object referent; //对象地址union { //这里又是一个联合体, 苹果设计的数据结构的确很棒struct {// 因为这里要存储的又是一个 weak 指针数组, 所以苹果继续选择采用哈希算法weak_referrer_t *referrers; //指向 referent 对象的 weak 指针数组uintptr_t out_of_line_ness : 2; //这里标记是否超过内联边界, 下面会提到uintptr_t num_refs : PTR_MINUS_2; //数组中已占用的大小uintptr_t mask; //数组下标最大值(数组大小 - 1)uintptr_t max_hash_displacement; //最大哈希偏移值};struct {//这是一个取名叫内联引用的数组weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; //宏定义的值是 4};};// weak_entry_t 的赋值操作直接使用 memcpy 函数拷贝 other 内存里面的内容到 this 中// 而不是用复制构造函数什么的形式实现应该也是为了提高效率考虑的...weak_entry_t operator(const weak_entry_t other) {memcpy(this, other, sizeof(other));return *this;}// 返回 true 表示使用 referrers 哈希数组 false 表示使用 inline_referrers 数组保存 weak_referrer_tbool out_of_line() {return (out_of_line_ness REFERRERS_OUT_OF_LINE);}// weak_entry_t 的构造函数// newReferent 是原始对象的指针// newReferrer 则是指向 newReferent 的弱引用变量的指针。// 初始化列表 referent(newReferent) 会调用: DisguisedPtr(T* ptr) : value(disguise(ptr)) { } 构造函数// 调用 disguise 函数把 newReferent 转化为一个整数赋值给 value。weak_entry_t(objc_object *newReferent, objc_object **newReferrer): referent(newReferent){// 把 newReferrer 放在数组 0 位也会调用 DisguisedPtr 构造函数把 newReferrer 转化为整数保存inline_referrers[0] newReferrer;// 循环把 inline_referrers 数组的剩余 3 位都置为 nilfor (int i 1; i WEAK_INLINE_COUNT; i) {inline_referrers[i] nil;}} }我们通过对象的地址, 可以在 weak_table_t 中找到对应的 weak_entry_t, weak_entry_t 中保存了所有指向这个对象的 weak 指针 苹果在 weak_entry_t 中又使用了一个共用体, 第一个结构体中 out_of_line_ness 占用 2bit, num_refs 在 64 位环境下占用了 62bit, 所以实际上两个结构体都是 32 字节, 共用一段地址. 当指向这个对象的 weak 指针不超过 4 个, 则直接使用数组 inline_referrers, 省去了哈希操作的步骤, 如果 weak 指针个数超过了 4 个, 就要使用第一个结构体中的哈希表. 2.2 weak_table的大概逻辑 在 ARC 下, 编译器会自动添加管理引用计数的代码, weak 指针赋值的时候, 编译器会调用 storeWeak 来赋值, 若 weak 指针有指向的对象, 那么会先调用 weak_unregister_no_lock() 方法来从原有的表中先删除这个 weak 指针, 然后再调用weak_register_no_lock() 来向对应的表中插入这个 weak 指针查找时先用被指向对象的地址来计算哈希值, 从 SideTables() 中找到对应的 SideTable, 再进一步使用这个对象地址来SideTable 的 weak_table 中找到对应的 weak_entry_t. 最终要进行操作的就是这个 weak_entry_t. 如果这个对象的 weak 指针不超过 4 个, 则直接操作 inline_referrers 数组, 否则会为 referrers 数组申请内存, 采用哈希算法来管理表.删除旧的 weak 指针时, 会使用原本指向的对象的地址来查找对应的 weak_entry_t, 从中删除这个 weak 指针. 如果删除之后 weak 指针数组为空, 则销毁这个 weak_entry_t, 原有位置置空, 原本被指向对象的 isa 指针的 weak 引用标记位 0.添加新的 weak 指针时, 如果查找到对应的 weak_entry_t, 则将 weak 指针插入到 referrers 数组中. 如果没找到则创建一个 weak_entry_t 配置好后插入 weak_table_t 的数组中. 3. weak的重要实现方法 3.1 objc_initWeak 函数 objc_initWeak 函数的主要作用是根据传入的 newObj 对象初始化一个__weak修饰的对象指针同时处理无效对象的情况以及进行一些性能优化操作。 id objc_initWeak(id *location, id newObj) { // 查看对象实例是否有效,无效对象直接导致指针释放if (!newObj) {*location nil;return nil;}// 这里传递了三个 Bool 数值// 使用 template 进行常量参数传递是为了优化性能return storeWeakfalse/*old*/, true/*new*/, true/*crash*/(location, (objc_object*)newObj); }然后我们看一下objc_initWeak()传入的两个参数代表什么 location:__weak指针的地址存储指针的地址这样便可以在最后将其指向的对象置为nil。newObj:所引用的对象。即例子中的p。 这个函数的功能如下 首先它会检查传入的 newObj 对象是否有效如果 newObj 是一个无效对象即nil那么它会将 location 指向的__weak指针设置为nil并直接返回nil。如果 newObj 是一个有效对象它会调用 storeWeak 函数进行实际的弱引用初始化操作。storeWeak 函数是底层的内部函数它会将 newObj 对象存储到 location 指向的内存地址中并设置标志位以及进行一些性能优化操作。 objc_initWeak函数有一个前提条件就是object必须是一个没有被注册为__weak对象的有效指针。而value则可以是nil或者指向一个有效的对象。 3.2 objc_storeWeak() 下面这段代码是Objective-C运行时中用于实现弱引用的 storeWeak 函数的模板实现。这个函数主要用于更新弱引用指针的指向并处理多线程情况下的竞争冲突。 // HaveOld: true - 变量有值 // false - 需要被及时清理当前值可能为 nil // HaveNew: true - 需要被分配的新值当前值可能为 nil // false - 不需要分配新值 // CrashIfDeallocating: true - 说明 newObj 已经释放或者 newObj 不支持弱引用该过程需要暂停 // false - 用 nil 替代存储 template bool HaveOld, bool HaveNew, bool CrashIfDeallocating static id storeWeak(id *location, objc_object *newObj) {// 该过程用来更新弱引用指针的指向// 初始化 previouslyInitializedClass 指针Class previouslyInitializedClass nil;id oldObj;// 声明两个 SideTable// ① 新旧散列创建SideTable *oldTable;SideTable *newTable;// 获得新值和旧值的锁存位置用地址作为唯一标示// 通过地址来建立索引标志防止桶重复// 下面指向的操作会改变旧值retry:if (HaveOld) {// 更改指针获得以 oldObj 为索引所存储的值地址oldObj *location;oldTable SideTables()[oldObj];} else {oldTable nil;}if (HaveNew) {// 更改新值指针获得以 newObj 为索引所存储的值地址newTable SideTables()[newObj];} else {newTable nil;}// 加锁操作防止多线程中竞争冲突SideTable::lockTwoHaveOld, HaveNew(oldTable, newTable);// 避免线程冲突重处理// location 应该与 oldObj 保持一致如果不同说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改if (HaveOld *location ! oldObj) {SideTable::unlockTwoHaveOld, HaveNew(oldTable, newTable);goto retry;}// 防止弱引用间死锁// 并且通过 initialize 初始化构造器保证所有弱引用的 isa 非空指向if (HaveNew newObj) {// 获得新对象的 isa 指针Class cls newObj-getIsa();// 判断 isa 非空且已经初始化if (cls ! previouslyInitializedClass !((objc_class *)cls)-isInitialized()) {// 解锁SideTable::unlockTwoHaveOld, HaveNew(oldTable, newTable);// 对其 isa 指针进行初始化_class_initialize(_class_getNonMetaClass(cls, (id)newObj));// 如果该类已经完成执行 initialize 方法是最理想情况// 如果该类 initialize 在线程中 // 例如 initialize 正在调用 storeWeak 方法// 需要手动对其增加保护策略并设置 previouslyInitializedClass 指针进行标记previouslyInitializedClass cls;// 重新尝试goto retry;}}// ② 清除旧值if (HaveOld) {weak_unregister_no_lock(oldTable-weak_table, oldObj, location);}// ③ 分配新值if (HaveNew) {newObj (objc_object *)weak_register_no_lock(newTable-weak_table, (id)newObj, location, CrashIfDeallocating);// 如果弱引用被释放 weak_register_no_lock 方法返回 nil // 在引用计数表中设置弱引用标记位if (newObj !newObj-isTaggedPointer()) {// 弱引用位初始化操作// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用newObj-setWeaklyReferenced_nolock();}// 之前不要设置 location 对象这里需要更改指针指向*location (id)newObj;}else {// 没有新值则无需更改}SideTable::unlockTwoHaveOld, HaveNew(oldTable, newTable);return (id)newObj; }我会逐步解释这个函数的主要步骤和作用 首先函数声明了一些变量包括 previouslyInitializedClass 用于标记已经初始化过的类oldObj 用于存储旧对象的引用oldTable 和 newTable 用于表示旧对象和新对象的 SideTable存储弱引用信息的数据结构。通过 retry 标签实现了一个重试的机制用于处理线程冲突。然后函数根据传入的模板参数 HaveOld 和 HaveNew 来获取旧对象和新对象的 SideTable并通过 SideTable::lockTwoHaveOld, HaveNew 对这两个 SideTable 进行加锁操作防止多线程竞争。防止线程冲突在加锁后函数会检查 location 是否与 oldObj 一致如果不一致说明当前的 location 已经处理过 oldObj但是又被其他线程所修改为了避免冲突需要重新执行 retry 标签处的代码重新获取旧对象。防止弱引用间死锁函数会检查是否存在新对象并且新对象不为nil。如果是则获得新对象的 isa 指针并检查该 isa 是否已经初始化。如果没有初始化则先对其进行初始化并且设置 previouslyInitializedClass 作为标记然后重新执行 retry 标签处的代码以防止其他线程竞争。接下来函数会根据模板参数 HaveOld 来清除旧值取消旧对象的弱引用并根据模板参数 HaveNew 来分配新值添加新对象的弱引用。如果新对象被成功注册并分配了弱引用则将弱引用位进行初始化并将 location 指向的对象指针更新为新对象的指针。最后函数通过 SideTable::unlockTwoHaveOld, HaveNew 对加锁的两个 SideTable 进行解锁并返回新对象的指针。 3.3 weak_register_no_lock id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) {objc_object *referent (objc_object *)referent_id;objc_object **referrer (objc_object **)referrer_id;// 如果referent为nil 或 referent 采用了TaggedPointer计数方式直接返回不做任何操作if (!referent || referent-isTaggedPointer()) return referent_id;// 确保被引用的对象可用没有在析构同时应该支持weak引用bool deallocating;if (!referent-ISA()-hasCustomRR()) {deallocating referent-rootIsDeallocating();}else {BOOL (*allowsWeakReference)(objc_object *, SEL) (BOOL(*)(objc_object *, SEL))object_getMethodImplementation((id)referent, SEL_allowsWeakReference);if ((IMP)allowsWeakReference _objc_msgForward) {return nil;}deallocating ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);}// 正在析构的对象不能够被弱引用if (deallocating) {if (crashIfDeallocating) {_objc_fatal(Cannot form weak reference to instance (%p) of class %s. It is possible that this object was over-released, or is in the process of deallocation.,(void*)referent, object_getClassName((id)referent));} else {return nil;}}// now remember it and where it is being stored// 在 weak_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中weak_entry_t *entry;if ((entry weak_entry_for_referent(weak_table, referent))) { // 如果能找到weak_entry,则讲referrer插入到weak_entry中append_referrer(entry, referrer); // 将referrer插入到weak_entry_t的引用数组中} else { // 如果找不到就新建一个weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table);weak_entry_insert(weak_table, new_entry);}// Do not set *referrer. objc_storeWeak() requires that the // value not change.return referent_id; }这段代码是Objective-C运行时中用于在弱引用表中注册一个弱引用的函数 weak_register_no_lock 的实现。该函数用于向 weak_table 中添加一个弱引用关系记录一个对象的弱引用指针。 现在我将逐步解释代码的主要步骤和作用 首先函数将传入的 referent_id 和 referrer_id 分别转换为 objc_object 类型的指针分别赋值给 referent 和 referrer 变量。然后函数会检查 referent 是否为nil或采用了TaggedPointer计数方式Tagged Pointer是一种优化机制用于在一些情况下直接将对象指针存储在指针本身而不通过额外的内存分配这里不需要处理弱引用。接下来函数会检查被引用的对象 referent 是否可用即它没有在析构过程中并且支持弱引用。这里需要注意在Objective-C中有些对象可能会通过重写allowsWeakReference方法来决定是否支持弱引用。所以对于有自定义引用计数方式的对象函数会调用allowsWeakReference方法来检查对象是否支持弱引用。如果被引用的对象 referent 正在析构过程中deallocating为true那么根据 crashIfDeallocating 参数的值函数会决定是返回nil还是抛出异常。如果 crashIfDeallocating 为true则会通过 _objc_fatal 抛出异常否则返回nil。如果被引用的对象 referent 可用并且支持弱引用则继续执行下面的步骤。函数会在 weak_table 中查找是否已经存在 referent 对应的弱引用条目 weak_entry。如果找到了就将 referrer 加入到该 weak_entry 中的引用数组中。如果找不到则创建一个新的 weak_entry 并将其插入到 weak_table 中然后将 referrer 加入到该新的 weak_entry 中的引用数组中。最后函数返回 referent_id即传入的被引用对象 referent 的指针。 3.4 weak_entry_for_referent static weak_entry_t * weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) {assert(referent);weak_entry_t *weak_entries weak_table-weak_entries;if (!weak_entries) return nil;size_t begin hash_pointer(referent) weak_table-mask; // 这里通过 weak_table-mask的位操作来确保index不会越界size_t index begin;size_t hash_displacement 0;while (weak_table-weak_entries[index].referent ! referent) {index (index1) weak_table-mask;if (index begin) bad_weak_table(weak_table-weak_entries); // 触发bad weak table crashhash_displacement;if (hash_displacement weak_table-max_hash_displacement) { // 当hash冲突超过了可能的max hash 冲突时说明元素没有在hash表中返回nil return nil;}}return weak_table-weak_entries[index]; }这是一个名为 weak_entry_for_referent 的函数用于在弱引用表中查找给定被引用对象 referent 对应的 weak_entry 条目。 3.5 append_referrer static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) {if (! entry-out_of_line()) { // 如果weak_entry 尚未使用动态数组走这里// Try to insert inline.//尝试插入内联引用的数组for (size_t i 0; i WEAK_INLINE_COUNT; i) {if (entry-inline_referrers[i] nil) {entry-inline_referrers[i] new_referrer;return;}}// 如果inline_referrers的位置已经存满了则要转型为referrers做动态数组。// Couldnt insert inline. Allocate out of line.weak_referrer_t *new_referrers (weak_referrer_t *)calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));// This constructed table is invalid, but grow_refs_and_insert// will fix it and rehash it.for (size_t i 0; i WEAK_INLINE_COUNT; i) {new_referrers[i] entry-inline_referrers[I];}entry-referrers new_referrers;entry-num_refs WEAK_INLINE_COUNT;entry-out_of_line_ness REFERRERS_OUT_OF_LINE;entry-mask WEAK_INLINE_COUNT-1;entry-max_hash_displacement 0;}// 对于动态数组的附加处理assert(entry-out_of_line()); // 断言 此时一定使用的动态数组if (entry-num_refs TABLE_SIZE(entry) * 3/4) { // 如果动态数组中元素个数大于或等于数组位置总空间的3/4则扩展数组空间为当前长度的一倍return grow_refs_and_insert(entry, new_referrer); // 扩容并插入}// 如果不需要扩容直接插入到weak_entry中// 注意weak_entry是一个哈希表keyw_hash_pointer(new_referrer) value: new_referrer// 细心的人可能注意到了这里weak_entry_t 的hash算法和 weak_table_t的hash算法是一样的同时扩容/减容的算法也是一样的size_t begin w_hash_pointer(new_referrer) (entry-mask); // (entry-mask) 确保了 begin的位置只能大于或等于 数组的长度size_t index begin; // 初始的hash indexsize_t hash_displacement 0; // 用于记录hash冲突的次数也就是hash再位移的次数while (entry-referrers[index] ! nil) {hash_displacement;index (index1) entry-mask; // index 1, 移到下一个位置再试一次能否插入。这里要考虑到entry-mask取值一定是0x111, 0x1111, 0x11111, ... 因为数组每次都是*2增长即8 16 32对应动态数组空间长度-1的mask也就是前面的取值。if (index begin) bad_weak_table(entry); // index begin 意味着数组绕了一圈都没有找到合适位置这时候一定是出了什么问题。}if (hash_displacement entry-max_hash_displacement) { // 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次肯定能够找到object对应的hash位置entry-max_hash_displacement hash_displacement;}// 将ref存入hash数组同时更新元素个数num_refsweak_referrer_t ref entry-referrers[index];ref new_referrer;entry-num_refs; }这段代码首先确定是使用定长数组还是动态数组如果是使用定长数组则直接将weak指针地址添加到数组即可如果定长数组已经用尽则需要将定长数组中的元素转存到动态数组中。 接着我们来看一下weak指针移除弱引用需要清除weak_entry时调用的方法weak_unregister_no_lock方法里面将旧的weak指针地址移除了。 3.6 weak_unregister_no_lock void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) {//对象的地址objc_object *referent (objc_object *)referent_id;//weak指针地址objc_object **referrer (objc_object **)referrer_id;weak_entry_t *entry;if (!referent) return;if ((entry weak_entry_for_referent(weak_table, referent))) { // 查找到referent所对应的weak_entry_tremove_referrer(entry, referrer); // 在referent所对应的weak_entry_t的hash数组中移除referrer// 移除元素之后 要检查一下weak_entry_t的hash数组是否已经空了bool empty true;if (entry-out_of_line() entry-num_refs ! 0) {empty false;}else {for (size_t i 0; i WEAK_INLINE_COUNT; i) {if (entry-inline_referrers[i]) {empty false; break;}}}if (empty) { // 如果weak_entry_t的hash数组已经空了则需要将weak_entry_t从weak_table中移除weak_entry_remove(weak_table, entry);}}// Do not set *referrer nil. objc_storeWeak() requires that the // value not change. }大概流程 首先它会在weak_table中找出referent对应的weak_entry_t 在weak_entry_t中移除referrer 移除元素后判断此时weak_entry_t中是否还有元素 emptytrue 如果此时weak_entry_t已经没有元素了则需要将weak_entry_t从weak_table中移除 4. dealloc 4.1 rootDealloc 当对象的引用计数为0时底层会调用_objc_rootDealloc方法对对象进行释放而在_objc_rootDealloc方法里面会调用rootDealloc方法。如下是rootDealloc方法的代码实现 xinline void objc_object::rootDealloc() {if (isTaggedPointer()) return; // fixme necessary?if (fastpath(isa.nonpointer !isa.weakly_referenced !isa.has_assoc !isa.has_cxx_dtor !isa.has_sidetable_rc)){assert(!sidetable_present());free(this);} else {object_dispose((id)this);} }大概流程 首先判断对象是否是Tagged Pointer如果是则直接返回。如果对象是采用了优化的isa计数方式且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。如果不能满足2中的条件则会调用object_dispose 方法。 4.2 object_dispose void *objc_destructInstance(id obj) {if (obj) {// Read all of the flags at once for performance.bool cxx obj-hasCxxDtor();bool assoc obj-hasAssociatedObjects();// This order is important.if (cxx) object_cxxDestruct(obj);if (assoc) _object_remove_associations(obj, /*deallocating*/true);obj-clearDeallocating();}return obj; }如果有自定义的C析构方法则调用C析构函数。如果有关联对象则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating 方法清除对象的相关引用。 4.3 clearDeallocating inline void objc_object::clearDeallocating() {if (slowpath(!isa.nonpointer)) {// Slow path for raw pointer isa.sidetable_clearDeallocating();}else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {// Slow path for non-pointer isa with weak refs and/or side table data.clearDeallocating_slow();}assert(!sidetable_present()); }clearDeallocating中有两个分支先判断对象是否采用了优化isa引用计数如果没有的话则需要调用sidetable_clearDeallocating方法清理对象存储在SideTable中的引用计数数据。如果对象采用了优化isa引用计数则判断是否有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced)符合这两种情况中一种的调用clearDeallocating_slow 方法。 4.4 sidetable_clearDeallocating void objc_object::sidetable_clearDeallocating() {SideTable table SideTables()[this];// clear any weak table items// clear extra retain count and deallocating bit// (fixme warn or abort if extra retain count 0 ?)//清除所有弱表项//清除额外的保留计数和释放位//如果额外保留计数0则修复警告或中止table.lock();RefcountMap::iterator it table.refcnts.find(this);if (it ! table.refcnts.end()) {if (it-second SIDE_TABLE_WEAKLY_REFERENCED) {weak_clear_no_lock(table.weak_table, (id)this);}table.refcnts.erase(it);}table.unlock(); }4.5 clearDeallocating_slow NEVER_INLINE void objc_object::clearDeallocating_slow() {assert(isa.nonpointer (isa.weakly_referenced || isa.has_sidetable_rc));SideTable table SideTables()[this]; // 在全局的SideTables中以this指针为key找到对应的SideTabletable.lock();if (isa.weakly_referenced) { // 如果obj被弱引用weak_clear_no_lock(table.weak_table, (id)this); // 在SideTable的weak_table中对this进行清理工作}if (isa.has_sidetable_rc) { // 如果采用了SideTable做引用计数table.refcnts.erase(this); // 在SideTable的引用计数中移除this}table.unlock(); }4.6 weak_clear_no_lock void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {objc_object *referent (objc_object *)referent_id;weak_entry_t *entry weak_entry_for_referent(weak_table, referent); // 找到referent在weak_table中对应的weak_entry_tif (entry nil) {/// XXX shouldnt happen, but does with mismatched CF/objc//printf(XXX no entry for clear deallocating %p\n, referent);return;}// zero out referencesweak_referrer_t *referrers;size_t count;// 找出weak引用referent的weak 指针地址数组以及数组长度if (entry-out_of_line()) {referrers entry-referrers;count TABLE_SIZE(entry);} else {referrers entry-inline_referrers;count WEAK_INLINE_COUNT;}for (size_t i 0; i count; i) {objc_object **referrer referrers[i]; // 取出每个weak ptr的地址if (referrer) {if (*referrer referent) { // 如果weak ptr确实weak引用了referent则将weak ptr设置为nil这也就是为什么weak 指针会自动设置为nil的原因*referrer nil;}else if (*referrer) { // 如果所存储的weak ptr没有weak 引用referent这可能是由于runtime代码的逻辑错误引起的报错_objc_inform(__weak variable at %p holds %p instead of %p. This is probably incorrect use of objc_storeWeak() and objc_loadWeak(). Break on objc_weak_error to debug.\n, referrer, (void*)*referrer, (void*)referent);objc_weak_error();}}}weak_entry_remove(weak_table, entry); // 由于referent要被释放了因此referent的weak_entry_t也要移除出weak_table }最后再来看一下weak指针销毁的方法 void objc_destroyWeak(id *location) {(void)storeWeakDoHaveOld, DontHaveNew, DontCrashIfDeallocating(location, nil); }该处调用storeWeak方法之后由于没有指向新的对象若我们的weak指针原来已经指向一个对象的话就会到weak_unregister_no_lock中来将旧的weak指针地址移除掉置为nil。 总结 weak的原理在于底层维护了一张weak_table_t结构的hash表key是所指对象的地址value是weak指针的地址数组。weak 关键字的作用是弱引用所引用对象的计数器不会加1并在引用对象被释放的时候自动被设置为 nil。对象释放时调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组然后遍历这个数组把其中的数据设为nil最后把这个entry从weak表中删除最后清理对象的记录。文章中介绍了SideTable、weak_table_t、weak_entry_t这样三个结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/918130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最新网站开发工具h5页面制作代码

一、前言大家都知道,基于Web端的测试的基础框架是需要Selenium做主要支撑的,这里边给大家介绍下Web测试核心之基于 Python 的 SeleniumSelenium 是用于测试 Web 应用程序用户界面 (UI) 的常用框架。它是一款用于运行端到端功能测试的超强工具。您可以使用…

哪个网站微博做的最好怎么将网站做成小程序

系统:ubuntu17.04数据库主要分文档型和服务型两类:文档型:如sqlite3 (17.04自带/usr/bin/sqlite3)就是一个文件,应用在移动端如手机,pad,家电等服务型:如mysql有服务端(存储数据)和客户端mysql数…

【英语启蒙动画合集】0基础宝宝必看的动画,超全!直接下载~

▼资源展示▼ 01 - SSS儿歌视频」 链接:https://pan.quark.cn/s/e7f58293918a 02 - Super Simple ABCs 自然拼读 链接:https://pan.quark.cn/s/7016192ad6f3 03- RAZ全部29个级别‼️‼️AA-Z(包含Z1 Z2)一定及时存…

基于OPC UA协议的SIMATIC PLC通信实现

一、系统架构设计 +-------------------+| 上位机(OPC UA Client) || (C#/Python/SCADA) |+--------+----------+|v +-------------------+ +-------------------+ | SIMATIC PLC | | OPC UA Serv…

Transformer模型/注意力机制/目标检测/语义分割/图神经网络/强化学习/生成式模型/自监督学习/物理信息神经网络等 - 指南

Transformer模型/注意力机制/目标检测/语义分割/图神经网络/强化学习/生成式模型/自监督学习/物理信息神经网络等 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !import…

AI 自动化智能体训练营 | 借助人工智能提升工作效率,打造自己的智能体工作流

课程背景与解决的问题 你是否也有这样的困扰? 每天被大量重复劳动占据时间? 报表、PPT、文案写得慢,效率低? 想用 AI 提高效率,却不知道从哪入手? 想做副业/创业,但缺乏技术与工具? 👉 这门训练营,将带你从…

无网站网络营销凡客诚品vancl

本文为大家介绍如何使用 串口 接收定长 和 不定长 的数据。 文章目录 前言一、串口接收定长数据1. 函数介绍2.代码实现 二、串口接收不定长数据1.函数介绍2. 代码实现 三,两者回调函数的区别比较四,空闲中断的介绍总结 前言 一、串口接收定长数据 1. 函…

做一个网站以及app多少钱深圳门户网站有哪些

需求:不去掉系统自带launcher的前提下,默认启动指定应用作为launcher现象:应用中带有属性"android.intent.category.HOME",开机会弹出选择界面思路:跳过选择界面,直接选中要启动的launcher并直接…

「Java EE开发指南」用MyEclipse开发的EJB开发工具(一)

「Java EE开发指南」用MyEclipse开发的EJB开发工具(一)如果您需要支持Java EE 5中引入的简化基于注释的POJO编程模型,那么EJB开发工具就是您的正确选择。在此您将了解到:EJB开发工具和EJB项目 持久性支持和EJB项目…

MX-X21

并没有参加 MX 比赛,这是一篇补题笔记。 T3 神人数据,一个显然假的贪心是从前往后能放就放,最后尝试将前后两端合并起来。 然后你会发现将近 50 个测试点还全是多测的情况下,我们仅仅 WA 了最后一个测试点。于是我…

实用指南:解析前端框架 Axios 的设计理念与源码

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

wordpress判断子分类响应式网站建设推荐乐云seo

目录 一、懒惰,尤其是脑子懒的人 1、首先,遇到问题学会自己去网上找答案 2、其次,带着两个及以上的方案 二、经常跟领导唱反调 1、首先,不要在公开场合进行反对,要学会给领导留足面子。 2、其次,一定…

Kubernetes Cilium网络组件和CoreDNS配置

1.部署helm网络组件wget https://mirrors.huaweicloud.com/helm/v3.15.2/helm-v3.15.2-linux-amd64.tar.gztar -zxvf helm-v3.15.2-linux-amd64.tar.gz cp linux-amd64/helm /usr/bin/# helm version version.BuildIn…

深入解析:博客SEO优化实战:从Google到百度,一套可复制的排名增长SOP

深入解析:博客SEO优化实战:从Google到百度,一套可复制的排名增长SOP2025-09-26 10:26 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: aut…

题解:P10107 [GDKOI2023 提高组] 树

题意:很简单了,不再赘述。 做法: 题解中好像有很牛的 bfs 序做法,太困难了,我只会暴力还常数很大的长链剖分。 首先看到是个 k 邻域问题,那基本上要不然是 bfs 序转化要不然是长链剖分,我只会后面这个东西所以考…

Gitee Wiki:AI赋能的下一代研发知识管理平台如何重塑软件行业协作范式

Gitee Wiki:AI赋能的下一代研发知识管理平台如何重塑软件行业协作范式 在数字化转型浪潮席卷全球的当下,软件研发领域正经历着前所未有的知识管理革命。传统文档管理系统碎片化严重、知识传承断层、安全管控薄弱等问…

COLMAP 安装在ubuntu20服务器上问题解决全记录

系统配置 主机型号:Supermicro SYS-4029GP-TRT2 CPU:Intel Xeon(双路,支持 AVX-512 / OpenMP 4.5) GPU:NVIDIA GPU,CUDA 11.8(驱动对应 515+ 版本) 操作系统:Ubuntu 20.04 LTS 内存:≥ 256 GB 编译器:gcc …

免费带后台的网站模板购物网站开发教学视频

一、接口和抽象类的区别? 方法定义:接口和抽象类,最明显的区别就是接口只是定义了一些方法而已,在不考虑Java8中default方法情况下,接口中只有抽象方法,是没有实现的代码的。(Java8中可以有默认方法) 修饰符:抽象类中的修饰符可以有public、protected和private和<…

完整教程:Prompt Tuning提示词微调工程

完整教程:Prompt Tuning提示词微调工程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mo…

Autodesk Moldflow 2026下载地址与安装教程

软件介绍 Autodesk Moldflow 2026是欧特克公司推出的注塑与压缩成型仿真软件,专为优化塑料产品设计及模具制造流程设计。该版本集成Autodesk Moldflow Data Fitting 2026工具,支持将原始材料数据转换为仿真兼容的.ud…