「iOS」——RunLoop学习

底层学习

  • iOS--RunLoop学习
    • RunLoop的概念
    • RunLoop与线程的关系
    • RunLoop的结构
      • Mode
      • Observer
      • Timer
      • Source
    • RunLoop 执行流程
    • RunLoop 的应用
      • 1.AutoreleasePool是什么时候释放的
      • 2.触控事件的响应
      • 3.刷新界面
      • 4.线程保活
  • 小知识
          • mach Port
      • **Toll-Free Bridging(对象桥接)详解**
        • `CFRunLoopTimer` 和 `NSTimer` 的桥接


iOS–RunLoop学习

RunLoop的概念

一般来讲,一个线程只能执行一次任务,任务完成后按成就会退出。但如果我们需要一个机制,让线程能随时处理事件但不退出。就需要这样一个dowhilt循环。

do {//获取消息//处理消息
} while (消息 != 退出)

这种模型通常被称为Event Loop,在很多系统和框架都有实现。例如:Node.js 的事件处理,Windows 程序的消息循环,而在macOS、iOS中就是Run Loop。

实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。

实际上,RunLoop就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面Event Loop的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

在iOS系统中,提供了两个EventLoop 的具体实现:

NSRunLoopCFRunLoopRef

CFRunLoopRef是在 CoreFoundation框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

RunLoop与线程的关系

RunLoop 是和线程一一对应的,app 启动之后,程序进入了主线程,苹果帮我们在主线程启动了一个 RunLoop。如果是我们开辟的线程,就需要自己手动开启 RunLoop,而且,如果你不主动去获取 RunLoop,那么子线程的 RunLoop 是不会开启的,它是懒加载的形式。

另外苹果不允许直接创建 RunLoop,只能通过 CFRunLoopGetMain()CFRunLoopGetCurrent() 去获取,其内部会创建一个 RunLoop 并返回给你(子线程),而它的销毁是在线程结束时。

这两个函数内部的逻辑大概是这样:

其中pthread_main_thread_np() 或 [NSThread mainThread] 获取主线程; pthread_self() 或 [NSThread currentThread] 获取当前线程。

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {OSSpinLockLock(&loopsLock);if (!loopsDic) {// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。loopsDic = CFDictionaryCreateMutable();CFRunLoopRef mainLoop = _CFRunLoopCreate();CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);}/// 直接从 Dictionary 里获取。CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));if (!loop) {/// 取不到时,创建一个loop = _CFRunLoopCreate();CFDictionarySetValue(loopsDic, thread, loop);/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);}OSSpinLockUnLock(&loopsLock);return loop;
}CFRunLoopRef CFRunLoopGetMain() {return _CFRunLoopGet(pthread_main_thread_np());
}CFRunLoopRef CFRunLoopGetCurrent() {return _CFRunLoopGet(pthread_self());
}

从上面的代码中可以看出:线程和RunLoop之间是一一对应的,其关系保存在一个全局的Dictionary中。

线程刚创建时并没有RunLoop,主动获取后系统会为我们创建一个RunLoop。在线程结束时RunLoop会被销毁。

  • CFRunLoopGetCurrent 函数便是获取当前线程的 CFRunLoop 对象,如果不存在的话会则会创建一个。
  • CFRunLoopGetMain 则是获取主线程的 CFRunLoop 对象。

就有如下代码:

  • NSRunloop类是Fundation框架中Runloop的对象,并且NSRunLoop是基于CFRunLoopRef的封装,提供了面向对象的API,但是这些API不是线程安全的。
  • CFRunLoopRef类是CoreFoundation框架中Runloop的对象,并且其提供了纯C语言函数的API,所有这些API都是线程安全。
// Foundation
NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // 获得当前RunLoop对象
NSRunLoop *runloop = [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象// Core Foundation
CFRunLoopRef runloop = CFRunLoopGetCurrent(); // 获得当前RunLoop对象
CFRunLoopRef runloop = CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop的结构

RunLoop里面有五个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

其结构图如下图所示:
请添加图片描述

Mode

Mode也就是模式,一个RunLoop当前只能处于某一种Mode中。Mode之间互不干扰,A Mode中发生的事情与B mode无关,尽管他们在一个RunLoop中。而苹果的滚动和默认状态分别对应两种不同的Mode,因此他们非常丝滑。

可以自定义 Mode,但是基本不会这样,苹果也为我们提供了几种 Mode:

  • kCFRunLoopDefaultMode :app默认Mode,通常主线程是在这个Mode下运行

  • UITrackingRunLoopMode :界面追踪Mode,比如ScrollView滚动时就处于这个Mode

  • UIInitializationRunLoopMode :刚启动app时进入的第一个Mode,启动完后不再使用

  • GSEventReceiveRunLoopMode :接受系统事件的内部Mode,通常用不到

  • kCFRunLoopCommonModes :不是一个真正意义上的Mode,但是如果你把事件丢到这里来,那么不管你当前处于什么Mode,都会触发你想要执行的事件。

  • NSModalPanelRunLoopMode:当应用程序显示一个模态对话框时使用此模式。在此模式下,RunLoop只处理与模态面板相关的事件,忽略其他非模态事件,确保用户只能与模态面板交互。

程序运行后,画面静止没有操作时,就处于 kCFRunLoopDefaultMode 状态,当滚动它时,就会处于 UITrackingRunLoopMode状态。如果你想要这两个状态能同时相应一件事情,要么同时添加到两种Mode里,要么把这件事情放到 kCFRunLoopCommonModes 中去执行

CFRunLoop对外暴露的管理Mode接口只有下面2个:

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);

Mode暴露的管理mode item的接口有下面几个:

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
  • 只能通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。

  • 对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。

Observer

CFRunLoopObserverRef是观察者,可以观察RunLoop的各种状态,每个Observer都包含一个回调(函数指针),当RunLoop的状态发生变化时,观察者就能通过回调接受这个变化。

苹果用一个枚举值来表示RunLoop的状态:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0),           // 即将进入 LoopkCFRunLoopBeforeTimers = (1UL << 1),    // 即将处理 TimerkCFRunLoopBeforeSources = (1UL << 2),   // 即将处理 SourcekCFRunLoopBeforeWaiting = (1UL << 5),   // 即将进入休眠kCFRunLoopAfterWaiting = (1UL << 6),    // 刚从休眠中唤醒kCFRunLoopExit = (1UL << 7),            // 即将退出 LoopkCFRunLoopAllActivities = 0x0FFFFFFFU   // 所有的状态
};

