当前位置: 首页 > news >正文

iOS ------ weak的基本原理

1.weak的基本概念

  • weak弱引用,所引用的对象的引用计数不会加一,引用对象被释放的时候会自动设置为nil
  • 多用于解决对象间的相互引用造成内存泄露的循环引用的问题

2.实现原理

Person *object = [[Person alloc] init];
id __weak objc = object;

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash表,key是所指对象的指针,Value是weak指针的地址的集合。

weak的实现原理可以概括为三步:

1,初始化时,runtime会调用objc__initWeak函数,初始化一个新的weak指针指向对象的地址
2,添加引用时,objc__initweak函数会调用objc__storeWeak函数。objc_storeWeak函数的作用是更新指针的指向,创建对应的弱应用表
3,释放时,调用clearDeallocating函数。clearDeallocate函数首先根据对象地址获取所有weak指针地址的数组,然后遍历其中的数据设为nil,最后把这个entry从weak表中删除,清理对象去的记录

初始化时

objc_initWeak方法

// location指针objc , newObj原始对象object
id objc_initWeak(id *location, id newObj) {// 查看原始对象实例是否有效// 无效对象直接导致指针释放if (!newObj) {*location = nil;return nil;}// 这里传递了三个 bool 数值// 使用 template 进行常量参数传递是为了优化性能return storeWeak<false/*old*/, true/*new*/, true/*crash*/>(location, (objc_object*)newObj);
}

该方法有两个参数locationnewObj

  • location:__weak指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
  • newObj:所引用的对象。即例子中的object。

objc_storeWeak方法

objc_initWeak方法,在该方法内部调用了storeWeak方法。下面我们来看下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:// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable,即获取其旧的Tableif (HaveOld) {// 更改指针,获得以 oldObj 为索引所存储的值地址oldObj = *location;oldTable = &SideTables()[oldObj];} else {  // 如果weak ptr之前没有弱引用过一个obj,则oldTable = niloldTable = nil;}// 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTableif (HaveNew) {// 更改新值指针,获得以 newObj 为索引所存储的值地址newTable = &SideTables()[newObj];} else {  // 如果weak ptr不需要引用一个新obj,则newTable = nilnewTable = 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();// 如果cls还没有初始化,先初始化,再尝试设置weakif (cls != previouslyInitializedClass  &&!((objc_class *)cls)->isInitialized()) {// 解锁SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);// 对其 isa 指针进行初始化_class_initialize(_class_getNonMetaClass(cls, (id)newObj));// 如果该类已经完成执行 +initialize 方法是最理想情况// 如果该类 +initialize 在线程中// 例如 +initialize 正在调用 storeWeak 方法// 需要手动对其增加保护策略,并设置 previouslyInitializedClass 指针进行标记,防止改if分支再次进入previouslyInitializedClass = cls;// 重新获取一遍newObj,这时的newObj应该已经初始化过了goto retry;}}// ② 清除旧值//  如果之前该指针有弱引用过一个obj那就得需要清除之前的弱引用if (HaveOld) {// 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}// ③ 分配新值// 如果weak_ptr需要弱引用新的对象newObjif (HaveNew) {// (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中// 如果弱引用被释放 weak_register_no_lock 方法返回 nilnewObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,(id)newObj, location,CrashIfDeallocating);// (2) 更新newObj的isa的weakly_referenced bit标志位if (newObj  &&  !newObj->isTaggedPointer()) {// 弱引用位初始化操作// 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用newObj->setWeaklyReferenced_nolock();}// (3)*location 赋值,也就是将weak ptr直接指向了newObj,也就是确保其指针指向是正确的。可以看到,这里并没有将newObj的引用计数+1*location = (id)newObj;}else {// 没有新值,则无需更改}// 解锁,其他线程可以访问oldTable, newTable了SideTable::unlockTwoHaveOld, HaveNew>(oldTable, newTable);// 返回newObj,此时的newObj与刚传入时相比,设置了weakly-referenced bit位置1return (id)newObj;
}
  • storeWeak方法实际上是接收了5个参数,分别是haveOldhaveNewcrashIfDeallocating,这三个参数都是以模板的方式传入的,是三个bool类型的参数。分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash
  • 该方法维护了oldTablenewTable分别表示旧的引用弱表和新的弱引用表,它们都是SideTablehash表。
  • 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法,在old Obj的weak_entry_t中移除weak_ptr地址
  • 如果weak指针需要指向一个新的引用,调用weak_register_no_lock方法,将weak_ptr的地址记录到newObj对应的weak_entry_t中
  • 调用setWeaklyReferenced_nolock方法修改weak新引用的对象的weakly_referenced bit标识位(是否有被弱引用指向过)

