【iOS】AutoreleasePool自动释放池的实现原理

目录

    • ARC与MRC
    • 项目中的main函数
    • 自动释放池
    • @autoreleasepool {}实现原理
      • AutoreleasePoolPage
        • 总结
      • objc_autoreleasePoolPush的源码分析
        • autoreleaseNewPage
          • autoreleaseFullPage
          • autoreleaseNoPage
        • autoreleaseFast
        • 总结
      • autorelease方法源码分析
      • objc_autoreleasePoolPop的源码分析
        • popPage
          • releaseUntil
        • 总结
        • 通过私有函数打印自动释放池的情况


ARC与MRC

苹果在 iOS 5 中引入了ARC(Automatic Reference Counting)自动引用计数内存管理技术,通过LLVM编译器和Runtime协作来进行自动管理内存

LLVM编译器会在编译时在合适的地方为 OC 对象插入retainreleaseautorelease代码,省去了在MRC(Manual Reference Counting)手动引用计数下手动插入这些代码的工作,减轻了开发者的工作量

在MRC下,当我们不需要一个对象的时候,要调用releaseautorelease方法来释放它:

  • 调用release会立即让对象的引用计数减 1 ,如果此时对象的引用计数为 0,对象就会被销毁
  • 调用autorelease会将该对象添加进自动释放池中,它会在一个恰当的时刻自动给对象调用release,所以autorelease相当于延迟了对象的释放

项目中的main函数

main函数在整个iOS项目中是一个非常不起眼的函数,却是整个iOS程序的入口

// main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"int main(int argc, char * argv[]) {NSString * appDelegateClassName;@autoreleasepool { // 自动释放池appDelegateClassName = NSStringFromClass([AppDelegate class]);}return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

上面main函数的返回值说明,所有的事件、消息全部交给了UIApplication来处理,意味着整个iOS的应用都是包含在一个自动释放池里的

自动释放池

@autoreleasepool {}实现原理

// main.m
int main(int argc, const char * argv[]) {@autoreleasepool {Person* person = [[[Person alloc] init]];}return 0;
}

我们使用clang命令将main.m文件转换为main.cpp文件,cpp文件内容如下:

int main(int argc, const char * argv[]) {/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool; Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")));}return 0;
}

发现会生成一个__AtAutoreleasePool结构体

struct __AtAutoreleasePool {__AtAutoreleasePool() { // 构造函数,在生成结构体变量的时候调用atautoreleasepoolobj = objc_autoreleasePoolPush();}~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用objc_autoreleasePoolPop(atautoreleasepoolobj);}void * atautoreleasepoolobj;
};

@autoreleasepool这个块作用域内:

  • 开头声明了一个__AtAutoreleasePool结构体类型局部变量,这时会调用构造函数objc_autoreleasePoolPush
  • 结尾等作用域结束,局部变量被销毁,调用析构函数objc_autoreleasePoolPop

看一下pushpop的实现:

void *objc_autoreleasePoolPush(void) {// 调用了AutoreleasePoolPage中的push方法return AutoreleasePoolPage::push();
}void objc_autoreleasePoolPop(void *ctxt) {// 调用了AutoreleasePoolPage中的pop方法AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePoolPage

分析一下这个核心的类AutoreleasePoolPage,其本质是AutoreleasePoolPageData类:

#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)class AutoreleasePoolPage : private AutoreleasePoolPageData
{friend struct thread_data_t;public:
// 表示一个空池子
#   define EMPTY_POOL_PLACEHOLDER ((AutoreleasePoolPage*)1)
// 哨兵对象
#   define POOL_BOUNDARY nil// 每页的大小static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOLPAGE_MAX_SIZE;  // must be multiple of vm page size
#elsePAGE_MIN_SIZE;  // size and alignment, power of 2
#endifprivate:static pthread_key_t const key = AUTORELEASE_POOL_KEY;static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasingstatic size_t const COUNT = SIZE / sizeof(id);static size_t const MAX_FAULTS = 2;// ....
}// AutoreleasePoolPageData
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSstruct AutoreleasePoolEntry {uintptr_t ptr: 48;uintptr_t count: 16;static const uintptr_t maxCount = 65535; // 2^16 - 1};static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endifmagic_t const magic; __unsafe_unretained id *next;pthread_t const thread;AutoreleasePoolPage * const parent; // 指向上一个AutoreleasePoolPage的指针(链表中的第一个为nil)AutoreleasePoolPage *child; // 指向下一个存储AutoreleasePoolPage的指针(链表中的最后一个为nil)// 代表深度,第一个page的depth为0,往后每递增一个page,depth会加1uint32_t const depth;// 表示high water mark(最高水位标记)uint32_t hiwat;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){}
};
  • PAGE_MIN_SIZE:从AutoreleasePoolPage类中可以看出,每个page对象的大小为1 << 12,即2的12次方,4096字节
  • magic:对当前AutoreleasePoolPage 完整性的校验,就是用来判断对象是否完成初始化的一个标志
  • next:指向下一个即将产生的autoreleased对象的存放位置(当next == begin()时,表示AutoreleasePoolPage为空;当next == end()时,表示AutoreleasePoolPage已满
  • thread:当前线程,表明page与线程有关
  • childparent:表明每个page对象是通过双向链表联系起来的
  • depth:代表深度代表深度,第一个page的depth为0,往后每递增一个page,depth会加1
  • hiwat:表示high water mark(最高水位标记)
总结

@autoreleasepool底层会生成一个__AtAutoreleasePool变量,此变量内部又会生成objc_autoreleasePoolPushobjc_autoreleasePoolPop两个函数分别在作用域的开始和结尾进行push(入栈)和pop(出栈)操作,这两个操作都是依靠于AutoreleasePoolPage类的,AutoreleasePoolPage是一个双向链表结构

  • 当进入@autoreleasepool作用域时,objc_autoreleasePoolPush 方法被调用, runtime 会向当前的 AutoreleasePoolPage 中添加一个 nil 对象作为哨兵对象,并返回该哨兵对象的地址;
  • 对象调用autorelease方法,会被加入到对应的的AutoreleasePoolPage中去,next指针类似一个游标,不断变化,记录位置。如果加入的对象超出一页的大小,便会自动加一个新页。
  • 当离开@autoreleasepool作用域时,objc_autoreleasePoolPop(哨兵对象地址)方法被调用,其会从当前 page 的 next 指标的上一个元素开始查找, 直到最近一个哨兵对象, 依次向这个范围中的对象发送release消息

因为哨兵对象的存在,自动释放池的嵌套也是满足的,不管是嵌套还是被嵌套的自动释放池,找自己对应的哨兵对象就行了

objc_autoreleasePoolPush的源码分析

objc_autoreleasePoolPush -> AutoreleasePoolPage::push

// 入栈
static inline void *push() 
{id *dest;if (slowpath(DebugPoolAllocation)) {// Each autorelease pool starts on a new pool page.// 创建一个新的page对象,将POOL_BOUNDARY加进去dest = autoreleaseNewPage(POOL_BOUNDARY);} else {// 已有page对象,快速加入POOL_BOUNDARYdest = autoreleaseFast(POOL_BOUNDARY);}ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);return dest;
}
autoreleaseNewPage

如果是一个空池,那么会调用autoreleaseNewPage

static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{// 获取当前操作页AutoreleasePoolPage *page = hotPage();// 将POOL_BOUNDARY加到page中(入栈)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 (result) result->fastcheck();return result;
}

autoreleaseNewPage内部判断有无page,有就调用autoreleaseFullPage将对象压入栈,否则调用autoreleaseNoPage创建新的page,然后再进行压栈操作

autoreleaseFullPage

池子中有page,直接入栈

static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{// The hot page is full. // Step to the next non-full page, adding a new page if necessary.// Then add the object to that page.ASSERT(page == hotPage());ASSERT(page->full()  ||  DebugPoolAllocation);// 循环遍历当前page是否满了do {// 如果子页面存在,则将页面替换为子页面if (page->child) page = page->child;// 如果子页面不存在,则新建页面else page = new AutoreleasePoolPage(page);} while (page->full());// 设置为当前操作pagesetHotPage(page);// 压入栈return page->add(obj);
}// 设置当前操作页
static inline void setHotPage(AutoreleasePoolPage *page) 
{if (page) page->fastcheck();tls_set_direct(key, (void *)page);
}static inline AutoreleasePoolPage *coldPage() 
{AutoreleasePoolPage *result = hotPage();if (result) {while (result->parent) {result = result->parent;result->fastcheck();}}return result;
}

add压栈

id *add(id obj) {assert(!full());unprotect();// 传入对象存储的位置id *ret = next;  // faster than `return next-1` because of aliasing// 将obj压栈到next指针位置,然后进行next++,即下一个对象存储的位置*next++ = obj;protect();return ret;
}
autoreleaseNoPage

池子中无page,创建新的page,再入栈

static __attribute__((noinline))
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 yetASSERT(!hotPage());bool pushExtraBoundary = false;// 判断是否为空占位符,如果是,则将入栈标识为trueif (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_BOUNDARY,并且没有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;}// 如果对象是POOL_BOUNDARY,且没有申请自动释放池内存,则设置一个空占位符存储在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.// 如果标识为true,则压入栈if (pushExtraBoundary) {page->add(POOL_BOUNDARY);}// Push the requested object or pool.return page->add(obj);
}
autoreleaseFast

如果一开始就有page页面,不是空池子,那么直接进入到autoreleaseFast,再分别进行判断

static inline id *autoreleaseFast(id obj)
{AutoreleasePoolPage *page = hotPage();if (page && !page->full()) { // 已有page,并且没满return page->add(obj);} else if (page) {// 如果满了,则安排新的pagereturn autoreleaseFullPage(obj, page);} else {// page不存在,新建return autoreleaseNoPage(obj);}
}
总结
  • 每一个AutoreleasePoolPage对象都会有一定的存储空间,大概占用4096个字节
  • 每一个AutoreleasePoolPage对象内部的成员变量会占56个字节,然后剩余的空间才用来存储autorelease对象
  • 每一个@autoreleasePool的开始都会先将POOL_BOUNDARY对象压入栈,然后才开始存储autorelease对象,并且push方法会返回POOL_BOUNDARY对象的内存地址
  • 当一个AutoreleasePoolPage对象存满后才会往下一个AutoreleasePoolPage对象里开始存储
  • AutoreleasePoolPage对象里面的beginend分别对应着autorelease对象开始入栈的起始地址和结束地址
  • AutoreleasePoolPage对象里面的next指向下一个能存放autorelease对象地址的区域

请添加图片描述

autorelease方法源码分析

autorelease方法底层会调用objc_object::rootAutorelease()函数

// objc_object::autorelease
inline id 
objc_object::autorelease()
{ASSERT(!isTaggedPointer());if (fastpath(!ISA()->hasCustomRR())) {return rootAutorelease();}return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}// objc_object::rootAutorelease
inline id 
objc_object::rootAutorelease()
{// 如果是TaggedPointer就返回if (isTaggedPointer()) return (id)this;if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;return rootAutorelease2();
}// objc_object::rootAutorelease2
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{ASSERT(!isTaggedPointer());return AutoreleasePoolPage::autorelease((id)this);
}

最后还是会调用到AutoreleasePoolPageautorelease

static inline id autorelease(id obj)
{ASSERT(!obj->isTaggedPointerOrNil());id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#elseASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
#endifreturn obj;
}

然后进入到快速压栈,autoreleaseFast进行压栈操作,autoreleasepool只会将调用了autorelease的对象压入栈

autorelease和objc_autoreleasePush的整体分析如下图所示:

请添加图片描述

objc_autoreleasePoolPop的源码分析

objc_autoreleasePoolPop -> AutoreleasePoolPage::pop

static inline void
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.// 如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置page = coldPage();token = page->begin();} else {// 获取token所在的pagepage = pageForPointer(token);}stop = (id *)token;// 判断最后一个位置,是否是POOL_BOUNDARYif (*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);
}

beginend分别对应着autorelease对象的起始地址和结束地址

// 开始存放autorelease对象的地址:开始地址 + 他本身占用的大小
id * begin() {return (id *) ((uint8_t *)this+sizeof(*this));
}// 结束地址:开始地址 + PAGE_MAX_SIZE
id * end() {return (id *) ((uint8_t *)this+SIZE);
}// coldPage
static inline AutoreleasePoolPage *coldPage() 
{AutoreleasePoolPage *result = hotPage();if (result) {while (result->parent) {result = result->parent;result->fastcheck();}}return result;
}
popPage
template<bool allowDebug>
static void
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 poolspage->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();}}
}// kill
void kill() 
{// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbageAutoreleasePoolPage *page = this;while (page->child) page = page->child;AutoreleasePoolPage *deathptr;do {deathptr = page;// 子节点 变成 父节点page = page->parent;if (page) {page->unprotect();//子节点置空page->child = nil;page->protect();}delete deathptr;} while (deathptr != this);
}