这里通过RunLoop的执行流程,来了解Observer的工作,具体如下:

请添加图片描述

由上图也可知道,Timer和Source就是RunLoop要干的活。

Timer

从结构的那张图可以看到,Mode中有一个Timer的数组,一个Mode中可以有多个Timer。Timer其实就是定时器,它的工作原理是:生成一个 Timer,确定要执行的任务,和多久执行一次,将其注册到 RunLoop 中,RunLoop 就会根据你设定的时间点,当时间点到时,去执行这个任务,如果它正在休眠,那么就会先唤醒 RunLoop,再去执行。

但是,这个时间点并不是非常准确。因为RunLoop的执行是有一个顺序的,要处理的事情也有先后顺序。如果时间点到了,RunLoop会将Timer要执行的事情添加到待执行清单,但是也需要等待执行清单前面的事情执行完了才会执行到Timer的事情

对于NSTimer,当我们用scheduledTimerWithTimeInterval方法初始化定时器时

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

系统会将NSTimer自动加入NSDefaultRunLoopMode模式中,所以他就等同于下面代码:

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

Source

Source是另外一种RunLoop要干的活。RunLoop中定义了两种版本的Source,一个是Source0,一个是Source1

  • Source0:处理 App 内部事件,App 自己负责管理(触发),如 UIEventCFSocket

​ Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

  • Source1:由 RunLoop 内核管理,Mach port 驱动,如 CFMackPortCFMessagePort

这里Source1比较不好理解。

Mach Port 是 macOS/iOS 底层(基于 Mach 微内核)的 进程间通信(IPC)机制,用于线程、进程或内核之间的安全消息传递。它是 XNU 内核(iOS/macOS 的核心)的核心组件之一,也是 Source1的底层驱动

所以简而言之,Source1其实就是进程间或者线程间通信的一种方式。比如在同一个RunLoop下,线程A想要给线程B发送C,这就需要通过port进行传输,然后系统将传输的东西包装成Source1,在线程A中监听port是否有东西传输过来,接收到后,唤醒RunLoop进行处理。

在源码上,他们不同的点在于:Source1的结构体中会带有mach_port_t,可以接受内核消息并触发回调。