SideTable结构体

SideTable的定义:

struct SideTable {// 保证原子操作的自旋锁spinlock_t slock;// 引用计数的 hash 表RefcountMap refcnts;// weak 引用全局 hash 表weak_table_t weak_table;
}

主要用于管理对象的引用计数和weak表

  • slock:为了防止竞争选择的自旋锁。
  • refcnts:用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。
  • weak_table:存储对象弱引用指针的hash表。是OC中weak功能实现的核心数据结构。

weak_table_t结构体

再来看看weak_table_t的底层代码

/**全局的弱引用表, 保存object作为key, weak_entry_t作为value* The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/
struct weak_table_t {// 保存了所有指向特定对象的weak指针集合weak_entry_t *weak_entries;// weak_table_t中有多少个weak_entry_tsize_t    num_entries;// weak_entry_t数组的countuintptr_t mask;// hash key 最大偏移值, // 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_table_t中不存在要找的weak_entry_tuintptr_t max_hash_displacement;
};
  • weak_entries:hash数组,用来存储弱引用对象的相关信息weak_entry_t。
  • num_entries:hash数组中的元素个数。
  • mask:hash数组长度-1,会参与hash计算。
  • max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)

weak_table_t是一个典型的hash结构。weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息。

weak_entry_t结构体

weak_entry_t的结构体也是一个hash结构,其存储的元素是弱应用对象指针的指针,通过操作指针的指针,就可以使得weak引用的指针在对象析构后,指向nil。

#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2typedef objc_object ** weak_referrer_t;struct weak_entry_t {// 所有weak指针指向的特定对象DisguisedPtr<objc_object> referent; // 被弱引用的对象// 共用体,保存weak指针的集合, // 引用个数小于4,用inline_referrers数组。用个数大于4,用动态数组weak_referrer_t *referrersunion {struct {weak_referrer_t *referrers;  // 弱引用该对象的对象指针地址的hash数组uintptr_t        out_of_line : 1;  // 是否使用动态hash数组标记位uintptr_t        num_refs : PTR_MINUS_1;  // hash数组中的元素个数uintptr_t        mask;  // hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)素个数)。uintptr_t        max_hash_displacement;  // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)};struct {// out_of_line=0 is LSB of one of these (don't care which)weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];};}
}
  • out_of_line:标志位。标志着weak_entry_t中是用数组保存还是hash表保存weak指针。
  • num_refs:引用计数。这里记录weak_entry_t表中weak指针的数量。
  • mask:weak_entry_t->referrers数组的count。
  • max_hash_displacement:hash key 最大偏移值, 采用了开放定制法解决hash冲突,超过+ max_hash_displacement说明weak_entry_t中不存在要找的weak_entry_t。

其中out_of_line的值通常情况下是等于零的,所以弱引用表总是一个objc_objective指针数组,当超过4时, 会变成hash

添加引用时

objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

weak_register_no_lock方法

新对象添加注册操作weak_register_no_lock,通过weak_register_no_lock函数把新的对象进行注册操作,完成与对应的弱引用表进行绑定操作

/*	weak_table:weak_table_t结构类型的全局的弱引用表。referent_id:weak指针所指的对象。*referrer_id:weak修饰的指针的地址。crashIfDeallocating:如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
*/
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 {  //不能被weak引用,直接返回nilBOOL (*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;
}
  • 如果referent为nil或referent采用了TaggedPointer计数方式,直接返回,不做任何操作。
  • 如果对象不能被weak引用,直接返回nil。
  • 如果对象正在析构,则抛出异常。
  • 如果对象没有再析构且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用
    对象的地址从弱引用表中找到对应的weak_entry,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry
