【iOS】ARC 与 Autorelease

news2025/7/23 6:16:35

ARC 与 Autorelease

文章目录

  • ARC 与 Autorelease
    • 前言
    • 何为ARC
      • 内存管理考虑方式
        • 自己生成的对象,自己持有
        • 非自己生成的对象,自己也可以持有
        • 不再需要自己持有的对象时释放
        • 非自己持有的对象无法释放
      • ARC的具体实现
        • 编译期和运行期ARC做的事情
          • ARC实现:
      • __autoreleasing 与 AutoreleasePool
        • AutoreleasePool的结构
          • AutoreleasePoolPage
          • objc_autoreleasePoolPush
            • autoreleaseNewPage
            • 压栈对象 autoreleaseFast
          • objc_autoreleasePoolPop
        • 小结

前言

今天笔者来学习一下有关于ARC和我们这里的一个Auorelease的内容

何为ARC

首先ARC就是我们自动引用计数,自动引用计数主要在代码中插入执行下面步骤

  • 生成对象
  • 持有对象
  • 释放对象
  • 废弃对象

在OC中对应的方法是:

对象操作OC方法
生成并且持有对象alloc/new/copy/mutableCopy等方法
持有对象retain方法
释放对象release
废弃对象dealloc

内存管理考虑方式

这里我们如果过度注意于引用计数这几个字上面的话,其实不算是一个正常客观的一个思考方式:

  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己也可以持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

在结合上面的对应的对象操作,我们下面对于这个几个部分进行一个讲解

自己生成的对象,自己持有

使用下面方法名称开头的方法名意味着自己生成的对象只有自己可以持有

  • alloc
  • new
  • copy
  • mutablecopy
+ (id)allocMyObject{
    return [[NSObject alloc] init];
}
+ (id)myObject{
    return [[NSObject alloc] init];
}

image-20250529215732414

这里我们打一个断点,来看一下这里的内容的内容,这里我们可以看到objc_release设置了一个标记位.只有用alloc开头的地方做了一个标记,另一个方法就没有标记.

这里博文后面在详细ARC对应实现内容,这里

非自己生成的对象,自己也可以持有

类似于这种代码:

NSMutableArray* ary = [NSMutableArray array];
[ary reatin];

这里是采用reatin来持有的

不再需要自己持有的对象时释放
[obj release];
非自己持有的对象无法释放

ARC的具体实现

在现在的OC语言中,我们有这些对象的是符合条件的:

  • block
  • 对象
  • 由attribute((NSObject))标记的类型。
编译期和运行期ARC做的事情
  • 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
  • ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。
ARC实现:

上面我们介绍了他的消息发送的标记,也就是ARC中生成并持有的操作

+ (id)allocMyObject{
    return [[NSObject alloc] init];
}
+ (id)myObject{
    return [[NSObject alloc] init];
}

image-20250531202157287

这里说明我们的allocMyObject方法会在标识位调用一个objc_release,而另一个方法则是调用我们的objc_unsfaleClaimAutoreleaseReturnVlaue.

这里还要对比一个函数objc_autoreleaseReturnValue:这个函数的作用相当于代替我们手动调用 autorelease, 创建了一个autorelease对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态, ReturnAtPlus0代表执行 autorelease, 以及ReturnAtPlus1代表不执行 autorelease。

id 
objc_autoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}

objc_unsafeClaimAutoreleasedReturnValue:这个函数的作用是:函数作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。

id
objc_unsafeClaimAutoreleasedReturnValue(id obj)
{
    if (acceptOptimizedReturn() == ReturnAtPlus0) return obj;

    return objc_releaseAndReturn(obj);
}

这时候我们在主函数赋值:

 id tmp1 = [self allocMyObject];

image-20250531210715174

这里我们可以看到下面有一个objc_storeStrong

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

先获取首先获取旧对象,然后进行比较,如果新对象和旧对象相同,则返回。否则,保留新对象,并将新对象的引用+1。否则,保留新对象,并将新对象的引用+1。然后更新指针*location,指向新对象。最后释放旧对象

这里其实就是我们的代码__strong修饰符给它插入了这个函数objc_storeStrong,所以在ARC的规则下其实就是通过下面这几种所有权修饰符号来插入不同的内存管理函数进行一个自动内存管理的:

  • __strong
  • __weak
  • __ unsafe __ retain
  • __autoreleasing