typedef struct {CFIndex version;void *  info;const void *(*retain)(const void *info);void    (*release)(const void *info);CFStringRef (*copyDescription)(const void *info);Boolean (*equal)(const void *info1, const void *info2);CFHashCode  (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)mach_port_t (*getPort)(void *info);void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#elsevoid *  (*getPort)(void *info);void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

我们还可以添加Run Loop Source

在此之前,我们看一看Run Loop Mode提供给我们管理mode item的接口

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

CFRunLoopAddSource的代码结构如下:

//添加source事件
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */CHECK_FOR_FORK();if (__CFRunLoopIsDeallocating(rl)) return;if (!__CFIsValid(rls)) return;Boolean doVer0Callout = false;__CFRunLoopLock(rl);//如果是kCFRunLoopCommonModesif (modeName == kCFRunLoopCommonModes) {//如果runloop的_commonModes存在,则copy一个新的复制给setCFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;//如果runl _commonModeItems为空if (NULL == rl->_commonModeItems) {//先初始化rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);}//把传入的CFRunLoopSourceRef加入_commonModeItemsCFSetAddValue(rl->_commonModeItems, rls);//如果刚才set copy到的数组里有数据if (NULL != set) {CFTypeRef context[2] = {rl, rls};/* add new item to all common-modes *///则把set里的所有mode都执行一遍__CFRunLoopAddItemToCommonModes函数CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);CFRelease(set);}//以上分支的逻辑就是,如果你往kCFRunLoopCommonModes里面添加一个source,那么所有_commonModes里的mode都会添加这个source} else {//根据modeName查找modeCFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);//如果_sources0不存在,则初始化_sources0,_sources0和_portToV1SourceMapif (NULL != rlm && NULL == rlm->_sources0) {rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);}//如果_sources0和_sources1中都不包含传入的sourceif (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {//如果version是0,则加到_sources0if (0 == rls->_context.version0.version) {CFSetAddValue(rlm->_sources0, rls);//如果version是1,则加到_sources1} else if (1 == rls->_context.version0.version) {CFSetAddValue(rlm->_sources1, rls);__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);if (CFPORT_NULL != src_port) {//此处只有在加到source1的时候才会把souce和一个mach_port_t对应起来//可以理解为,source1可以通过内核向其端口发送消息来主动唤醒runloopCFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);__CFPortSetInsert(src_port, rlm->_portSet);}}__CFRunLoopSourceLock(rls);//把runloop加入到source的_runLoops中if (NULL == rls->_runLoops) {rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!}CFBagAddValue(rls->_runLoops, rl);__CFRunLoopSourceUnlock(rls);if (0 == rls->_context.version0.version) {if (NULL != rls->_context.version0.schedule) {doVer0Callout = true;}}}if (NULL != rlm) {__CFRunLoopModeUnlock(rlm);}}__CFRunLoopUnlock(rl);if (doVer0Callout) {// although it looses some protection for the source, we have no choice but// to do this after unlocking the run loop and mode locks, to avoid deadlocks// where the source wants to take a lock which is already held in another// thread which is itself waiting for a run loop/mode lockrls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */}
}

传入的三个参数分别是:

  • RunLoop对象
  • 添加的事件源即Source
  • RunLoop Mode的名称

通过添加source的这段代码可以得出如下结论:

  • 如果modeName传入kCFRunLoopCommonModes,则该source会被保存到RunLoop的_commonModeItems中

  • 如果modeName传入kCFRunLoopCommonModes,则该source会被添加到所有commonMode中

  • 如果modeName传入的不是kCFRunLoopCommonModes,则会先查找该Mode,如果没有,会创建一个

  • 同一个source在一个mode中只能被添加一次

RunLoop 执行流程

  1. 通知 Observer 已经进入 RunLoop

  2. 通知 Observer 即将处理 Timer

  3. 通知 Observer 即将处理 Source0

  4. 处理 Source0

  5. 如果有 Source1,跳到第 9 步(处理 Source1)

  6. 通知 Observer 即将休眠

  7. 将线程置于休眠状态,直到发生以下事件之一

    • 有 Source0

    • Timer 到时间执行

    • 外部手动唤醒

    • 为 RunLoop 设定的时间超时

  8. 通知 Observer 线程刚被唤醒

  9. 处理待处理事件

    • 如果是 Timer 事件,处理 Timer 并重新启动循环,跳到 2

    • 如果 Source1 触发,处理 Source1

    • 如果 RunLoop 被手动唤醒但尚未超时,重新启动循环,跳到 2

  10. 通知 Observer 即将退出 Loop