weak_entry_for_referent取entry
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 = (index+1) & weak_table->mask;// index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。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表中,返回nilreturn nil;}}//返回找到的元素return &weak_table->weak_entries[index];
}
append_referrer添加元素
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{// 如果weak_entry 尚未使用动态数组,走这里if (! entry->out_of_line()) {// 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,做动态数组。// Couldn't 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()); // 断言:此时一定使用的动态数组// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 扩容,并插入return grow_refs_and_insert(entry, new_referrer);}// 如果不需要扩容,直接插入到weak_entry中// 注意,weak_entry是一个哈希表,key:w_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 = (index+1) & entry->mask;  // index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)if (index == begin) bad_weak_table(entry); // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。}// 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置if (hash_displacement > entry->max_hash_displacement) { entry->max_hash_displacement = hash_displacement;}// 将值插入刚才找到的hash表的空位,同时,更新元素个数num_refsweak_referrer_t &ref = entry->referrers[index];ref = new_referrer;entry->num_refs++;
}

这段代码就是实现了元素的插入,分为静态数组插入和动态数组插入,其中还加了静态数组到动态数组的变换。

移除引用

如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法将旧的weak指针地址移除。

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;objc_object **referrer = (objc_object **)referrer_id;weak_entry_t *entry;//  弱引用对象为nil不存在,直接返回if (!referent) return;// 查找到referent所对应的weak_entry_tif ((entry = weak_entry_for_referent(weak_table, referent))) { remove_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;}}}// 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除if (empty) {weak_entry_remove(weak_table, entry);}}return;
}
  • 首先,它会在weak_table中找出referent对应的weak_entry_t。
  • 在weak_entry_t中移除referrer。
  • 移除元素后,判断此时weak_entry_t中是否还有元素 (empty==true?)。
  • 如果此时weak_entry_t已经没有元素了,则需要将weak_entry_t从weak_table中移除。

释放时

当weak引用指向的对象被释放时,又是如何去处理weak指针的?

调用clearDealocating函数。clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历数组把其中的数据设为nil,最后把entryweak表清除,最后清理对象的记录。

clearDeallocating方法

inline void 
objc_object::clearDeallocating()
{//  判断对象是否采用了优化isa引用计数if (slowpath(!isa.nonpointer)) {// Slow path for raw pointer isa.//  如果没有的话则需要清理对象存储在SideTable中的引用计数数据sidetable_clearDeallocating();}//  如果对象采用了优化isa引用计数,则判断是否有使用weak引用(isa.weakly_referenced)或者有使用SideTable的辅助引用计数(isa.has_sidetable_rc),符合这两种情况中一种的,调用clearDeallocating_slow方法。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_slow方法

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));// 在全局的SideTables中,以this指针为key,找到对应的SideTableSideTable& table = SideTables()[this];//  上锁table.lock();// 如果obj被弱引用if (isa.weakly_referenced) {// 在SideTable的weak_table中对this进行清理工作weak_clear_no_lock(&table.weak_table, (id)this);}// 如果采用了SideTable做引用计数if (isa.has_sidetable_rc) {// 在SideTable的引用计数中移除thistable.refcnts.erase(this);}//  解锁table.unlock();
}

这里调用了weak_clear_no_lock来做weak_table的清理工作。

void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{objc_object *referent = (objc_object *)referent_id;// 找到referent在weak_table中对应的weak_entry_tweak_entry_t *entry = weak_entry_for_referent(weak_table, referent);if (entry == nil) {/// XXX shouldn't 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) {// 取出每个weak ptr的地址objc_object **referrer = referrers[i];if (referrer) {// 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因if (*referrer == referent) {*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();}}}// 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_tableweak_entry_remove(weak_table, entry);
}

objc_clear_deallocating该函数的动作如下:

  • 找到referent在weak_table中对应的weak_entry_t
  • 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil
  • 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table

总结:

  • weak的原理在底层维护了一张weak_table_t结构的hash表,key是所致对象的地址,value是weak指针的地址数组(weak_entry_t)
    • 如果weak指针之前指向了一个弱引用,则会调用weak_unregister_no_lock方法,在old Obj的weak_entry_t中移除weak_ptr地址
  • 如果weak指针需要指向一个新的引用,调用weak_register_no_lock方法,将weak_ptr的地址记录到newObj对应的weak_entry_t
  • 对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entryweak表中删除,最后清理对象的记录。
  • 本文介绍了SideTable、weak_table_t、weak_entry_t这样三个结构,它们之间的关系如下图所示。