内部会调用releaseUntil循环遍历进行pop操作

releaseUntil
void releaseUntil(id *stop) 
{// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage// 循环遍历// 判断下一个对象是否等于stop,如果不等于,则进入while循环while (this->next != stop) {// Restart from hotPage() every time, in case -release // autoreleased more objectsAutoreleasePoolPage *page = hotPage();// fixme I think this `while` can be `if`, but I can't prove it// 如果当前页是空的while (page->empty()) {// 将page赋值为父节点页page = page->parent;// 并设置当前页为父节点页setHotPage(page);}page->unprotect();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRSAutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;// create an obj with the zeroed out top byte and release thatid obj = (id)entry->ptr;int count = (int)entry->count;  // grab these before memset
#elseid obj = *--page->next;
#endifmemset((void*)page->next, SCRIBBLE, sizeof(*page->next));page->protect();if (obj != POOL_BOUNDARY) { // 只要不是POOL_BOUNDARY,就进行release
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS// release count+1 times since it is count of the additional// autoreleases beyond the first onefor (int i = 0; i < count + 1; i++) {objc_release(obj);}
#elseobjc_release(obj);
#endif}}// 设置当前页setHotPage(this);#if DEBUG// we expect any children to be completely emptyfor (AutoreleasePoolPage *page = child; page; page = page->child) {ASSERT(page->empty());}
#endif
}
总结
  • pop函数会将POOL_BOUNDARY的内存地址传进去
  • autorelease对象从end的结束地址开始进行发送release消息,一直找到POOL_BOUNDARY为止
  • 一旦发现当前页已经空了,就会去上一个页面进行pop,并释放当前页面
  • 整个入栈出栈的顺序是采用先进后出,和栈中顺序一样,但不代表着这里说的是真正的栈