__autoreleasing 与 AutoreleasePool

在ARC无效的时候:

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

在ARC有效的时候:

@autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        id __autoreleasing obj = [[NSObject alloc] init];
        
    }

这里通过给对象赋值给附有__autoreleasing的变量等价于在MRC情况下,调用对象的autorelease方法,就是把对象注册到我们的autorelasePool

image-20250601205334794

如上图所示的,其实可以按照下面这个表来理解__autoreleasing的和自动释放池的一个关系:

机制角色
__autoreleasing标记对象:声明对象应交给自动释放池管理。
Autorelease Pool托管对象:存储被标记的对象,并在自身销毁时统一释放它们。

但是我们在实际开发中很少使用过有关于__autoreleasing这个来显式声明,这里有下面几种情况对象会被自动注册到AutoreleasePool

  • 编译器会进行优化,检查方法名是否以 alloc/new/copy/mutableCopy开始,如果不是则自动将返回对象注册到 Autoreleasepool;
+ (id)myObject{
    return [[NSObject alloc] init];
}

image-20250601213418593

  • 在访问__weak变量的时候,实际上必定要访问注册到 Autoreleasepool的对象,即会自动加入 Autoreleasepool;

  • id的指针或对象的指针(id*,NSError **),在没有显式地指定修饰符时候,会被默认附加上 __autoreleasing修饰符,加入 Autoreleasepool。这里是为了实现一个传递指针值的安全,把它注册到autoreleasepool可以保证这个对象不会被以外释放

AutoreleasePool的结构
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    }
    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;
};

这里可以看到它的一个结构体样式,从本质上来讲这个释放池也是一个对象.

认识他的一个底层结构:

Autorelease pool implementation

- A thread's autorelease pool is a stack of pointers. 
线程的自动释放池是指针的堆栈

- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每个指针都是要释放的对象,或者是POOL_BOUNDARY,它是自动释放池的边界。

- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
池令牌是指向该池的POOL_BOUNDARY的指针。弹出池后,将释放比哨点更热的每个对象。

- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. 
堆栈分为两个双向链接的页面列表。根据需要添加和删除页面。

- Thread-local storage points to the hot page, where newly autoreleased objects are stored. 
线程本地存储指向热页面,该页面存储新自动释放的对象。
 

可以总结成下面四点:

  • 1、自动释放池一个关于指针的栈
  • 2、其中的指针是指要释放的对象或者 pool_boundary 哨兵(现在经常被称为 边界
  • 3、自动释放池是一个的结构(虚拟内存中提及过) ,而且这个页是一个双向链表(表示有父节点 和 子节点,在类中提及过,即类的继承链)
  • 4、自动释放池和线程有关系

我们主要关心三个问题:

  • 什么时候创建
  • 对象是怎么加入自动释放池的
  • 那些对象会被加入
AutoreleasePoolPage

这两个函数是我们之前在上面看到的两个方法:

void *
_objc_autoreleasePoolPush(void)
{
    return objc_autoreleasePoolPush();
}

void
_objc_autoreleasePoolPop(void *ctxt)
{
    objc_autoreleasePoolPop(ctxt);
}
  • 下面这个源码展示对应的一个结构:
#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

public:
	static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
		PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
		PAGE_MIN_SIZE;  // size and alignment, power of 2 4096字节大小
#endif
    
private:
	static pthread_key_t const key = AUTORELEASE_POOL_KEY;
	static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
	static size_t const COUNT = SIZE / sizeof(id);
    static size_t const MAX_FAULTS = 2;

    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil

    // SIZE-sizeof(*this) bytes of contents follow

从上面的page的信息可以看出,其实每一个自动释放池是一个页,页的大小是4096字节.

然后发现它继承于AutoreleasePoolPageData,下面展示出这个结构体的样式

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    //用来校验AutoreleasePoolPage的结构是否完整
	magic_t const magic;//16个字节
  
    //指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
	__unsafe_unretained id *next;//8字节 存储指针的一个指针
  
    //指向当前线程
	pthread_t const thread;//8字节
  
    //指向父节点,第一个结点的parent值为nil
	AutoreleasePoolPage * const parent;//8字节
  
    //指向子节点,最后一个结点的child值为nil
	AutoreleasePoolPage *child;//8字节
  
    //表示深度,从0开始,往后递增1
	uint32_t const depth;//4字节
  
    //表示high water mark 最大入栈数量标记
	uint32_t hiwat;//4字节

    //初始化
	AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
		: magic(), next(_next), thread(_thread),
		  parent(_parent), child(nil),
		  depth(_depth), hiwat(_hiwat)
	{
	}
};