在这里插入图片描述

相关文章:

iOS ------ weak的基本原理

1.weak的基本概念 weak弱引用&#xff0c;所引用的对象的引用计数不会加一&#xff0c;引用对象被释放的时候会自动设置为nil多用于解决对象间的相互引用造成内存泄露的循环引用的问题 2.实现原理 Person *object [[Person alloc] init]; id __weak objc object;Runtime维…...

实时更新UI界面

1.处理实时通信&#xff0c;几种方案 1&#xff1a;当一个用户发送一条需要实时更新的信息&#xff0c;我可以直接查找在线用户&#xff0c;通过在线用户来进行判断条件&#xff0c;发送更新请求 2&#xff1a;用户在一个需要实时更新的界面时&#xff0c;就不断的向服务端发…...

为什么Spring不推荐@Autowired用于字段注入

背景 Spring是Java程序员常用的框架之一。官方从Spring 4.0开始不推荐使用Autowired进行字段注入。 Spring注入方式 基于构造器注入&#xff1a;在构造器上使用Autowired。 优点&#xff1a;可以声明字段为final&#xff0c;确保字段在构造时被初始化。 基于setter方法注入&…...

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第三十九章 Linux MISC驱动

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…...

基于MobileNetv2的垃圾分类函数式自动微分-昇思25天打卡

基于MobileNetv2的垃圾分类 本文档主要介绍垃圾分类代码开发的方法。通过读取本地图像数据作为输入&#xff0c;对图像中的垃圾物体进行检测&#xff0c;并且将检测结果图片保存到文件中。 1、实验目的 了解熟悉垃圾分类应用代码的编写&#xff08;Python语言&#xff09;&a…...

STM32CubeIDE(CAN)

目录 一、概念 1、简述 2、CAN 的几种模式 二、实践 1、环回模式轮询通信 1.1 软件配置 1.2 代码编写 2、环回模式中断通信 2.1 软件配置 2.2 代码编写 一、概念 1、简述 STM32微控制器系列包含多个型号&#xff0c;其中一些型号集成了CAN&#xff08;Controller Are…...

GO Channel使用详解(各种场景下的最佳实践)

GO Channel使用详解(各种场景下的最佳实践) 一个知识点:通过反射的方式执行 select 语句,在处理很多的 case clause,尤其是不定长的 case clause 的时候,非常有用。而且,在后面介绍任务编排的实现时,我也会采用这种方法,所以,我先带你具体学习下 Channel 的反射用法…...

SwiftUI 5.0(iOS 17)滚动视图的滚动目标行为(Target Behavior)解惑和实战

概览 在 SwiftUI 的开发过程中我们常说&#xff1a;“屏幕不够&#xff0c;滚动来凑”。可见滚动视图对于超长内容的呈现有着多么秉轴持钧的重要作用。 这不&#xff0c;从 SwiftUI 5.0&#xff08;iOS 17&#xff09;开始苹果又为滚动视图增加了全新的功能。但是官方的示例可…...

picker 构建记录

picker 构建记录 tomlinuxtom:~/openverify/picker$ cd picker bash: cd: picker: 没有那个文件或目录 tomlinuxtom:~/openverify/picker$ export BUILD_XSPCOMM_SWIGpython tomlinuxtom:~/openverify/picker$ make rm -rf temp build /home/tom/Tools/verible-v0.0-3724/bin/…...

Docker部署kafka,Docker所在宿主机以外主机访问

# 安装启动zookeeper docker run -d --name zookeeper --publish 2181:2181 --volume /etc/localtime:/etc/localtime zookeeper:latest --network 指定的网络 -p&#xff1a;设置映射端口&#xff08;默认2181&#xff09; -d&#xff1a;后台启动 # 启动kafka docker run -d…...

控制欲过强的Linux小进程