实际上 RunLoop 内部就是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里,直到超时或手动停止,该函数才会返回。

但是默认时间是一个巨大的数,可以理解为无穷大即不会超时。

RunLoop 进入休眠所调用的函数是 mach_msg(),其内部会进行一个系统调用,然后内核会将线程置于等待状态,所以这是一个系统级别的休眠。因此RunLoop在休眠时不会占用CPU。

RunLoop 的应用

1.AutoreleasePool是什么时候释放的

自动释放池的释放时机和RunLoop有关。苹果在主线程的RunLoop中注册了两个Observer。

第一个 Observer,监听一个事件,就是 Entry,即将进入 Loop 的时候,创建一个自动释放池,并且给了一个最高的优先级,保证自动释放池的创建发生在其他回调之前,这是为了保证能管理所有的引用计数。

第二个 Observer,监听两个事件,一个 BeforeWaiting,一个 ExitBeforeWaiting 的时候,干两件事,一个释放旧的池,然后创建一个新的池,所以这个时候,自动释放池就会有一次释放的操作,是在 RunLoop 即将进入休眠的时候。Exit 的时候,也释放自动释放池,这里也有一次释放的操作。

即:

  1. 进入RunLoop,先创建一个自动释放池
  2. RunLoop休息**kCFRunLoopBeforeWaiting(即将休眠)**,释放的当前的自动释放池,创建新的自动释放池
  3. RunLoop退出**kCFRunLoopExit(退出 RunLoop)**,释放当前的自动释放池

2.触控事件的响应

苹果会提前在App内注册一个Source1来监听系统事件。

比如,当一个 触摸/锁屏/摇晃 之类的系统事情产生,系统会先包装,包装好了,通过 mach port 传输给需要的 App 进程,传输后,提前注册的 Source1 就会触发回调,然后由 App 内部再进行分发。该行为发生在kCFRunLoopAfterWaiting 阶段

  1. 注册一个 Source1 用于接收系统事件
  2. 硬件事件发生
  3. IOKit.framework 生成 IOHIDEvent 事件并由 SpringBoard 接收
  4. SpringBoard 用 mach port 转发给需要的 App
  5. 注册的 Source1 触发回调
  6. 回调中将 IOHIDEvent 包装成 UIEvent 进行处理或分发

3.刷新界面

我们都知道改变 UI 的参数后,它并不会立马刷新。而它的刷新,也是通过 RunLoop 来实现。

当 UI 需要更新,先标记一个 dirty,然后提交到一个全局容器中去,调用 setNeedsLayout/setNeedsDisplay 后,系统将视图标记为待更新。。然后,在休眠前(BeforeWaiting)或退出时(Exit),调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ 触发界面渲染。

最终执行 CA::Transaction::commit() 提交所有 UI 变更。

4.线程保活

线程保活问题,从字面意思上就是保护线程的生命周期不结束.正常情况下,当线程执行完一次任务之后,需要进行资源回收,但是当有一个任务,随时都有可能去调用,如果在子线程去执行,并且让子线程一直存活着,为了避免来回多次创建毁线程的动作, 降低性能消耗。

通俗一点,我们可以创建一个常驻线程,其RunLoop始终运行,来实现这个功能。

例如第三方库AFNetworking

Runloop启动前必须要至少一个Timer/Observer/Source,所以AFNetworking在[runLoop run]

之前创建了NSMachPort添加进去了.通常情况下调用者需要持有这个NSMachPort并在外部线程通过这个port发送消息到loop内

+ (void)networkRequestThreadEntryPoint:(id)__unused object {@autoreleasepool {[[NSThread currentThread] setName:@"AFNetworking"];NSRunLoop *runLoop = [NSRunLoop currentRunLoop];[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];[runLoop run];}
}+ (NSThread *)networkRequestThread {static NSThread *_networkRequestThread = nil;static dispatch_once_t oncePredicate;dispatch_once(&oncePredicate, ^{_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];[_networkRequestThread start];});return _networkRequestThread;
}

小知识

mach Port

Mach Port 是 macOS/iOS 底层(基于 Mach 微内核)的 进程间通信(IPC)机制,用于线程、进程或内核之间的安全消息传递。它是 XNU 内核(iOS/macOS 的核心)的核心组件之一,也是 RunLoop 事件源(Source1)的底层驱动

Toll-Free Bridging(对象桥接)详解

