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

iOS——MRC与ARC以及自动释放池深入底层学习

MRC与ARC再回顾

在前面,我们简单学了MRC与ARC。MRC指手动内存管理,需要开发者使用retainrelease等手动管理对象的引用计数,确保对象在必要时被释放。ARC指自动内存管理,由编译器自动管理对象的引用计数,开发者不需要手动管理内存。

哪些对象需要我们进行内存管理呢?

  • 任何继承了NSObject的对象需要进行内存管理
  • 而其他非对象类型(int、char、float、double、struct、enum等) 不需要进行内存管理

这是因为

  • 继承了NSObject的对象的存储在操作系统的堆里边。而操作系统的堆一般由程序员分配释放。
  • 非OC对象一般放在操作系统的栈里面,操作系统的栈由操作系统自动分配释放。

例:

int main(int argc, const char * argv[])
{@autoreleasepool {int a = 10; // 栈int b = 20; // 栈// Person对象(计数器==1) : 堆Person *p = [[Person alloc] init];// p(局部指针变量): 栈}// 经过上面代码后, 栈里面的变量a、b、p 都会被回收// 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1return 0;
}

MRC深入

首先,先回顾空指针和野指针;

空指针、野指针

空指针:

空指针指的是没有指向存储空间的指针(里面存的是 nil, 也就是 0)。
给空指针发消息是没有任何反应的,也不会报错

空指针比如:

NSObject *a = [[NSObject alloc] init];   //执行完引用计数为1
[a release];   //执行完引用计数为 0,实例对象被释放。
a = nil;   //此时,a变为了空指针。
[a release];   // 再给空指针a发送消息就不会报错了。
[a release];  // 这段代码不会报错

野指针:

只要一个对象被释放了,我们就称这个对象为“僵尸对象(不能再使用的对象)”。
当一个指针指向一个僵尸对象,我们就称这个指针为「野指针」。
只要给一个野指针发送消息就会报错(EXC_BAD_ACCESS 错误)。

MRC避免循环引用

定义两个了类Person和Dog
Person类:

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class Dog;
@interface Person : NSObject
@property (nonatomic, assign) Dog *dog;
@endNS_ASSUME_NONNULL_END

Dog类:

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class Person;
@interface Dog : NSObject
@property (nonatomic, retain) Person *person;
@endNS_ASSUME_NONNULL_END

主函数:

Person *p = [[Person alloc] init];
Dog *d = [[Dog alloc] init];p.dog = d;
d.person = p;[p release];
[d release];

我们看上面的代码,会出现 A 对象要拥有 B 对象,而 B 对应又要拥有 A 对象,此时会形成循环 retain,导致 A 对象和 B 对象永远无法释放。

要解决这个问题的话:不要让 A retain B,B retain A,所以其中一方不要做retain方法。当两端互相引用时,应该一端用 retain,一端用 assign。

ARC深入

在之前的博客里,已经学习了ARC许多知识,包括修饰符、规则、属性和简单实现。可以看我的博客:iOS——【自动引用计数】ARC规则及实现
现在我们更深入的了解它的实现。

在Objective C中,有三种类型是ARC适用的:
1. block
2. objective 对象,id, Class, NSError*等
3. 由attribute((NSObject))标记的类型。

*像double ,CFStringRef等不是ARC适用的,仍然需要手动管理内存。
Tips: 以CF开头的(Core Foundation)的对象往往需要手动管理内存。

ARC在编译期和运行期分别做了什么?

先说结论:

1. 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
2. ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。

接下来我们来探究这个结论:

ARC的实现

ARC的底层是怎么工作的

首先根据前面的学习,我们知道,ARC有一套命名规则:若方法名以alloc、new、copy、mutableCopy开头,则方法的调用者需要负责保留和释放使用该方法返回的对象,在MRC中,就需要我们手动保留和释放返回的对象。如果不以上面这些开头,则不用调用者保留和释放,因为方法内部会自动执行autorelease方法。

下面用实例说明:
实例1
首先,我们创建一个Person类,这个类有两个类方法,一个是用creat开头的,一个是用new开头的,现在调用这两个方法:

#import "Person.h"@implementation Person+ (instancetype)creatPerson {return [[self alloc] init];
}
+ (instancetype)newPerson {return [[self alloc] init];
}@end
#import <Foundation/Foundation.h>
#import "Person.h"int main(int argc, const char * argv[]) {[Person creatPerson];[Person newPerson];
}

