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);
}
该方法有两个参数location
和newObj
。
- 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个参数,分别是haveOld
、haveNew
和crashIfDeallocating
,这三个参数都是以模板的方式传入的,是三个bool类型的参数。分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,如果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash- 该方法维护了
oldTable
和newTable
分别表示旧的引用弱表和新的弱引用表,它们都是SideTable
的hash
表。 - 如果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
,最后把entry
从weak
表清除,最后清理对象的记录。
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_pt
r地址
- 如果weak指针之前指向了一个弱引用,则会调用
- 如果weak指针需要指向一个新的引用,调用
weak_register_no_lock
方法,将weak_ptr
的地址记录到newObj对应的weak_entry_t
中 - 对象释放时,调用
clearDeallocating
函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil
,最后把这个entry
从weak
表中删除,最后清理对象的记录。 - 本文介绍了SideTable、weak_table_t、weak_entry_t这样三个结构,它们之间的关系如下图所示。
相关文章:

iOS ------ weak的基本原理
1.weak的基本概念 weak弱引用,所引用的对象的引用计数不会加一,引用对象被释放的时候会自动设置为nil多用于解决对象间的相互引用造成内存泄露的循环引用的问题 2.实现原理 Person *object [[Person alloc] init]; id __weak objc object;Runtime维…...
实时更新UI界面
1.处理实时通信,几种方案 1:当一个用户发送一条需要实时更新的信息,我可以直接查找在线用户,通过在线用户来进行判断条件,发送更新请求 2:用户在一个需要实时更新的界面时,就不断的向服务端发…...
为什么Spring不推荐@Autowired用于字段注入
背景 Spring是Java程序员常用的框架之一。官方从Spring 4.0开始不推荐使用Autowired进行字段注入。 Spring注入方式 基于构造器注入:在构造器上使用Autowired。 优点:可以声明字段为final,确保字段在构造时被初始化。 基于setter方法注入&…...

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第三十九章 Linux MISC驱动
i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…...

基于MobileNetv2的垃圾分类函数式自动微分-昇思25天打卡
基于MobileNetv2的垃圾分类 本文档主要介绍垃圾分类代码开发的方法。通过读取本地图像数据作为输入,对图像中的垃圾物体进行检测,并且将检测结果图片保存到文件中。 1、实验目的 了解熟悉垃圾分类应用代码的编写(Python语言)&a…...

STM32CubeIDE(CAN)
目录 一、概念 1、简述 2、CAN 的几种模式 二、实践 1、环回模式轮询通信 1.1 软件配置 1.2 代码编写 2、环回模式中断通信 2.1 软件配置 2.2 代码编写 一、概念 1、简述 STM32微控制器系列包含多个型号,其中一些型号集成了CAN(Controller Are…...
GO Channel使用详解(各种场景下的最佳实践)
GO Channel使用详解(各种场景下的最佳实践) 一个知识点:通过反射的方式执行 select 语句,在处理很多的 case clause,尤其是不定长的 case clause 的时候,非常有用。而且,在后面介绍任务编排的实现时,我也会采用这种方法,所以,我先带你具体学习下 Channel 的反射用法…...

SwiftUI 5.0(iOS 17)滚动视图的滚动目标行为(Target Behavior)解惑和实战
概览 在 SwiftUI 的开发过程中我们常说:“屏幕不够,滚动来凑”。可见滚动视图对于超长内容的呈现有着多么秉轴持钧的重要作用。 这不,从 SwiftUI 5.0(iOS 17)开始苹果又为滚动视图增加了全新的功能。但是官方的示例可…...

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:设置映射端口(默认2181) -d:后台启动 # 启动kafka docker run -d…...

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

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

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

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

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

《SeTformer Is What You Need for Vision and Language》
会议:AAAI 年份:2024 论文: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…...
网络安全法规对企业做等保有哪些具体规定?
网络安全法规对企业做等保的具体规定 根据《中华人民共和国网络安全法》,企业作为网络运营者,需要履行网络安全等级保护制度的相关义务,确保网络安全和数据保护。具体规定包括: 网络安全等级保护制度:企业应根据网络安…...
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)
实现一个简单的模糊查询的逻辑,输入框能提示相关项。 主要借助qt的QCompleter 类( Qt 框架中提供的一个用于自动补全和模糊搜索的类),结合一些控件,比如QComboBox和QLineEdit,实现模糊查询的功能。 1&…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...

Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...