在 Apple 的框架中,Toll-Free Bridging 是一种允许 Core Foundation 对象(C 语言)和 Foundation 对象(Objective-C)之间无缝转换 的机制。

  • 特点:某些 Core Foundation 和 Foundation 的类实际上是同一底层实现的两种接口,可以 直接强制转换 而无需额外内存操作。
  • 内存管理:转换后的对象仍遵循原有的内存管理规则(ARC 或手动 CFRetain/CFRelease)。
CFRunLoopTimerNSTimer 的桥接
  • CFRunLoopTimerRef:Core Foundation 的 C 语言结构体,用于定时任务。
  • NSTimer:Foundation 的 Objective-C 类,封装了定时器功能。
  • 桥接关系
// Core Foundation → Foundation
CFRunLoopTimerRef cfTimer = ...;
NSTimer *nsTimer = (__bridge NSTimer *)cfTimer;// Foundation → Core Foundation
NSTimer *nsTimer = ...;
CFRunLoopTimerRef cfTimer = (__bridge CFRunLoopTimerRef)nsTimer;

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

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

相关文章

从零构建 Node20+pnpm+pm2 环境镜像:基于 Dockerfile 的两种方案及持久化配置指南

前言&#xff1a;在Node.js项目部署中&#xff0c;环境一致性和服务自动恢复是运维的核心需求。无论是本地开发还是生产部署&#xff0c;使用Docker封装Node20、pnpm&#xff08;高效包管理&#xff09;和pm2&#xff08;进程守护&#xff09;环境&#xff0c;能避免“本地能跑…

【Python机器学习】4.3. 模型优化

喜欢的话别忘了点赞、收藏加关注哦&#xff08;关注即可查看全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 4.3.1. 实战中会遇到的问题 首先看一个例子&#xff1a; 根据任检测数据x1x_1x1​、x2x_2x2…

Impact rating 影响等级定义(学习笔记)

影响等级可以通过四个方面定义&#xff0c;包含安全性safety&#xff0c;经济型financial&#xff0c;操作性operational&#xff0c;和私密性privacy 即[S,F,O,P]这四个方面。每个方面又可以定义四个不同的等级&#xff0c;包含severe&#xff08;严重的&#xff09;&#xff…

同花顺前端潜在面试题目与答案

潜在面试题目与答案 以下是根据您提供的“岗位职责”和“岗位要求”整理出的潜在面试题目和参考答案。请注意&#xff0c;这些答案仅供参考&#xff0c;您需要根据自己的实际经验和理解进行更详细和个性化的阐述。 一、基础技术知识&#xff08;Vue/前端工程化/HTML/CSS/JS&…

J2EE模式---组合实体模式

组合实体模式基础概念组合实体模式&#xff08;Composite Entity Pattern&#xff09;是一种企业级设计模式&#xff0c;属于 J2EE 模式的一种&#xff0c;其核心思想是将多个实体对象组合成一个更高层次的对象&#xff08;组合实体&#xff09;&#xff0c;以简化客户端与这些…

基于CloudBase+React+CodeBudddy的云上智能睡眠应用开发实践

本文详细记录了如何利用CloudBase云开发平台、React前端框架和CodeBudddy智能编程技术栈&#xff0c;构建一个云端智能睡眠监测与分析系统。通过完整的项目实践&#xff0c;探索AIoT时代健康管理应用的开发范式。一、智能睡眠监测&#xff1a;云时代的健康守护者在快节奏的现代…

QML 模型

QML模型基础架构QML采用经典的Model-View-Delegate (MVD)​架构来分离数据与界面&#xff0c;这与MVC模式类似但更加适合声明式UI开发。在这个架构中&#xff1a;​Model​&#xff1a;负责管理数据&#xff0c;可以是简单的整数&#xff0c;也可以是复杂的C自定义模型​View​…

基于Trae IDE与MCP实现网页自动化测试的最佳实践

引言 在现代Web开发流程中&#xff0c;自动化测试已成为保障应用质量、提升开发效率的关键环节。Playwright作为一款新兴的测试框架&#xff0c;因其出色的跨浏览器支持能力和丰富的API特性&#xff0c;正逐渐成为自动化测试领域的主流选择。本文将详细介绍如何在葡萄城Trae ID…

Android 动画优化

动画是提升 Android 应用用户体验的核心手段 —— 流畅的过渡动画能让页面切换更自然&#xff0c;交互反馈动画能让操作更有质感。但动画也是性能 “重灾区”&#xff1a;掉帧、卡顿、内存暴涨等问题&#xff0c;往往源于对动画原理和优化技巧的忽视。本文将从动画性能的核心瓶…