pop出栈图示:

请添加图片描述

通过私有函数打印自动释放池的情况

我们可以通过一个私有函数_objc_autoreleasePoolPrint来打印分析整个autorelease的过程

// 声明内部私有函数,可以调用执行
extern void _objc_autoreleasePoolPrint(void);int main(int argc, const char * argv[]) {@autoreleasepool { // r1 = pushPerson* person1 = [[[Person alloc] init] autorelease];Person* person2 = [[[Person alloc] init] autorelease];@autoreleasepool { // r2 = push()Person* person3 = [[[Person alloc] init] autorelease];@autoreleasepool { // r3 = push()Person* person4 = [[[Person alloc] init] autorelease];_objc_autoreleasePoolPrint();} // pop(r3)} // pop(r2)
//        _objc_autoreleasePoolPrint();} // pop(r1)
}return 0;
}

打印结果:

在这里插入图片描述

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

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

相关文章

谁来做引领企业精益变革的舵手最合适?

在这个瞬息万变的商业时代&#xff0c;企业如同航行在波涛汹涌的大海中的巨轮&#xff0c;既需面对未知的挑战&#xff0c;也要抓住稍纵即逝的机遇。而在这场没有终点的航行中&#xff0c;引领企业实现精益变革的舵手&#xff0c;无疑是推动企业破浪前行、稳健致远的关键角色。…

