【iOS】——消息传递底层实现
消息传递是什么
Objective-C是一种动态类型语言,这意味着在编译时并不确定对象的具体类型,而是在运行时决定。消息传递机制允许程序在运行时向对象发送消息,对象再决定如何响应这些消息。
当你通过对象调用方法时,例如像这样[obj someMethod]编译器会将其转换为一个消息发送的底层调用,通常是 objc_msgSend(obj, @selector(someMethod))。这个函数接受两个主要参数:方法的调用者和方法选择器(也就是方法名)。
void objc_msgSend(id self, SEL cmd, ....)
第一 个参数代表接收者也就是方法调用者,第二个参数代表方法选择器(SEL 是选择子的类型)也就是方法的名字,后续参数就是消息中的 那些参数,其顺序不变。
选择子SEL
选择子(Selector)是用于表示方法名的数据类型。它是一个在运行时由编译器生成的唯一的标识符,用于在对象中查找并调用相应的方法。
OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个方法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的
IMP
IMP是一个函数指针,它指向方法的实际实现。当运行时系统找到了与选择器相匹配的方法时,它会获取该方法的IMP,然后调用这个函数指针来执行方法的代码。
IMP通常被声明为id返回类型和接受id类型的self和SEL类型的_cmd参数的函数指针。例如:
typedef id (*IMP)(id, SEL, ...);
SEL与IMP的关系
在运行时系统中,SEL和IMP是紧密相关的。当你调用一个方法时,运行时系统首先将方法名解析为SEL,然后使用这个SEL去查找与之对应的IMP。一旦找到IMP,运行时系统就会调用这个函数指针来执行方法的代码。
消息传递的流程
-
首先,Runtime系统会通过obj的 isa 指针找到其所属的class
-
接着在这个类的缓存中查找与选择器匹配的方法实现
-
如果缓存中没找到接着在这个类的方法列表(method list)中查找与选择器(someMethod)匹配的方法实现(IMP)。
-
如果在当前类中没有找到,Runtime会沿着类的继承链往它的 superclass 中查找,也是先查缓存再查方法列表,直到到达根类(通常为 NSObject)。
-
一旦找到someMethod这个函数,就去执行它的实现IMP 。
-
如果直到根类还是没找到就会进行消息转发流程。
消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
objc_msgSend()的伪代码如下:
id objc_msgSend(id self, SEL _cmd, ...) {//获取当前对象的类对象用于在运行时获取方法实现Class class = object_getClass(self);//根据类对象和选择器_cmd获取IMP指针IMP imp = class_getMethodImplementation(class, _cmd);//判空IMP并返回该方法实现return imp ? imp(self, _cmd, ...) : 0;
}
上面代码之所以是伪代码是因为objc_msgSend 是用汇编语言写的,针对不同架构有不同的实现。汇编语言的效率比c/c++更快,它直接对寄存器进行访问和操作,相比较内存的操作更加底层效率更高。
其源代码比较多,下面是核心部分:
ENTRY _objc_msgSend // 开始_objc_msgSend函数的定义
MESSENGER_START // 标记消息传递的开始NilTest NORMAL // 检查self是否为nil,如果是nil则引发异常GetIsaFast NORMAL // 快速获取self对象的isa指针,isa指向对象的类信息// r11 = self->isaCacheLookup NORMAL // 在缓存中查找IMP,如果找到则直接调用IMP// 这里利用了方法缓存,以提高性能NilTestSupport NORMAL // 如果self是nil的备用处理GetIsaSupport // 如果需要,更全面地获取isa信息// 这是为了支持特殊情况下的isa获取// cache miss: go search the method lists
LCacheMiss: // 缓存中没找到,开始在方法列表中查找IMP// isa仍然在r11寄存器中MethodTableLookup %a1, %a2 // 在方法表中查找与选择器匹配的IMP// r11 = IMP,这里查找方法实现cmp %r11, %r11 // 设置eq标志位,用于非 stret (simple) 的方法转发// 这里比较r11与自身,实际上是设置条件码,用于判断是否需要转发jmp *%r11 // 跳转到IMP地址并执行// 这里直接调用了方法实现END_ENTRY _objc_msgSend // 结束_objc_msgSend函数的定义
MethodTableLookup 宏是重点,负责在缓存没命中时在方法表中负责查找 IMP:
.macro MethodTableLookupMESSENGER_END_SLOWSaveRegisters// _class_lookupMethodAndLoadCache3(receiver, selector, class)movq $0, %a1movq $1, %a2movq %r11, %a3call __class_lookupMethodAndLoadCache3// IMP is now in %raxmovq %rax, %r11RestoreRegisters.endmacro
从上面的代码可以看出方法查找 IMP 的工作交给了 OC 中的 _class_lookupMethodAndLoadCache3 函数,并将 IMP 返回(从 r11 挪到 rax)。最后在 objc_msgSend 中调用 IMP。
消息传递快速查找
-
首先检查消息接收者
receiver是否存在,不存在则不做任何处理。 -
接着通过
receiver的isa指针找到对应的class类对象, -
从
cache中获取buckets,从buckets中对比参数sel,看在缓存里有没有同名方法如果buckets中有对应的SEL则返回它对应的IMP -
如果在缓存中没有找到匹配的方法选择子,则执行慢速查找过程,即调用 _objc_msgSend_uncached 函数,并进一步调用 _lookUpImpOrForward 函数进行全局的方法查找。
简单来说快速查找就是在所属类的缓存中查找SEL对应的IMP指针
buckets是cache中的结构体,bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。
cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。