image-20250602133217831

这里画出了他的一个样式,它是一个双方向链表,它其实是一个这样的结构AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage中间存储他的一个信息.

objc_autoreleasePoolPush

现在我们学习它的压栈这个函数:

static inline void *push() 
    {
        id *dest;
  			
        if (slowpath(DebugPoolAllocation)) { //判断是否有pool
            // Each autorelease pool starts on a new pool page.
          //如果没有就创建
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
          //存在压栈一个哨兵
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
autoreleaseNewPage

创建一个NewPage

id *autoreleaseNewPage(id obj)
    {
  			//获得当前操作页
        AutoreleasePoolPage *page = hotPage();
  			//如果存在,则需要压栈
        if (page) return autoreleaseFullPage(obj, page);
  			//不存在就创建页
        else return autoreleaseNoPage(obj);
    }
//获得当前操作页
static inline AutoreleasePoolPage *hotPage() 
    {
  
  //获取当前页
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
  //如果是一个空池,则返回nil,否则,返回当前线程的自动释放池
    if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
//创建类的函数
id *autoreleaseNoPage(id obj)
    {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        ASSERT(!hotPage());

        bool pushExtraBoundary = false;
        //判断是否是空占位符,如果是,则压栈哨兵标识符置为YES
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        //如果对象不是哨兵对象,且没有Pool,则报错
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         objc_thread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
  			//对象是哨兵对象,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }

        // We are pushing an object or a non-placeholder'd pool.

  			//初始化第一页
        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
  			//压栈这里的哨兵节点
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }


    static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }

下面笔者直接给出结论:

  • autoreleasePool这里的链表的第一个页面可以存储了504个NSobject对象的指针,他的大小是4040 + 一个AutoreleasePoolPageData的大小(56字节) = 4096, 因为这里有一个哨兵节点,
  • 从第二页开始可以存储505个对象,因为少了一个哨兵节点所以正好可以存储505个对象4040 / 8 = 505(这里为什么存储的是8个字节大小,是因为这里保存的是一个对象的指针,可以更好的利用内存).

下面看一下这个具体的结构

在这里插入图片描述

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

每一个页内类似与一个栈的结构,通过数组实现的一个栈

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

AutoreleasePool结构图示

上面这个图展示出了这个双向链表的一个具体结构

压栈对象 autoreleaseFast

上面介绍了有关于创建一个autoreleasePool所做的事情,下面介绍一下有关于对象是怎么被压入栈中的.

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage(); // 先获取当前页
        if (page && !page->full()) { //页没满直接添加
            return page->add(obj);
        } else if (page) { //页满了
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj); //第一次创建页
        }
    }

这里也就分成这几个步骤:

  • 获取当前操作页,并判断页是否存在以及是否满了
  • 如果页存在,且未满,则通过add方法压栈对象 (给next加加,添加数据类似与一个数组的样式);
  • 如果页存在,且满了,则通过autoreleaseFullPage方法安排新的页面 (就是通过双向链表产生一个新页来实现)
  • 如果页不存在,则通过autoreleaseNoPage方法创建新页
objc_autoreleasePoolPop

这里的出出栈的思路其实大致和压入栈中一样

pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
  //判断入参
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) { 
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            page = pageForPointer(token); //获取当前页
        }

        stop = (id *)token;
  //判断最后一个位置是不是哨兵
        if (*stop != POOL_BOUNDARY) {
          //如果不是哨兵就是一个正常的对象
            if (stop == page->begin()  &&  !page->parent) {
              //如果是第一个位置,且没有父节点,什么也不做
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }

        return popPage<false>(token, page, stop);
    }
  • 这里先处理入参
  • 容错处理
  • 通过popPage出栈页 (这里其实就是类似于给这个页中的对象发送release消息)下面看一下这里的源码
popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
//释放到stop位置之前的所有对象
void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;

            // create an obj with the zeroed out top byte and release that
            id obj = (id)entry->ptr;
            int count = (int)entry->count;  // grab these before memset
#else
            id obj = *--page->next; //类似于处栈的方式处理里面的数据
#endif
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                // release count+1 times since it is count of the additional
                // autoreleases beyond the first one
                for (int i = 0; i < count + 1; i++) {
                    objc_release(obj);
                }
#else
                objc_release(obj);
#endif
            }
        }

        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }

上面其实就展示了这里的一个移除数据的操作:通过id obj = *--page->next; //类似于处栈的方式处理里面的数据然后发送release消息来释放内存

小结

这里其实就分成两个部分分析了AutoreleasePool的内容

  • 在自动释放池的压栈(即push)操作中
    • 当没有pool,即只有空占位符(存储在tls中)时,则创建页,压栈哨兵对象
    • 在页中压栈普通对象主要是通过next指针递增进行的,
    • 页满了时,需要设置页的child对象为新建页
  • 在出栈操作中
    • 通过next指针递减来实现一个释放
    • 页空了时,需要赋值页的parent对象为当前页

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

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

相关文章

铁电液晶破局 VR/AR:10000PPI 重构元宇宙显示体验

一、VR/AR 沉浸感困境&#xff1a;传统显示技术的天花板在哪&#xff1f; &#xff08;一&#xff09;纱窗效应与眩晕感&#xff1a;近眼显示的双重枷锁 当用户戴上 VR 头显&#xff0c;眼前像素网格形成的 “纱窗效应” 瞬间打破沉浸感。传统液晶 500-600PPI 的像素密度&…

竞争加剧,美团的战略升维:反内卷、科技与全球化

5月26日&#xff0c;美团发布2025年第一季度业绩报告&#xff0c;交出了一份兼具韧性与创新性的成绩单。 报告显示&#xff0c;公司一季度总营收866亿元&#xff0c;同比增长18%&#xff1b;核心本地商业收入643亿元&#xff0c;同比增长18%&#xff1b;季度研发投入58亿元&a…

(17)课36:窗口函数的例题:例三登录时间与连续三天登录,例四球员的进球时刻连续进球。

&#xff08;89&#xff09;例三登录时间 &#xff1a; 保留代码版本 &#xff1a; CREATE TABLE sql_8( user_id varchar(2), login_date date ); insert into sql_8(user_id,login_date) values(A,2024-09-02),(A,2024-09-03),(A,2024-09-04),(B,2023-11-25),(B,2023-12- 3…

高性能分布式消息队列系统(二)

上一篇博客将C进行实现消息队列的用到的核心技术以及环境配置进行了详细的说明&#xff0c;这一篇博客进行记录消息队列进行实现的核心模块的设计 五、项目的需求分析 5.1、项目框架的概念性理解 5.1.1、消息队列的设计和生产消费者模型的关系 在现代系统架构中&#xff0c;…

华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《天然蓄水库》: 目录 题目…

【Harmony OS】数据存储

目录 数据存储概述 首选项数据存储 关系型数据库 数据存储概述 • 数据存储 是为了解决应用数据持久化问题&#xff0c;使得数据能够存储在外存中&#xff0c;达到保存或共享目的。 • 鸿蒙应用数据存储包括 本地数据存储 和 分布式数据存储 。 • 本地数据存储 为应用…

MybatisPlus--核心功能--service接口

Service接口 基本用法 MyBatisPlus同时也提供了service接口&#xff0c;继承后一些基础的增删改查的service代码&#xff0c;也不需要去书写。 接口名为Iservice&#xff0c;而Iservice也继承了IRepository&#xff0c;这里提供的方法跟BaseMapper相比只多不少&#xff0c;整…

uniapp调试,设置默认展示的toolbar内容

uniapp调试&#xff0c;设置默认展示的toolbar内容 设置pages.json中 pages数组中 json的顺序就可以只需要调整顺序&#xff0c;不会影响该bar在页面中的显示默认展示第一条page

笔记本电脑开机无线网卡自动禁用问题