FFmpeg Windows安装教程

一. 下载ffmpeg 进入Download FFmpeg网址&#xff0c;点击下载windows版ffmpeg。 下载第一个essentials版本就行。 二. 环境配置 上面源码解压后如下 将bin添加到系统环境变量 验证安装是否成功&#xff0c;输入ffmpeg –version&#xff0c;显示版本即为安装成功。

Python学习(1):使用Python的Dask库实现并行计算

目录 一、Dask介绍 二、使用说明 安装 三、测试 1、单个文件中实现功能 2、运行多个可执行文件 最近在写并行计算相关部分&#xff0c;用到了python的Dask库。 Dask官网&#xff1a;Dask | Scale the Python tools you love 一、Dask介绍 Dask是一个灵活的并行和分布式…

网工内推 | 国企运维工程师,华为认证优先,最高年薪20w

01 上海陆家嘴物业管理有限公司 &#x1f537;招聘岗位&#xff1a;IT运维工程师 &#x1f537;岗位职责&#xff1a; 1、负责对公司软、硬件系统、周边设备、桌面系统、服务器、网络基础环境运行维护、故障排除。 2、负责对各部门软件操作、网络安全进行检查、指导。 3、负责…

Mysql——update更新数据的方式

注&#xff1a;文章参考&#xff1a; MySQL 更新数据 不同条件(批量)更新不同值_update批量更新同一列不同值-CSDN博客文章浏览阅读2w次&#xff0c;点赞20次&#xff0c;收藏70次。一般在更新时会遇到以下场景&#xff1a;1.全部更新&#xff1b;2.根据条件更新字段中的某部分…

vivado OPT_SKIPPED

当跳过候选基元单元的逻辑优化时&#xff0c;OPT_skipped属性 更新单元格以反映跳过的优化。当跳过多个优化时 在同一单元格上&#xff0c;OPT_SKIPPED值包含跳过的优化列表。 架构支持 所有架构。 适用对象 OPT_SKIPPED属性放置在单元格上。 价值观 下表列出了各种OPT_design选…

【CSDN平台BUG】markdown图片链接格式被手机端编辑器自动破坏(8.6 已修复)

文章目录 bug以及解决方法bug原理锐评后续 bug以及解决方法 现在是2024年8月&#xff0c;我打开csdn手机编辑器打算修改一下2023年12月的一篇文章&#xff0c;结果一进入编辑器&#xff0c;源码就变成了下面这个样子&#xff0c;我起初不以为意&#xff0c;就点击了发布&#…

Revit二次开发选择过滤器,SelectionFilter

过滤器分为选择过滤器与规则过滤器 规则过滤器可以看我之前写的这一篇文章: Revit二次开发在项目中给链接模型附加过滤器 选择过滤器顾名思义就是可以将选择的构件ID集合传入并加入到视图过滤器中,有一些场景需要对某些构件进行过滤选择,但是没有共同的逻辑规则进行筛选的情况…

Golang | Leetcode Golang题解之第313题超级丑数

题目&#xff1a; 题解&#xff1a; func nthSuperUglyNumber(n int, primes []int) int {dp : make([]int, n1)m : len(primes)pointers : make([]int, m)nums : make([]int, m)for i : range nums {nums[i] 1}for i : 1; i < n; i {minNum : math.MaxInt64for j : range…

力扣面试150 基本计算器 双栈模拟

Problem: 224. 基本计算器 &#x1f468;‍&#x1f3eb; 参考题解 Code class Solution {public int calculate(String s) {// 存放所有的数字&#xff0c;用于计算LinkedList<Integer> nums new LinkedList<>();// 为了防止第一个数为负数&#xff0c;先往 nu…