控制欲强?视奸&#xff1f;普通人那才叫视奸&#xff0c;您是皇帝&#xff0c;天下大事无一逃过您的耳目&#xff0c;您想看什么就看什么&#xff0c;臣怀疑他在朋友圈私养兵士&#xff0c;囤积枪甲&#xff0c;蓄意谋反&#xff0c;图谋皇位啊&#xff01; 哈哈哈哈开个玩笑&…...

​探讨元宇宙和VR虚拟现实之间的区别​

在数字时代&#xff0c;人们对虚拟现实的兴趣与日俱增。在虚拟现实技术的推动下&#xff0c;出现了两个概念&#xff1a;元宇宙和VR虚拟现实。虽然这两个概念都与虚拟现实有关&#xff0c;但它们有着不同的特点和用途。在本文中&#xff0c;我们将探讨元宇宙和VR虚拟现实之间的…...

Docker Desktop安装

0 Preface/Foreward 1 安装 1.1 运行docker安装包 安装完Docker Desktop后&#xff0c;运行Docker Desktop&#xff0c;出现WSL 2安装不完整情况&#xff0c;具体情况如下&#xff1a; 解决方法&#xff1a;旧版 WSL 的手动安装步骤 | Microsoft Learn 也可以直接下载新的安…...

《Towards Black-Box Membership Inference Attack for Diffusion Models》论文笔记

《Towards Black-Box Membership Inference Attack for Diffusion Models》 Abstract 识别艺术品是否用于训练扩散模型的挑战&#xff0c;重点是人工智能生成的艺术品中的成员推断攻击——copyright protection不需要访问内部模型组件的新型黑盒攻击方法展示了在评估 DALL-E …...

vscode调试nextjs前端后端程序、nextjs api接口

最近有一个项目使用了nextjs框架&#xff0c;并且使用nextjs同时实现了前后端&#xff0c;由于之前前后端都是分离的&#xff0c;前端的调试可以通过在代码种添加debugger或者直接在浏览器中打断点实现&#xff0c;现在想调试后端接口&#xff0c;前面的方式就不适用了。故研究…...

《SeTformer Is What You Need for Vision and Language》

会议&#xff1a;AAAI 年份&#xff1a;2024 论文&#xff1a;DDAE: Towards Deep Dynamic Vision BERT Pretraining - AMinerhttps://www.aminer.cn/pub/6602613613fb2c6cf6c387c2/ddae-towards-deep-dynamic-vision-bert-pretraining 摘要 这篇论文介绍了一种新型的变换器…...

[保姆级教程]uniapp安装使用uViewUI教程

文章目录 创建 UniApp 项目下载uView UI下载安装方式步骤 1: 安装 uView UI步骤 2: 查看uView UI是否下载成功步骤 3: 引入 uView 主 JS 库步骤 4: 引入 uView 的全局 SCSS 主题文件步骤 5: 引入 uView 基础样式步骤 6: 配置 easycom 组件模式注意事项 NPM方式步骤 1: 安装 uVi…...

网络安全法规对企业做等保有哪些具体规定?

网络安全法规对企业做等保的具体规定 根据《中华人民共和国网络安全法》&#xff0c;企业作为网络运营者&#xff0c;需要履行网络安全等级保护制度的相关义务&#xff0c;确保网络安全和数据保护。具体规定包括&#xff1a; 网络安全等级保护制度&#xff1a;企业应根据网络安…...

Java开发中超好用Orika属性映射工具

Orika属性映射工具 引入pom依赖 <dependency><groupId>ma.glasnost.orika</groupId><artifactId>orika-core</artifactId><version>1.5.4</version></dependency>上干货 封装的工具类:OriUtilsimport ma.glasnost.orika.Map…...

qt初入门8:下拉框,输入框模糊查询,提示简单了解 (借助QCompleter)

实现一个简单的模糊查询的逻辑&#xff0c;输入框能提示相关项。 主要借助qt的QCompleter 类&#xff08; Qt 框架中提供的一个用于自动补全和模糊搜索的类&#xff09;&#xff0c;结合一些控件&#xff0c;比如QComboBox和QLineEdit&#xff0c;实现模糊查询的功能。 1&…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

质量体系的重要

质量体系是为确保产品、服务或过程质量满足规定要求&#xff0c;由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面&#xff1a; &#x1f3db;️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限&#xff0c;形成层级清晰的管理网络&#xf…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...