1.问题环境 电脑品牌&#xff1a;华硕笔记本天选4 电脑型号&#xff1a;FX507VV 电脑系统&#xff1a;windows 11_x64_24h2 文档编写时间&#xff1a;2025年6月 2.问题现象 1. 笔记本电脑开机之后自动禁用无线网卡 使用USB转RJ45转接头同样无效&#xff0c;这个网卡也给禁…

推荐一款使用html开发桌面应用的工具——mixone

简介 mixone是开发桌面应用&#xff08;Win、Mac、Linux&#xff09;的一款工具、其基于electron实现。其拥有简单的工程结构。以为熟悉前端开发的程序员可以很轻松的开发出桌面应用&#xff0c;它比electron的其他框架更简单&#xff0c;因为那些框架基本上还需要了解electro…

【云原生开发】如何通过client-go来操作K8S集群

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

八.MySQL复合查询

一.基本查询回顾 分组统计 group by 函数作用示例语句说明count(*)统计记录条数select deptno, count(*) from emp group by deptno;每个部门有多少人&#xff1f;sum(sal)某字段求和select deptno, sum(sal) from emp group by deptno;每个部门总工资avg(sal)求平均值select…

FastMCP vs MCP:协议标准与实现框架的协同

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

AI视频“入驻”手机,多模态成智能终端的新战场

文&#xff5c;乐乐 今天&#xff0c;无线蓝牙耳机&#xff08;TWS&#xff09;已经成为人人都用得起的产品。 但退回到9年前&#xff0c;苹果AirPods是全球第一款真正意义上的无线蓝牙耳机。靠着自研并申请专利的Snoop监听技术&#xff0c;苹果解决了蓝牙耳机左右延时和能耗…

nginx+tomcat负载均衡群集

一 案例部署Tomcat 目录 一 案例部署Tomcat 1.案例概述 1.1案例前置知识点 &#xff08;1&#xff09;Tomcat简介 &#xff08;2&#xff09;应用场景 2.实施准备 &#xff08;1&#xff09;关闭Linux防火墙 &#xff08;2&#xff09;安装Java 2.1 安装配置TOMACT …

建造者模式:优雅构建复杂对象

引言 在软件开发中&#xff0c;有时我们需要创建一个由多个部分组成的复杂对象&#xff0c;这些部分可能有不同的变体或配置。如果直接在一个构造函数中设置所有参数&#xff0c;代码会变得难以阅读和维护。当对象构建过程复杂&#xff0c;且需要多个步骤时&#xff0c;我们可…

现场总线结构在楼宇自控系统中的技术要求与实施要点分析

在建筑智能化程度不断提升的当下&#xff0c;楼宇自控系统承担着协调建筑内各类设备高效运行的重任。传统的集中式控制系统在面对复杂建筑环境时&#xff0c;逐渐暴露出布线繁琐、扩展性差、可靠性低等问题。而现场总线结构凭借其分散控制、通信高效等特性&#xff0c;成为楼宇…

学习路之PHP--easyswoole使用视图和模板

学习路之PHP--easyswoole使用视图和模板 一、安装依赖插件二、 实现渲染引擎三、注册渲染引擎四、测试调用写的模板五、优化六、最后补充 一、安装依赖插件 composer require easyswoole/template:1.1.* composer require topthink/think-template相关版本&#xff1a; "…

《云原生安全攻防》-- K8s网络策略:通过NetworkPolicy实现微隔离

默认情况下&#xff0c;K8s集群的网络是没有任何限制的&#xff0c;所有的Pod之间都可以相互访问。这就意味着&#xff0c;一旦攻击者入侵了某个Pod&#xff0c;就能够访问到集群中任意Pod&#xff0c;存在比较大的安全风险。 在本节课程中&#xff0c;我们将详细介绍如何通过N…

06 APP 自动化- H5 元素定位

文章目录 H5 元素定位1、APP 分类2、H5 元素3、H5 元素定位环境的搭建4、代码实现&#xff1a; H5 元素定位 1、APP 分类 1、Android 原生 APP2、混合 APP(Android 原生控件H5页面)3、纯 H5 App 2、H5 元素 H5 元素容器 WebViewWebView 控件实现展示网页 3、H5 元素定位环…