Linux——进程间通信,匿名管道,进程池

文章目录一、进程间通信&#xff08;IPC&#xff09;的理解1.为什么进程间要通信&#xff08;IPC&#xff09;2.如何进行通信二、匿名管道1.管道的理解2.匿名管道的使用3.管道的五种特性4.管道的四种通信情况5.管道缓冲区容量三、进程池1.进程池的理解2.进程池的制作四、源码Pr…

深度分析Java内存回收机制

内存回收机制是Java区别于C/C等语言的核心特性之一&#xff0c;也是Java开发者理解程序性能、解决内存相关问题&#xff08;如内存泄漏、OOM&#xff09;的关键。 核心目标&#xff1a; 自动回收程序中不再使用的对象所占用的内存&#xff0c;防止内存耗尽&#xff0c;同时尽量…

uniapp “requestPayment:fail [payment支付宝:62009]未知错误“

解决方案&#xff1a;兄弟&#xff0c;有一种可能是你用测试机没有安装支付宝

分布在内侧内嗅皮层(MEC)的带状细胞对NLP中的深层语义分析的积极影响和启示

带状细胞&#xff08;Band Cells&#xff09;作为内侧内嗅皮层&#xff08;Medial Entorhinal Cortex, MEC&#xff09;层Ⅱ/Ⅲ的核心空间编码单元&#xff08;如网格细胞、头方向细胞等&#xff09;&#xff0c;其独特的神经计算机制为自然语言处理&#xff08;NLP&#xff09…

综合实验(4)

文章目录 目录 文章目录 前言 实验配置 实验总结 总结 前言 Cisco IOS Site-to-Site VPN&#xff08;虚拟专用网络&#xff09;是一种通过公共网络&#xff08;如互联网&#xff09;建立安全连接的技术&#xff0c;使不同地理位置的局域网&#xff08;LAN&#xff09;能够安…

JavaSE:开发环境的搭建(Eclipse)

一、IDE概述与核心价值 集成开发环境定义 提供编译器、调试器、项目管理工具的统一平台&#xff0c;显著提升开发效率。 Eclipse核心优势&#xff1a; 免费开源 &#xff1a;社区驱动&#xff0c;无授权费用跨平台支持 &#xff1a;Windows/Linux/macOS全兼容多语言扩展 &a…

使用LLaMA-Factory对大模型进行微调

之前了解过一些LLM从训练到落地的过程; 其中一个重要的步骤就是微调; 预训练&#xff1a;在大规模数据上学习通用语言知识。(使用海量无标注文本&#xff08;TB级&#xff09;) 微调&#xff1a;在预训练基础上&#xff0c;使用特定任务的标注数据进一步优化模型。(使用少量任务…

WxPython——一些最常见的错误现象及解决方法

一些最常见的错误现象及解决方法 有一些错误它们可能会发生在你的wxPython应用程序对象或初始的顶级窗口在创建时&#xff0c;这些错误可能是很难诊断的。下面我们列出一些最常见的错误现象及解决方法&#xff1a; 错误现象&#xff1a;程序启动时提示“unable to import modul…

SparkSQL 子查询 IN/NOT IN 对 NULL 值的处理

SparkSQL 子查询 IN/NOT IN 对 NULL 值的处理 官网&#xff1a;https://spark.apache.org/docs/4.0.0/sql-ref-functions.html https://spark.apache.org/docs/4.0.0/sql-ref-null-semantics.html#innot-in-subquery Unlike the EXISTS expression, IN expression can return…

【安卓笔记】lifecycle与viewModel

0. 环境&#xff1a; 电脑&#xff1a;Windows10 Android Studio: 2024.3.2 编程语言: Java Gradle version&#xff1a;8.11.1 Compile Sdk Version&#xff1a;35 Java 版本&#xff1a;Java11 1. 本篇文章涉及到的内容 lifecycle livedata databinding viewModel 2. …

84、逆向工程开发方法

逆向工程开发方法是一种通过分析现有产品、系统或代码来理解其设计原理、功能实现及潜在缺陷&#xff0c;并在此基础上进行改进、复制或创新的技术过程。它广泛应用于软件、硬件、机械、电子等多个领域&#xff0c;尤其在缺乏原始设计文档或需要快速掌握复杂系统时具有显著优势…