消息传递慢速查找
当消息发送的快速查找过程无法找到匹配的方法实现时,就会进入 _lookUpImpOrForward 函数根据继承链和协议信息逐级查找方法。
- 从本类的 method list **(二分查找/遍历查找)**查找imp
- 从本类的父类的cache查找imp**(汇编)**
- 从本类的父类的method list **(二分查找/遍历查找)**查找imp …继承链遍历…(父类->…->根父类)里找cache和method list的imp
- 若上面环节有任何一个环节查找到了imp,跳出循环,缓存方法到本类的cache,并返回imp
- 直到查找到nil,指定imp为消息转发,跳出循环,执行动态决议resolveMethod_locked(消息转发的内容)
简单来说慢速查找就是在所属类的方法列表中查找SEL对应的IMP指针,没找到就沿着继承链一直查找方法缓存和方法列表,找到就返回IMP,没找到就执行动态决议
lookUpImpOrForward函数
前面提到的 _class_lookupMethodAndLoadCache3 函数其实就是简单的调用了 lookUpImpOrForward 函数:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
注意 lookUpImpOrForward 调用时使用缓存参数传入为 NO,因为之前快速查找的时候已经尝试过查找缓存了。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) 实现了一套查找 IMP 的标准路径,也就是在消息转发(Forward)之前的逻辑。
下面是lookUpImpOrForward函数源码:
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {const IMP forward_imp = (IMP)_objc_msgForward_impcache; // 定义转发的IMPIMP imp = nil; // 初始化IMP为nilClass curClass;runtimeLock.assertUnlocked(); // 确认运行时锁未被持有// 如果类未初始化,设置LOOKUP_NOCACHE,防止缓存单个条目的情况if (slowpath(!cls->isInitialized())) {behavior |= LOOKUP_NOCACHE;}runtimeLock.lock(); // 锁住运行时锁// 验证类是否是已知的类checkIsKnownClass(cls);// 确保类已实现和初始化cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);curClass = cls; // 当前类for (unsigned attempts = unreasonableClassCount(); ;) {// 如果类的缓存是常量优化缓存// 再一次从cache查找imp// 目的:防止多线程操作时,刚好调用函数,此时缓存进来了if (curClass->cache.isConstantOptimizedCache(true)) {
#if CONFIG_USE_PREOPT_CACHESimp = cache_getImp(curClass, sel); // 从缓存中查找IMPif (imp) goto done_unlock; // 如果找到IMP,结束循环curClass = curClass->cache.preoptFallbackClass();
#endif} else {// 从当前类的方法列表中查找匹配的选择器method_t *meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {imp = meth->imp(false); // 获取IMPgoto done;}// 每次判断都会把curClass的父类赋值给curClassif (slowpath((curClass = curClass->getSuperclass()) == nil)) {imp = forward_imp; // 使用转发IMPbreak;}}// 防止超类链中出现循环if (slowpath(--attempts == 0)) {_objc_fatal("Memory corruption in class list.");}// 在超类缓存中查找IMPimp = cache_getImp(curClass, sel);if (slowpath(imp == forward_imp)) {// 在超类中发现转发条目,停止搜索break;}if (fastpath(imp)) {// 在超类中找到方法,缓存结果goto done;}}// 尝试方法解析器if (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}done:if (fastpath((behavior & LOOKUP_NOCACHE) == )) {
#if CONFIG_USE_PREOPT_CACHESwhile (cls->cache.isConstantOptimizedCache(true)) {cls = cls->cache.preoptFallbackClass();}
#endiflog_and_fill_cache(cls, imp, sel, inst, curClass); // 填充缓存}done_unlock:runtimeLock.unlock(); // 解锁// 如果设置LOOKUP_NIL并且IMP是转发IMP,则返回nilif (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {return nil;}return imp; // 返回找到的IMP
}
总体的流程如下:
首先检查接收者 inst 是否为空,如果为空则直接返回空。
接下来,代码根据接收者的类对象 cls 进行一系列的处理和查找操作,以找到适当的方法实现 imp。包括:
-
检查类对象是否已经初始化,如果尚未初始化则将 LOOKUP_NOCACHE 标志添加到 behavior 中,避免缓存查找。
-
通过 realizeAndInitializeIfNeeded_locked 函数对类对象进行实例化和初始化处理,确保类对象已经准备就绪。
-
使用循环逐级查找方法实现,包括在类的缓存中查找、在类的方法列表中查找、在父类链中查找。如果找到了匹配的方法实现,则跳转到 done 标签处。
-
如果在查找过程中找不到匹配的方法实现,则说明需要进行消息转发。将消息转发的默认实现 forward_imp 赋给 imp。
-
如果设置了 LOOKUP_RESOLVER 标志,说明需要调用方法解析器进行进一步处理,跳转到 resolveMethod_locked 函数进行解析。
-
在查找或转发结束后,如果未设置 LOOKUP_NOCACHE 标志,将找到的方法实现 imp 缓存到类对象的缓存中。
-
代码解锁runtime锁,根据需要返回找到的方法实现
imp或空值
在循环中,首先检查当前类对象的缓存是否是常量优化缓存(isConstantOptimizedCache)。如果是常量优化缓存,代码尝试从缓存中获取方法实现(cache_getImp(curClass, sel))。如果成功获取到方法实现,则跳转到 done_unlock 标签处,结束查找。
如果当前类对象的缓存不是常量优化缓存,代码继续执行。通过调用 getMethodNoSuper_nolock 函数在当前类对象的方法列表中查找方法(meth = getMethodNoSuper_nolock(curClass, sel))。如果找到匹配的方法,则获取对应的方法实现(imp = meth->imp(false)),跳转到 done 标签处,结束查找。
如果在当前类对象的方法列表中没有找到匹配的方法实现,代码继续执行。将当前类对象的父类赋值给 curClass,并判断是否为 nil。如果父类为 nil,说明已经到达了继承链的顶端,没有找到匹配的方法实现。此时将默认的转发实现 forward_imp 赋给 imp,并跳出循环。
在循环的每次迭代中,会将 attempts 的值减一,表示尚未完成的查找次数。如果 attempts 的值减到零,则说明类对象的继承链中存在循环,这是不合理的。此时会触发一个错误,终止程序执行。
如果在当前类对象的缓存中找到了转发的条目(imp == forward_imp),表示在父类的缓存中找到了转发的方法实现。这时会停止循环,但不会将转发的方法实现缓存,而是先调用方法解析器来处理。
最后,在循环结束后,会根据需要将找到的方法实现缓存到类对象的缓存中,然后解锁运行时锁,并根据需要返回找到的方法实现或空值。
动态决议 resolveMethod_locked
当慢速查找依然没有找到IMP时,会进入方法动态解析阶段,源码如下:
// 尝试方法解析器if (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}
这里调用了resolveMethod_locked方法,下面是它的源代码:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{runtimeLock.assertLocked();ASSERT(cls->isRealized());//**加锁**runtimeLock.unlock();//**判断是否是元类**if (! cls->isMetaClass()) {//**不是元类,调用resolveInstanceMethod方法**resolveInstanceMethod(inst, sel, cls);} else {//**是元类,调用resolveClassMethod**resolveClassMethod(inst, sel, cls);//**如果调用上面的方法还没有找到,尝试调用resolveInstanceMethod**//**原因是根据isa的继承链,根元类的父类是NSObject,所以在元类中如果没有找到**//**最后可能会在NSObjct中找到目标方法**if (!lookUpImpOrNilTryCache(inst, sel, cls)) {resolveInstanceMethod(inst, sel, cls);}}//**重新调用lookUpImpOrForwardTryCache方法,返回方法查找流程**//**因为已经进行过一次动态方法决议,下次将不会再进入,所以不会造成死循环**return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
-
首先判断进行解析的是否是元类
-
如果不是元类,则调用
_class_resolveInstanceMethod进行实例方法动态解析 -
如果是元类,则调用
_class_resolveClassMethod进行类方法动态解析 -
b完成类方法动态解析后,再次查询cls中的imp,如果没有找到,则进行一次对象方法动态解析
-
最后执行
lookUpImpOrForwardTryCache函数resolveInstanceMethod和resolveClassMethod也称为方法的动态决议。resolveInstanceMethod方法
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{runtimeLock.assertUnlocked();//**如果目标类没有初始化,直接报错**ASSERT(cls->isRealized());//**创建一个方法名为resolveInstanceMethod的SEL**SEL resolve_sel = @selector(resolveInstanceMethod:);//**判断resolveInstanceMethod是否在目标类中实现**//**如果我们的类是继承自NSObject的话,那么这个判断就永远为false**//**因为在NSObject中已经默认实现了resolveInstanceMethod方法**//**因为是去cls->ISA也就是元类中查找,所以我们可以断定,resolveInstanceMethod是个类方法**if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {// Resolver not implemented.return;}//**强制类型转换objc_msgSend**BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;//**通过objc_msgSend方法调用resolveInstanceMethod**bool resolved = msg(cls, resolve_sel, sel);//**拼接上LOOKUP_NIL参数后,重新调用方法查找流程**//**虽然调用了resolveInstanceMethod,但是这个返回值是bool**//**所以我们要获取对应的imp,还是需要通过方法查找流程**//**如果通过resolveInstanceMethod添加了方法,就缓存在类中**//**没添加,则缓存forward_imp**IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);//**组装相应的信息**if (resolved && PrintResolving) {if (imp) {………}else {………}}
}
首先创建一个方法名为resolveInstanceMethod的SEL对象resolve_sel;
然后判断resolve_sel是否实现,如果继承NSObject则必定已经实现,这里通过cls->ISA可以知道,resolveInstanceMethod是个类方法;
通过objc_ msgSend直接调用resolveInstanceMethod方法,因为是直接对cls发送消息,所以也可以看出resolveInstanceMethods是类方法;
调用lookUpImpOrNilTryCache方法,重新返回到方法查找的流程当中去;
resolveClassMethod方法
static void resolveClassMethod(id inst, SEL sel, Class cls)
{runtimeLock.assertUnlocked();ASSERT(cls->isRealized());ASSERT(cls->isMetaClass());//**判断resolveClassMethod是否实现,NSObject中默认实现**if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {// Resolver not implemented.return;}//**获取目标类**Class nonmeta;{mutex_locker_t lock(runtimeLock);nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);// +initialize path should have realized nonmeta alreadyif (!nonmeta->isRealized()) {_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",nonmeta->nameForLogging(), nonmeta);}}//**强制类型转换objc_msgSend**BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;//**通过objc_msgSend调用类中的resolveClassMethod方法**bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);//**拼接上LOOKUP_NIL参数后,重新调用方法查找流程**//**类方法实际上就是元类对象中的对象方法,所以可以通过方法查找流程在元类中查找**//**如果通过resolveClassMethod添加了,就缓存方法在元类中**//**没添加,则缓存forward_imp**IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);if (resolved && PrintResolving) {if (imp) {……}else {……}}
}
这个方法与resolveInstanceMethod比较类似,如果通过resolveClassMethod方法添加了目标imp,则将其缓存在目标元类中,否则缓存forward_imp。
lookUpImpOrNilTryCache方法
在resolveInstanceMethod和resolveClassMethod中都会调用的lookUpImpOrNilTryCache方法
extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{//**这里behavior没传,所以是默认值0**//**behavior | LOOKUP_NIL = 0 | 4 = 4**return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
给参数behavior拼接上LOOKUP_NIL然后调用_lookUpImpTryCache方法
lookUpImpTryCache方法
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{runtimeLock.assertUnlocked();//**判断类是否初始化**if (slowpath(!cls->isInitialized())) {//**没有初始化直接调用lookUpImpOrForward**//**里面针对没初始化的类,有相关处理**return lookUpImpOrForward(inst, sel, cls, behavior);}//**去缓存中,查找sel是否有对应的imp**IMP imp = cache_getImp(cls, sel);//**找到了则跳去done**if (imp != NULL) goto done;//**没找到继续往下走,去共享缓存中查找**
#if CONFIG_USE_PREOPT_CACHESif (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);}
#endif//**没找到对应的imp,调用lookUpImpOrForward方法查找,behavior = 4**//** 4 & 2 = 0 ,所以这次方法查找不会再次进行动态方法决议**//**将_objc_msgForward_impcache缓存起来,方便下次直接返回**if (slowpath(imp == NULL)) {return lookUpImpOrForward(inst, sel, cls, behavior);}done://**命中缓存中,并且sel对应的imp为_objc_msgForward_impcache**//**说明动态方法决议已经执行过,且没有添加imp,则直接返回空**if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {return nil;}//**说明动态方法决议中添加了对应的imp**return imp;
}
-
首先判断类是否初始化,如果没有初始化则直接调用
lookUpImpOrForward,里面有针对没初始化的类进行相应的处理; -
然后去缓存中进行
方法的快速查找,找到了就去done -
缓存中没找到,如果支持共享缓存,则去共享缓存中查找
-
都没有查找到,则通过慢速方法查找去查找方法,由于
behavior的值发生改变,这次慢速查找不会再次调用动态方法决议 -
在
done流程中,如果已经执行过动态方法决议且并没有添加imp,则缓存中的sel对应imp为_objc_msgForward_impcache,这时直接返回nil。否则返回添加的imp实现。
如果系统在动态决议阶段没有找到实现,就会进入消息转发阶段。

相关文章:
【iOS】——消息传递底层实现
消息传递是什么 Objective-C是一种动态类型语言,这意味着在编译时并不确定对象的具体类型,而是在运行时决定。消息传递机制允许程序在运行时向对象发送消息,对象再决定如何响应这些消息。 当你通过对象调用方法时,例如像这样[ob…...
PostgreSQL数据库从入门到精通系列之十:表空间、索引表空间、创建表空间、创建索引空间、创建分区表、创建分区表的分区、创建指定表空间、索引表空间的分区表
PostgreSQL数据库从入门到精通系列之十:表空间、索引表空间、创建表空间、创建索引空间、创建分区表、创建分区表的分区、创建指定表空间、索引表空间的分区表 一、数据库表空间和数据库之间的关系二、索引表空间和数据库之间的关系三、创建角色四、创建表空间目录五、创建表空…...
恶补,先验分布,后验分布 ,似然估计
恶补,打一遍增加印象 先验分布后验分布,似然估计 声明:仅记录个人学习,并无其他用途。 先验分布 后验分布, 似然估计 隔壁小哥的故事: 隔壁小哥要去15公里外的一个公园里玩,小哥可以选择步行…...
JS之数组中的reduce方法
文章目录 基本语法:callbackFn 的参数:例子1. 数组求和2. 数组求积3. 扁平化数组4. 数组元素计数5. 使用对象解构和展开运算符合并数组中的对象6. 求最大值和最小值 函数组合异步操作中的 reduce总结 reduce 是 JavaScript 中 Array 对象的一个方法,非常…...
在win10上通过WSL和docker安装Ubuntu子系统,并配置Ubuntu可成功使用宿主机GPU
本文主要记录win10系统上,通过WSL的Ubuntu系统以及Docker使用GPU的全部过程。 文章目录 1、 启用hyper-v2、 安装docker3、 安装WSL3.1 安装WSL23.1.1 检查是否安装了WSL23.1.1 安装和配置 WSL 23.2 安装Ubuntu 子系统3.3 检查并修改WSL版本4、docker配置ubuntu20.04 LTS5、下…...
python需要掌握那些语法
1-list数据类型 内置方法查看长度len(list) 2.array数据类型 查看形状 3.tuple 取出元组 t (1, 2, 3, 4, 5) # 取出第一个元素 2first_element t[0] 3print(first_element) # 输出:1 4 5# 取出第三个元素 6third_element t[2] 7pr…...
CentOS Mysql8 数据库安装
添加mysql yum仓库 这里安装的是8.0版本,如需其他版本在此查看mysql版本列表 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm sudo rpm -Uvh mysql80-community-release-el7-3.noarch.rpm安装mysql sudo yum install mysql-server …...
新手教程---python-函数(新添加)
一、递归函数 在Python中,递归函数是指一个函数调用自身的过程。递归函数一般包括两个部分:基本情况和递归情况。 基本情况是指在递归过程中终止递归的条件。如果不满足基本情况,递归函数将进入递归情况,调用自身,并缩…...
Windows tasklist命令详解,Windows查看进程
「作者简介」:冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础著作 《网络安全自学教程》,适合基础薄弱的同学系统化的学习网络安全,用最短的时间掌握最核心的技术。 tasklist 可以…...
数据结构——线性表(循环链表)
一、循环链表定义 将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一 个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)。 循环链表解决了一个很麻烦的问题。如何从当中一 个结点出发&am…...
深度剖析机构号矩阵系统:如何根据业务需求做出明智选择
在数字化营销的浪潮中,短视频平台如抖音、快手等已成为品牌传播和用户互动的重要渠道。为了更高效地管理这些平台的账号,机构号矩阵系统应运而生。本文将深度剖析机构号矩阵系统,并探讨如何根据业务需求做出明智的选择。 机构号矩阵系统概述…...
go语言的基础语法
基础语法 与python、vue等类似,go语言也分常量和变量等,常量用const(不可变)和变量var(可变)定义 常量 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型,值不可更改,表达式只支…...
Modbus转Ethernet/IP网关模块与汇川PLC通讯案例
Modbus转Ethernet/IP网关模块(XD-MDEP100)是一种用于将Modbus协议转换为Ethernet/IP协议的设备。它可以将Modbus RTU和Modbus TCP两种不同格式的Modbus数据包转换为Ethernet/IP协议的数据包,实现不同厂家的设备之间的数据交换和共享。在汇川P…...
【玩转python】入门篇day11-位运算
1、位运算语法 计算机中所有的数据都是以二进制的形式存储在设备中。即 0、1 两种状态,计算机对二进制数据进行的运算(、-、、/)都是叫位运算,相比在代码中直接使用(、-、、/)运算符,合理的运用位运算更能显著提高代码在机器上的执行效率。 …...
【Gitlab】记一次升级 Gitlab 后 API 失效的问题
背景 前段时间,因内部使用的 Gitlab 版本存在漏洞,需要进行升级,于是乎,将 Gitlab 从 16.6.0 升级到 16.11.3。而我们项目有个接口是用于获取 Gitlab 上的开发人员。 然后,今天,突然发现这个接口获取不到…...
2024.7.19 作业
1.链表的排序 int list_sort(NodePtr L) {if(NULLL || L->len<1){printf("排序失败");return -1;}int lenL->len1;NodePtr p;int i,j;for( i1;i<len;i){for( j0,pL;j<len-i;j,pp->next){if( p->data > p->next->data ){datatype tp-&…...
python如何创建SQLite 数据库连接,如何将数据库存储在内存中?
嗨,大家好,我是兰若姐姐。今天给大家说下如何创建SQLite 数据库连接,并将数据库存储在内存中,这是一种临时的、私有的数据存储空间,一般用于以下情形: 什么都不说,先上代码: import sqlite3创建数据库连接…...
机器学习-20-基于交互式web应用框架streamlit的基础使用教程
参考简洁而优雅地展示你的算法和数据——streamlit教程(一) 原理介绍与布局控制 参考Streamlit 讲解专栏(二):搭建第一个应用 Streamlit 讲解专栏(三):两种方案构建多页面 Streamlit 讲解专栏(五):探索强大而灵活的 st.write() 函数 1 streamlit 1.1 运行原理 im…...
基于luckysheet实现在线电子表格和Excel在线预览
概述 本文基于luckysheet实现在线的电子表格,并基于luckyexcel实现excel文件的导入和在线预览。 效果 实现 1. luckysheet介绍 Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。 官方文档在线Demo 2. 实现 …...
【学习笔记】无人机系统(UAS)的连接、识别和跟踪(一)-3GPP TS 23.256 技术规范概述
3GPP TS 23.256 技术规范,主要定义了3GPP系统对无人机(UAV)的连接性、身份识别、跟踪及A2X(Aircraft-to-Everything)服务的支持。 3GPP TS 23.256 技术规范: 以下是文档的核心内容总结: UAV系…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