接下来我们查看它的汇编代码:
在这里插入图片描述

可以看见,在newPerson方法下,首先用objc_megSendnewPerson发送消息,然后有一个objc_release的标志位,这个标志位意味着系统在这个位置会调用objc_release函数。
而在creatPerson方法下,creatPerson中ARC修改后的代码大致逻辑是:

//Person.m
+ (instancetype)creatPerson {id temp = [self new];  return objc_autoreleaseReturnValue(temp); 
} 
//main.m
- (void)testForARC { objc_unsafeClaimAutoreleasedReturnValue([Person creatPerson]); 
}

在create中多了objc_autoreleaseReturnValue,这是因为ARC规则,无需手动释放的内部自动autorelease。
objc_autoreleaseReturnValue: 这个函数的作用相当于代替我们手动调用 autorelease, 创建了一个autorelease对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态, ReturnAtPlus0代表执行 autorelease, 以及ReturnAtPlus1代表不执行 autorelease。
objc_unsafeClaimAutoreleasedReturnValue函数作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。

实例2
我们再给出下面的例子:

id temp2 = [Person newPerson];

查看汇编代码:
在这里插入图片描述

可以看出来,与前面不同的是多了一个objc_storeStrong的标志位。因此可知,编译器在这里调用了该函数。我们在objc库里看看这个函数的实现。在objc4的NSObject.mm中:

void
objc_storeStrong(id *location, id obj)
{id prev = *location;if (obj == prev) {return;}objc_retain(obj);*location = obj;objc_release(prev);
}

说一下这个函数:objc_storeStrong函数是用于管理强引用的一个函数,主要用于更新一个指向对象的强引用,并确保正确的内存管理。
在上面这段代码里:首先获取旧对象的引用(prev)。然后进行比较,如果新对象和旧对象相同,则返回。否则,保留新对象,并将新对象的引用+1。然后更新指针*location,指向新对象。最后释放旧对象。使用这个函数是因为:temp2是一个强引用变量。编译器会将上述代码转换为类似以下内容:

id temp2 = [Person newPerson];
objc_storeStrong(&temp2, nil);

而同样的,在creatPerson方法中:

id temp1 = [Person creatPerson];

它的汇编代码:
在这里插入图片描述

这里同样使用了objc_storeStrong函数,但是多了一个objc_retainAutoreleasedReturnValue函数,这个函数将替代 MRC中的 retain方法, 此函数也会检测刚才提到的那个标志位, 如果为ReturnAtPlus0执行该对象的 retain操作,否则直接返回对象本身。

在这个例子中, 由于代码中没有对对象进行保留, 所以创建时objc_autoreleaseReturnValue函数设置的标志位状态是应该是ReturnAtPlus0。所以, 该函数在此处是会进行 retain操作的。
实例3
有如下代码:

#import "strat.h"
#import "Person.h"@interface strat ()@property (nonatomic, strong) id temp1;
@property (nonatomic, strong) id temp2;@end@implementation strat- (void)gogo {self.temp1 = [Person creatPerson];self.temp2 = [Person newPerson];
}@end

我们先看temp2的汇编代码:
在这里插入图片描述

可以看出,这段代码相比于实例2的代码,多了一个megSend:objc_msgSend$setTemp2,这个是setter方法的函数,所以其实这里相当于多了一个setter操作。因此这里ARC的补充的代码大概是:

- (void)setTemp2:(id)newValue {objc_storeStrong(&_temp2, newValue);
}

temp1与temp2被补充的部分差不多。

自动释放池

自动释放池(autorelease)是 Objective-C 内存管理的重要特性之一。
简单来说它允许开发者推迟对象的释放时间,通常在下一个事件循环中才执行释放操作。
自动释放池之前在博客 中学习过,现在来深入学习一下。

MRC下的自动释放池

在MRC下使用自动释放池是通过NSAutoreleasePool实现的(如果编译器版本是LLVM.3.0以上,那么@autorelease{…}也可以使用),其生命周期相当于c语言变量的作用域。对于所有调用过 autorelease方法的对象,在废弃 NSAutoreleasePool对象时,都将调用 release实例方法。