开源免费的wiki知识库

开源的Wiki知识库有多种选择&#xff0c;它们各自具有不同的特点和优势&#xff0c;适用于不同的场景和需求。以下是一些主流的开源Wiki知识库系统&#xff1a; MediaWiki 简介&#xff1a;MediaWiki是使用PHP编写的免费开源Wiki软件包&#xff0c;是Wikipedia和其他Wikimedia…

鸿蒙(API 12 Beta2版)媒体开发【使用AudioCapturer开发音频录制功能】

如何选择音频录制开发方式 系统提供了多样化的API&#xff0c;来帮助开发者完成音频录制的开发&#xff0c;不同的API适用于不同录音输出格式、音频使用场景或不同开发语言。因此&#xff0c;选择合适的音频录制API&#xff0c;有助于降低开发工作量&#xff0c;实现更佳的音频…

Mybatis学习(3)

目录 一、JDBC vs Mybatis 二、Mybatis Plugin 三、Dao接口和xml文件的sql如何建立关联 四、Mybatis如何将sql执行结果封装为目标对象并返回的&#xff1f;都有哪些映射形式&#xff1f; 五、动态SQL 六、一级缓存和二级缓存 七、接口绑定的实现 八、Mybatis vs Hiberna…

【C++高阶】:自定义删除器的全面探索

✨ 我凌于山壑万里&#xff0c;一生自由随风起 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;C学习 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&am…

Java代码生成器EasyCode

Java代码生成器EasyCode 一、安装插件二、连接数据库后右键Generator生成代码 一、安装插件 在 IntelliJ IDEA 的插件市场中搜索 EasyCode&#xff0c;然后安装该插件 二、连接数据库后右键Generator生成代码 勇敢面对挑战&#xff0c;成功从不会远离坚持者。坚持不懈的努力…

原生js: AI聊天功能, 仿照chatGPT问答功能

问: 现在我们需要一个ai聊天功能, 接口已经给出: 只要是message就是我们的数据, 是message_end就是结束信息, 其他的我们不需要管. 回答: 我们不使用传统的fetch请求这个接口, 而是使用sse, eventSource去请求, 当我们输入框回车 或者 点击元素, 获取到输入框中用户输入的值…

SpringSecurity+Mysql数据库实现用户安全登录认证

Spring Security 是一个提供身份认证、授权和防范常见攻击的安全权限框架。无论是对命令式&#xff0c;还是响应式web应用程序都完美支持&#xff0c;现在主要用作保护基于 Spring 框架的应用程序的事实标准。相对于shiro来说&#xff0c;SpringSecurity功能更加复杂而且更加强…

吴恩达:如何系统学习机器学习?

最近在知乎圆桌里看到吴恩达的回答&#xff0c;【如何系统学习机器学习&#xff1f;】颇为惊喜&#xff0c;仿佛看到了知乎刚成立时的样子&#xff0c;请各个行业大佬来分享专业知识。 该回答目前已经有三千多赞&#xff0c;评论区也相当火爆&#xff0c;一片膜拜之声。 吴恩…

Java线程池的这几个大坑,你踩过几个?

首先看一个简单的例子&#xff1a;代码可能会抛出空指针异常,但这个异常就会被吞掉。 要优雅解决问题&#xff0c;可以为线程池设置一个全局的异常处理器,使用自定义的线程工厂来设置! java public class CustomThreadFactory implements ThreadFactory { private final Threa…

Vue3从零开始——掌握setup、ref和reactive函数的奥秘

文章目录 一、Vue 3 组合式 API 概述二、setup​ 函数的基本使用2.1 setup​ 函数的特点2.2 setup​ 函数的基本结构2.3 实现一个简单的小demo 三、ref​ 函数的功能和应用3.1 ref​函数介绍3.2 基本使用3.2.1 定义ref​数据3.2.2 修改响应式变量 3.3 使用ref​函数实现计数器 …