int main(int argc, const char * argv[]) {@autoreleasepool {//创建并使pool持有一个自动释放池NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];//创建Person实例对象pPerson *p = [[Person alloc] init];//将实例对象p加入pool持有的自动释放池[p autorelease];//废弃NSAutoreleasePool对象;//并向pool池中的所有对象发送消息,让它们调用release方法。这里相当于也[p release]了[pool drain];//这里会报错,因为p已被释放NSLog(@"%@", p);}return 0;
}

报错内容:Thread 1: EXC_BAD_ACCESS (code=1, address=0x370001a2b46808)

这里我当时有一个问题:当调用[p autorelease]的时候,是怎么判断当前的自动释放池是pool持有的那个的?
这是因为:运行时系统维护了一个全局的栈来跟踪所有的自动释放池。当一个新的 NSAutoreleasePool 对象被创建时,它会被推入栈顶。随后,当调用 -autorelease 方法时,当前的栈顶的自动释放池会接收该对象。

理解 NSAutoreleasePool对象的生命周期,如下图所示:
在这里插入图片描述

ARC下使用自动释放池

ARC环境不能使用 NSAutoreleasePool类也不能调用autorelease方法,代替它们实现对象自动释放的是 @autoreleasepool块和__autoreleasing修饰符。比较两种环境下的代码差异如下图:
在这里插入图片描述

ARC环境下使用自动释放池:

@autoreleasepool {id obj = [[NSObject alloc] init];NSLog(@"%@", obj); 
}

ARC的很多情况下,即使是不显式的使用 __autoreleasing,也能实现对象被注册到释放池中。主要包括以下几种情况:

编译器会进行优化,检查方法名是否以 alloc/new/copy/mutableCopy开始,如果不是则自动将返回对象注册到 Autoreleasepool;
访问附有 __weak修饰符的变量时,实际上必定要访问注册到 Autoreleasepool的对象,即会自动加入 Autoreleasepool;
id的指针或对象的指针(id*,NSError **),在没有显式地指定修饰符时候,会被默认附加上 __autoreleasing修饰符,加入 Autoreleasepool。

AutoreleasePool的具体实现

有以下自动释放池的代码:

int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"hello");}return 0;
}

将其转化为cpp代码:

int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSLog((NSString *)&__NSConstantStringImpl__var_folders_mr_nr_xs_2x079d0zxp68ymmm4c0000gn_T_main_82a790_mi_0);}return 0;
}

可以发现,自动释放池在底层中对应的是一个__AtAutoreleasePool的结构体,我们再在源码中可以找到这个结构体的定义:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);struct __AtAutoreleasePool {__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}// 边界对象(指针),用来标识自动释放池的开始和结束。这个对象在创建和销毁自动释放池时分别被推入和弹出。void * atautoreleasepoolobj;
};

可以看见,在这个结构体中有一个构造函数内部调用objc_autoreleasePoolPush(),和一个析构函数内部调用objc_autoreleasePoolPop(atautoreleasepoolobj),以及一个atautoreleasepoolobj边界对象。

  • objc_autoreleasePoolPush()用于返回边界对象
  • objc_autoreleasePoolPop(atautoreleasepoolobj)用于传入边界对象

边界对象其实就是 nil的别名,而它的作用事实上也就是为了起到一个标识的作用。

AutoreleasePoolPage

我们继续深入对objc_autoreleasePoolPush()objc_autoreleasePoolPop(atautoreleasepoolobj)进行学习,发现它们实际上是对AutoreleasePoolPage对应的静态方法 push和 pop的封装。
在NSObject的源码中可以发现以下代码:

void *
objc_autoreleasePoolPush(void)
{return AutoreleasePoolPage::push();
}NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage是C++的一个类,在NSObject.mm中可以找到它的代码,这是它的一部分核心代码:

class AutoreleasePoolPage {
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)  //空池占位
#   define POOL_BOUNDARY nil                //边界对象(即哨兵对象)static pthread_key_t const key = AUTORELEASE_POOL_KEY;static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasingstatic size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOLPAGE_MAX_SIZE;  // must be multiple of vm page size
#elsePAGE_MAX_SIZE;  // size and alignment, power of 2
#endifstatic size_t const COUNT = SIZE / sizeof(id);magic_t const magic;                  //校验AutoreleasePagePoolPage结构是否完整id *next;                             //指向新加入的autorelease对象的下一个位置,初始化时指向begin()pthread_t const thread;               //当前所在线程,AutoreleasePool是和线程一一对应的AutoreleasePoolPage * const parent;   //指向父节点page,第一个结点的parent值为nilAutoreleasePoolPage *child;           //指向子节点page,最后一个结点的child值为niluint32_t const depth;                 //链表深度,节点个数uint32_t hiwat;                       //数据容纳的一个上限//......
};

每个自动释放池都是是由若干个 AutoreleasePoolPage组成的双向链表结构,如下图所示:
在这里插入图片描述

AutoreleasePoolPage中拥有 parent和 child指针,分别指向上一个和下一个 page;当前一个 page的空间被占满(每个 AutorelePoolPage的大小为4096字节)时,就会新建一个 AutorelePoolPage对象并连接到链表中,后来的 Autorelease对象也会添加到新的 page中;

另外,当 next==begin()时,表示 AutoreleasePoolPage为空;
当 next ==end(),表示 AutoreleasePoolPage已满。

AutoreleasePool子线程上的释放时机

子线程默认不开启 RunLoop,那么其中的延时对象该如何释放呢?其实这依然要从 Thread和 AutoreleasePool的关系来考虑:

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.

也就是说,每一个线程都会维护自己的 Autoreleasepool栈,所以子线程虽然默认没有开启 RunLoop,但是依然存在 AutoreleasePool,在子线程退出的时候会去释放 autorelease对象。
前面讲到过,ARC会根据一些情况进行优化,添加 __autoreleasing修饰符,其实这就相当于对需要延时释放的对象调用了 autorelease方法。从源码分析的角度来看,如果子线程中没有创建 AutoreleasePool ,而一旦产生了 Autorelease对象,就会调用 autoreleaseNoPage方法自动创建 hotpage,并将对象加入到其栈中。所以,一般情况下,子线程中即使我们不手动添加自动释放池,也不会产生内存泄漏。

weak修饰符补充

在前面的博客中,我们知道了weak指针的一个生命周期是objc_initWeak函数到objc_destroyWeak函数,但其实这两个函数中都引用了另一个runtime的函数storeWeak,这个函数和我们刚刚见到的storeStrong函数是对应的,现在我们到NSObject.mm中看看他们的源码:

id
objc_initWeak(id *location, id newObj)
{if (!newObj) {*location = nil;return nil;}return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
}void
objc_destroyWeak(id *location)
{(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>(location, nil);
}

storeWeak:

static id 
storeWeak(id *location, objc_object *newObj)
{ASSERT(haveOld  ||  haveNew); // 断言必须有旧值或新值if (!haveNew) ASSERT(newObj == nil); // 如果没有新值,断言新对象为 nilClass previouslyInitializedClass = nil; // 之前初始化的类id oldObj; // 旧对象SideTable *oldTable; // 旧对象的 SideTableSideTable *newTable; // 新对象的 SideTable// 获取旧值和新值的锁。按锁地址排序以防止锁排序问题。// 如果旧值在操作过程中发生变化,重试。retry:if (haveOld) {oldObj = *location; // 获取旧对象oldTable = &SideTables()[oldObj]; // 获取旧对象的 SideTable} else {oldTable = nil; // 没有旧值}if (haveNew) {newTable = &SideTables()[newObj]; // 获取新对象的 SideTable} else {newTable = nil; // 没有新值}SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); // 获取旧值和新值的锁if (haveOld  &&  *location != oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 如果旧值在操作过程中发生变化,解锁并重试goto retry;}// 为了防止弱引用机制与 +initialize 机制之间的死锁,确保没有弱引用的对象具有未初始化的 isa。if (haveNew  &&  newObj) {Class cls = newObj->getIsa(); // 获取新对象的类if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) // 如果类未初始化{SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁class_initialize(cls, (id)newObj); // 初始化类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 ? CrashIfDeallocating : ReturnNilIfDeallocating); // 在弱引用表中登记新对象// weak_register_no_lock 如果弱存储应被拒绝,则返回 nil// 在引用计数表中设置 is-weakly-referenced 位。if (!_objc_isTaggedPointerOrNil(newObj)) {newObj->setWeaklyReferenced_nolock(); // 设置新对象为弱引用}// 不要在其他地方设置 *location。这会引入竞争条件。*location = (id)newObj; // 更新位置指针为新对象}else {// 没有新值。存储没有变化。}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); // 解锁旧值和新值的 SideTable// 这个操作必须在解锁后调用,因为它可能会调用任意代码。// 特别是,即使 _setWeaklyReferenced 没有实现,resolveInstanceMethod: 可能实现,并可能回调到弱引用机制。callSetWeaklyReferenced((id)newObj); // 调用设置弱引用的方法return (id)newObj; // 返回新对象
}

这段代码的主要思路是管理弱引用对象,首先获取旧对象和新对象并锁定相关表以防止多线程冲突,接着检查并确保新对象的类已初始化,然后从弱引用表中解除旧对象的登记,再将新对象登记到弱引用表并设置弱引用标志,最后解锁并调用设置弱引用的方法,从而安全地更新和管理弱引用对象。

弱引用表是Objective-C中用来管理和追踪弱引用对象的数据结构。在Objective-C的内存管理中,弱引用是一种不会增加对象引用计数的引用类型,这意味着当对象没有强引用时,弱引用不会阻止对象被释放。
弱引用表的主要作用包括三个方面:首先,它能够跟踪记录被弱引用的对象,并存储这些弱引用的位置;其次,当对象的所有强引用都被释放后,系统会自动将这些对象从弱引用表中移除,并将弱引用设置为nil,从而防止悬挂指针问题的发生;最后,通过弱引用表,可以有效地防止因循环引用而导致的内存泄漏,因为弱引用不会增加对象的引用计数,也不会导致对象无法释放。

ARC不会优化的场景。

ARC适用于绝大多数场景,但并不是万能的,例如performSelect系列有许多方法,带有选择子,编译器不知道选择子具体是什么,必须到了运行期才能确定,因此在编译时,不知道其方法名,没法利用ARC内存规则来判断是否释放。

如果这时我们使用selector(newTest)就会造成内存泄漏,因为ARC此时不会帮助我们添加release。

相关文章:

iOS——MRC与ARC以及自动释放池深入底层学习

MRC与ARC再回顾 在前面&#xff0c;我们简单学了MRC与ARC。MRC指手动内存管理&#xff0c;需要开发者使用retain、release等手动管理对象的引用计数&#xff0c;确保对象在必要时被释放。ARC指自动内存管理&#xff0c;由编译器自动管理对象的引用计数&#xff0c;开发者不需要…...

OpenCV教程:cv2如何把两张图片的大小,设置成相同的宽高

-------------OpenCV教程集合------------- Python教程99&#xff1a;一起来初识OpenCV&#xff08;一个跨平台的计算机视觉库&#xff09; OpenCV教程01&#xff1a;图像的操作&#xff08;读取显示保存属性获取和修改像素值&#xff09; OpenCV教程02&#xff1a;图像处理…...

web前端 Vue 框架面试120题(五)

面试题 81 . 请简述Vue更新数组时触发视图更新的方法&#xff1f; 参考回答&#xff1a; push()&#xff1b;pop()&#xff1b;shift()&#xff1b;unshift()&#xff1b;splice()&#xff1b;sort()&#xff1b;reverse()面试题 82 . 简述如何使用Vue-router实现懒加载的方式…...

CV12_ONNX转RKNN模型(谛听盒子)

暂时简单整理一下&#xff1a; 1.在边缘设备上配置相关环境。 2.配置完成后&#xff0c;获取模型中间的输入输出结果&#xff0c;保存为npy格式。 3.将onnx格式的模型&#xff0c;以及中间输入输出文件传送到边缘设备上。 4.编写一个python文件用于转换模型格式&#xff0c…...

k8s集群创建devops项目一直等待状态,没有发现host

问题分析&#xff1a; kubesphere在帮我们自动化创建一些智能自动化的额时候难免会发生一些小错误&#xff0c;devops-jenkins是一个部署也会生成一个容器组即pod&#xff0c;容器组的容器服务端口是 targetPort&#xff0c;容器组对外暴露的端口是port&#xff0c;拿devops-c…...

chatglm2-6b-prompt尝试

参考https://cloud.tencent.com/developer/article/2426296 chatglm2-6b&#xff0c;通过prompt来实现zero-shot/fewshot的自然语言处理任务 import json import re import os from modelscope.utils.constant import Tasks from modelscope import Model from modelscope.pi…...

vite+vue3项目初始化搭建

vitevue3项目初始化搭建 "nodejs": v18.19.0 "pnpm": 8.15.0 "vue": v3.4.21 "vite": v5.2.01.创建项目 Vite中文官网 pnpm create vitelatest项目名字&#xff1a;gd_web 选择框架&#xff1a;Vue3 选择语言&#xff1a;JavaScrip…...

使用 Vue3、Node.js、MySQL、Electron 和 Express 实现用户登录、文章管理和截屏功能

在现代 Web 开发中&#xff0c;前后端分离的架构已经成为主流。本文将详细介绍如何使用 Vue3、Node.js、MySQL、Electron 和 Express 实现一个完整的用户登录、文章管理和截屏功能的应用。我们将从项目的初始化开始&#xff0c;逐步实现各个功能模块&#xff0c;并提供详细的代…...

django中日志模块logging的配置和使用

一、文件的配置 settings.py文件中添加LOGGING块的配置&#xff0c;配置如下 # 日志记录 LOGGING {"version": 1,"disable_existing_loggers": False, # 用于确定在应用新的日志配置时是否禁用之前配置的日志器# 格式器"formatters": {"v…...

pyqt/pyside QTableWidget失去焦点后,选中的行仍高亮的显示

正常情况下pyqt/pyside的QTableWidget&#xff0c;点击input或者按钮失去焦点后 行的颜色消失了 如何在失去焦点时保持行的选中颜色&#xff0c;增加下面的代码&#xff1a; # 获取当前表格部件的调色板 p tableWidget.palette()# 获取活跃状态下的高亮颜色和高亮文本颜色&a…...

函数定义、合约与面向对象(以太坊solidity合约)

函数定义、合约与面向对象&#xff08;以太坊solidity合约&#xff09; 1-函数定义、构造与多态2-事件日志3-面向对象特征 1-函数定义、构造与多态 创建合约就是创建类&#xff0c;部署合约就是实例化 合约的方法还支持多态 还能使用第三方的库进行开发 整个合约部署后&…...

微服务:nacos

Nacos 由Alibaba推出的集成于SpringCloudAlibaba中的一款开源注册中心框架 主要功能: 注册中心 配置管理 nacos的安装和部署 nacos默认访问端口8848 docker pull nacos/nacos-server:1.2.0 docker run --env MODEstandalone --name nacos --restartalways -d -p 8848:8…...

前端css常用笔记

文章目录 一、样式二、vue笔记2.1、组件之间的通信2.1.1 子组件调用父组件的方法2.1.2 父组件调用子组件的方法2.1.3 孙组件调用祖父组件方法的实现 2.2、使用若依时,node_nodules越来越大的问题2.3、echart笔记 一、样式 1 文字与图标对不齐的解决方法 /**给icon加上这个样式即…...

WINUI或WPF灵活使用样式、控件模板、自定义控件、用户控件

在WINUI与WPF 中&#xff0c;控件模板&#xff08;ControlTemplate&#xff09;、样式&#xff08;Style&#xff09;、自定义控件&#xff08;CustomControl&#xff09;和用户控件&#xff08;UserControl&#xff09;都是构建复杂和灵活用户界面的重要工具&#xff0c;但它们…...

如何用EXCEL自动解方程/方程组?利用 矩阵乘法X=A-*B,X=mmult(minverse(A), B)

目录 问题的由来 1 数据 → 模拟分析 → 单变量求解 1.1 找一个单元格填入公式 1.2 功能入口 1.3 选择单变量求解&#xff0c;分别填入内容 1.4 求解 1.5 这个感觉用处不大 2 重点介绍&#xff0c;用EXCEL进行矩阵运算解方程的操作 2.1 运用EXCEL进行矩阵运算&…...

ComfyUI进阶:Comfyroll插件 (二)

前言&#xff1a; 学习ComfyUI是一场持久战&#xff0c;而Comfyroll Studio 是一款功能强大的自定义节点集合&#xff0c;专为 ComfyUI 用户打造&#xff0c;旨在提供更加丰富和专业的图像生成与编辑工具。借助这些节点&#xff0c;用户可以在静态图像的精细调整和动态动画的复…...

Spring Boot集成Activity7实现简单的审批流

由于客户对于系统里的一些新增数据&#xff0c;例如照片墙、照片等&#xff0c;想实现上级逐级审批通过才可见的效果&#xff0c;于是引入了Acitivity7工作流技术来实现&#xff0c;本文是对实现过程的介绍讲解&#xff0c;由于我是中途交接前同事的这块需求&#xff0c;所以具…...

自动驾驶,革了谁的命

概述 从AI 的出现开始&#xff0c;到现在已经慢慢地开始改变着周遭的世界。 从对话聊天&#xff0c;到当前的看图识文&#xff0c;图片转动效等等&#xff0c;慢慢地在与实体结合后&#xff0c;其发挥的威力是巨大的。 科技将会是改变世界的核心驱动力之一&#xff0c;已经深…...

在线实习项目|泰迪智能科技企业级项目学习,暑期大数据人工智能学习

在线实习介绍 实习时间&#xff1a;每个项目周期七周左右 面向对象&#xff1a;大数据、计算机相关专业学生&#xff1b;大三、大四毕业年度学生 在线实习收获 1、获得项目实战技能&#xff0c;积累项目经验 2、获得在线实习证明 项目特点…...

【BUG】已解决:To update, run: python.exe -m pip install --upgrade pip

To update, run: python.exe -m pip install --upgrade pip 目录 To update, run: python.exe -m pip install --upgrade pip 【常见模块错误】 解决办法&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&…...

Lua 运算符

Lua 运算符 Lua 是一种轻量级的编程语言&#xff0c;广泛用于游戏开发、脚本编写和其他应用程序。它具有一套丰富的运算符&#xff0c;用于执行各种数学和逻辑操作。本文将详细介绍 Lua 中的运算符&#xff0c;包括算术运算符、关系运算符、逻辑运算符和其他特殊运算符。 算术…...

园区道路车辆智能管控视频解决方案,打造安全畅通的园区交通环境

一、背景需求分析 随着企业园区的快速发展和扩张&#xff0c;道路车辆管理成为了保障园区秩序、提升运营效率及确保员工安全的重要任务。针对这一需求&#xff0c;旭帆科技TSINGSEE青犀提出了一种企业园区道路车辆管控的解决方案&#xff0c;通过整合视频监控、智能识别等技术…...

MATLAB R2023b下载安装教程汉化中文版设置

MATLAB R2023b下载安装教程汉化中文版设置 Matlab 是一款功能强大的商业数学软件 Matlab&#xff08;Matrix Labortory&#xff09;即矩阵实验室&#xff0c;它在数值计算、数据分析、算法开发、建模与仿真等众多领域都发挥着重要作用。 Matlab 具有以下显著特点和优势&…...

Java二十三种设计模式-工厂方法模式(2/23)

工厂方法模式&#xff1a;设计模式中的瑞士军刀 引言 在软件开发中&#xff0c;工厂方法模式是一种常用的创建型设计模式&#xff0c;它用于处理对象的创建&#xff0c;将对象的实例化推迟到子类中进行。这种模式不仅简化了对象的创建过程&#xff0c;还提高了代码的可维护性…...

【iOS】OC类与对象的本质分析

目录 前言clang常用命令对象本质探索属性的本质对象的内存大小isa 指针探究 前言 OC 代码的底层实现都是 C/C代码&#xff0c;OC 的对象都是基于 C/C 的数据结构实现的&#xff0c;实际 OC 对象的本质就是结构体&#xff0c;那到底是一个怎样的结构体呢&#xff1f; clang常用…...

【机器学习】使用Python的dlib库实现人脸识别技术

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、传统人脸识别技术1. 基于几何特征的方法2. 基于模板匹配的方法3. 基于统计学习的方法 三、深度学习在脸识别中的应用1. 卷积神经网络&#xff08;CNN&#xff09;2. FaceNet和ArcFace 四、使用Python和dlib库实…...

GitHub 令牌泄漏, Python 核心资源库面临潜在攻击

TheHackerNews网站消息&#xff0c;软件供应链安全公司 JFrog 的网络安全研究人员称&#xff0c;他们发现了一个意外泄露的 GitHub 令牌&#xff0c;可授予 Python 语言 GitHub 存储库、Python 软件包索引&#xff08;PyPI&#xff09;和 Python 软件基金会&#xff08;PSF&…...

【面试题】Golang 锁的相关问题(第七篇)

目录 1.Mutex 几种状态 1. 锁定状态&#xff08;Locked&#xff09; 2. 未锁定状态&#xff08;Unlocked&#xff09; 3. 唤醒状态&#xff08;Woken&#xff09; 4. 饥饿状态&#xff08;Starving&#xff09; 5. 等待者计数&#xff08;Waiters Count&#xff09; 总结…...

深入剖析CommonJS modules和ECMAScript modules

目录 前言CommonJS&#xff1a;服务器端模块化的先驱背景与起源语法与机制 ECMAScript Modules&#xff1a;现代前端的基石背景与起源语法与机制 比较与权衡语法差异加载机制编译时与运行时运行时行为构建第三方库现代开发环境 结论 前言 在 JavaScript 生态系统中&#xff0c…...

角点检测及MATLAB实现

一、角点简介 角点通常指的是两条直线构成角时的交点。‌在更广泛的应用中&#xff0c;‌角点这一概念也被扩展到数字图像处理领域&#xff0c;‌其中角点被定义为图像中物体轮廓线的连接点&#xff0c;‌这些点在某方面属性特别突出&#xff0c;‌即在某些属性上强度最大或者最…...

东莞工业品网站建设/如何做平台推广赚钱

编写函数&#xff0c;参数是两个非负整数n和m,返回组合数 &#xff0c; 其中m<n<25。例如&#xff0c;n25,m12时答案为5200300。转载于:https://www.cnblogs.com/jjzzx/p/5338052.html...

下载网站软件免费安装/湖人排名最新

科目二总结 注意!!! 以下情况根据具体情况调节 1.左倒车入库 直行&#xff0c;车头碰住黄线的时候朝左打死&#xff0c;人对准直角朝右回半圈&#xff0c;目视前方&#xff0c;车正了朝右回到初始角度&#xff0c;w朝上 倒车&#xff1a;挂倒挡&#xff0c;后视镜的角碰到黄…...

wordpress顶踩仿织梦/在线搜索资源

思路1&#xff1a;使用回溯法&#xff0c;同时剪掉不合理分支 // 产生不同的括号队列&#xff0c;选用回溯法 class Solution { public:vector<string> res;vector<string> generateParenthesis(int n) {string str "";backtrack(0,0,n,str);return re…...

wordpress 判断语言/泉州排名推广

IP核&#xff1a;美国著名的Dataquest咨询公司将半导体产业的IP定义为“用于ASIC或FPGA中的预先设计好的电路功能模块”。IP主要分为软IP、固IP和硬IP。软IP是用Verilog/VHDL等硬件描述语言描述的功能块&#xff0c;但是并不涉及用什么具体电路元件实现这些功能。固IP是完成了综…...

建设厅网站/成都seo外包

很多大学生在期末编写javaweb课程设计项目时&#xff0c;知道要实现什么功能&#xff0c;但是实际操作时一头雾水&#xff0c;不知道从何下手&#xff0c;出现眼高手低的情况&#xff0c;这也难怪&#xff0c;因为有的学校教课模式不是边学编练&#xff0c;一般都是老师上课讲课…...

wordpress文章排版/东莞网站推广策划

摘要&#xff1a;2018 年“双 11”的交易额又达到了一个历史新高度 2135 亿。相比十年前&#xff0c;我们的交易额增长了 360 多倍&#xff0c;而交易峰值增长了 1200 多倍。相对应的&#xff0c;系统数呈现爆发式增长。系统在支撑“双 11”过程中的复杂度和难度呈现指数级形式…...