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

iOS ------ 消息传递和消息转发

一,消息传递

在OC中,传递消息就是在对象上调用方法

相对于C语言的方法就“静态绑定”的函数,在编译器就决定了运行时所要调用的函数。在OC中,如果向某对象传递消息,就会使用动态绑定机制来决定需要调用那个方法。调用那个方法完全取决于运行期决定,甚至可以在程序运行时改变。编译时并不能确定方法有没有对应的实现,没有写方法的具体实现也不会报错。

给对象发送消息可以这样来写:

id returnValue = [someObject messageName:parameter];

本例中,someObject叫做“接收者”(receiver),messageName叫做“选择子”(selector)。选择子与参数合起来称作“消息”(message)。编译器将其转换为C语言函数调用objc_msgSend

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

这个函数将消息接受者,选择子和参数作为主要参数,其原型如下:

objc_msgSend(receiver, selector)                    // 不带参数
objc_msgSend(receiver, selector, arg1, arg2,...)    // 带参数

消息传递的关键在于objc_class结构体有两个关键的字段:

  • isa 指向父类的指针
  • methodLists 类的方法分发表(dispatch table)

其中对象的isa指针让对象可以访问类和类的继承链。

消息传递的过程:

  • 当消息传递给一个对象时,首先从运行时系统缓存objc_cache中进行查找。如果找到则执行,否则执行下面的步骤
  • objc_msgSend通过isa指针获取类的结构体,然后通过选择子作为“键”在方法分发表methodLists查找应该执行的方法,实际上查找的就是相应方法的IMP函数指针,Dispatch table 是一张SELIMP的对应表。也就是说方法编号SEL最后还要通过Dispatch table表找到对应的IMPIMP是一个函数指针,然后去执行这个方法
  • 如果未找到,通过isa找到父类并在父类的分发表中查找,一直沿着类的继承链找到NSObject类,一旦找到则传入相应的参数来执行方法的具体实现,并将该方法加入到本类的方法缓存objc_cache。如果一直未找到方法的实现那么消息发送阶段结束,进入动态解析阶段,解析到就结束。如果未解析到,则会进入消息转发流程。

在这里插入图片描述

消息传递分为三个阶段:

  • 消息发送阶段
  • 动态解析阶段
  • 消息转发阶段

方法查找的核心函数就是 _class_lookupMethodAndLoadCache3 函数,接下来重点分析 _class_lookupMethodAndLoadCache3 函数内的源码。

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

lookUpImpOrForward 函数

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
{// initialize = YES , cache = NO , resolver = YESIMP imp = nil;bool triedResolver = NO;runtimeLock.assertUnlocked();// 缓存查找, 因为cache传入的为NO, 这里不会进行缓存查找, 因为在汇编语言中CacheLookup已经查找过// Optimistic cache lookupif (cache) {imp = cache_getImp(cls, sel);if (imp) return imp;}// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.// runtimeLock is held during method search to make// method-lookup + cache-fill atomic with respect to method addition.// Otherwise, a category could be added but ignored indefinitely because// the cache was re-filled with the old value after the cache flush on// behalf of the category.runtimeLock.lock();checkIsKnownClass(cls);if (!cls->isRealized()) {realizeClass(cls);}if (initialize  &&  !cls->isInitialized()) {runtimeLock.unlock();_class_initialize (_class_getNonMetaClass(cls, inst));runtimeLock.lock();// If sel == initialize, _class_initialize will send +initialize and // then the messenger will send +initialize again after this // procedure finishes. Of course, if this is not being called // from the messenger then it won't happen. 2778172}retry:    runtimeLock.assertLocked();// Try this class's cache.// 防止动态添加方法,缓存会变化,再次查找缓存。imp = cache_getImp(cls, sel);// 如果查找到imp, 直接调用done, 返回方法地址if (imp) goto done;// 查找方法列表, 传入类对象和方法名// Try this class's method lists.{// 根据sel去类对象里面查找方法Method meth = getMethodNoSuper_nolock(cls, sel);if (meth) {// 如果方法存在,则缓存方法log_and_fill_cache(cls, meth->imp, sel, inst, cls);// 方法缓存之后, 取出imp, 调用done返回impimp = meth->imp;goto done;}}// 如果类方法列表中没有找到, 则去父类的缓存中或方法列表中查找方法// Try superclass caches and method lists.{unsigned attempts = unreasonableClassCount();for (Class curClass = cls->superclass;curClass != nil;curClass = curClass->superclass){// Halt if there is a cycle in the superclass chain.if (--attempts == 0) {_objc_fatal("Memory corruption in class list.");}// 查找父类的缓存// Superclass cache.imp = cache_getImp(curClass, sel);if (imp) {if (imp != (IMP)_objc_msgForward_impcache) {// 在父类中找到方法, 在本类中缓存方法, 注意这里传入的是cls, 将方法缓存在本类缓存列表中, 而非父类中// Found the method in a superclass. Cache it in this class.					log_and_fill_cache(cls, imp, sel, inst, curClass);goto done;}else {// Found a forward:: entry in a superclass.// Stop searching, but don't cache yet; call method // resolver for this class first.break;}}// 查找父类的方法列表// Superclass method list.Method meth = getMethodNoSuper_nolock(curClass, sel);if (meth) {// 同样拿到方法, 在本类进行缓存log_and_fill_cache(cls, meth->imp, sel, inst, curClass);imp = meth->imp;goto done;}}}// ---------------- 消息发送阶段完成 ---------------------// ---------------- 进入动态解析阶段 ---------------------// 上述列表中都没有找到方法实现, 则尝试解析方法// No implementation found. Try method resolver once.if (resolver  &&  !triedResolver) {runtimeLock.unlock();_class_resolveMethod(cls, sel, inst);runtimeLock.lock();// Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead.triedResolver = YES;goto retry;}// ---------------- 动态解析阶段完成 ---------------------// ---------------- 进入消息转发阶段 ---------------------// No implementation found, and method resolver didn't help. // Use forwarding.imp = (IMP)_objc_msgForward_impcache;cache_fill(cls, sel, imp, inst);done:runtimeLock.unlock();return imp;
} 

方法缓存

在进行查找时,OC会运行时会利用缓存机制来提高查找的速度,在方法查找中,他会将最近使用过的方法实现存储在缓存中下次调用相同的方法就可以直接在缓存中获取实现,避免了反复查找的过程。

类缓存(objc_cache)

struct objc_cache {unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;unsigned int occupied                                    OBJC2_UNAVAILABLE;Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};
  • mask: 指定分配的缓存 bucket 的总数。,所以缓存的 size(total)是 mask+1。

  • occupied: 指定实际占用的缓存bucket的总数。

  • buckets: 指向 Method 数据结构指针的数组。

为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在 objc_cache 中,所以在实际运行中,大部分常用的方法都是会被缓存起来的。

SEL和IMP

IMP是OC方法实现代码块的地址,可以通过它像C语言函数一样直接调用方法实现。

typedef id (&IMP)(id,SEL,...);

IMP是一个函数指针,这个被指向的函数包含一个接搜消息的对象id(self指针),调用方法的选择子SEL(方法名),以及不定个数的方法参数,并返回一个id.

SEL是OC中表示方法名的数据类型,在运行时有编译器生成的唯一标识符用于在对象查找并调用相应的方法。

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

OC编译时会根据方法的名字包括参数序列,生成区分这个方法的唯一ID,不管是子类还是父类,只要方法的名字包括参数序列相同,它们的ID就相同。

二,消息转发

当一个对象能够接收一个消息时,会走完正常的消息传递过程。弱无法接受会发生什么呢?

  • 默认情况下,如果以[object message] 的形式调用方法,如果object无法响应message消息时,编译器会报错
  • 如果是以performselector 的形式调用方法,则需要等到运行时才能确定object是否能接受message消息,则程序崩溃

当不确定一个对象是否能接受某个消息时,可以调用respondsToSelector:来进行判断

if ([self respondsToSelector:@selector(method)]) {[self performSelector:@selector(method)];
}

当一个对象无法接受莫哥消息时,就会启动“消息转发”机制。通过学习转发机制可以告诉对象如何处理未知的消息。

消息转发机制分为三个阶段:

  • 动态方法解析
  • 备用接受者
  • 完整消息转发

在这里插入图片描述

1,动态方法解析

// No implementation found. Try method resolver once.
//未找到实现。尝试一次方法解析器if (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}

如果没找到方法则尝试调用resolveMethod_locked动态解析,只会执行一次:

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{lockdebug::assert_locked(&runtimeLock);ASSERT(cls->isRealized());runtimeLock.unlock();//判断是不是元类if (! cls->isMetaClass()) {// try [cls resolveInstanceMethod:sel]resolveInstanceMethod(inst, sel, cls);}else {// try [nonMetaClass resolveClassMethod:sel]// and [cls resolveInstanceMethod:sel]resolveClassMethod(inst, sel, cls);if (!lookUpImpOrNilTryCache(inst, sel, cls)) {resolveInstanceMethod(inst, sel, cls);}}// chances are that calling the resolver have populated the cache// so attempt using itreturn lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

主要用的的方法如下

// 类方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起,可以在此添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//其中参数sel为未处理的方法

上述代码的大致流程:

  • 先检查进行解析的是否是元类
  • 如果不是元类,则调用resolveInstanceMethod:进行对象方法动态调用
  • 如果是元类,则调用resolveClassMethod:进行类方法动态解析,完成类方法动态解析后,再次查询cls中的imp,如果没有找到,则进行一次对象方法动态解析

两个方法resolveInstanceMethodresolveClassMethod则称为方法的动态决议。

执行完上述代码后返回lookUpImpOrForwardTryCache

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{return _lookUpImpTryCache(inst, sel, cls, behavior);
}

这个方法调用的是_lookUpImpTryCache方法:

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{lockdebug::assert_unlocked(&runtimeLock);if (slowpath(!cls->isInitialized())) {// see comment in lookUpImpOrForwardreturn lookUpImpOrForward(inst, sel, cls, behavior);}IMP imp = cache_getImp(cls, sel);if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHESif (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);}
#endifif (slowpath(imp == NULL)) {return lookUpImpOrForward(inst, sel, cls, behavior);}done:if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {return nil;}return imp;
}

可以看到这里有cache_getImp;也就是说在进行一次动态决议之后,还会通过cache_getImp从cache里找一遍方法的sel。

#endifif (slowpath(imp == NULL)) {return lookUpImpOrForward(inst, sel, cls, behavior);}

如果还是没找到(imp == NULL),也就是无法通过动态添加方法的话,还会执行一次lookUpImpOrForward,这时候进lookUpImpOrForward方法,这里behavior传的值会发生变化。

第二次进入lookUpImpOrForward方法后,执行到if (slowpath(behavior & LOOKUP_RESOLVER))这个判断时

// 这里就是消息转发机制第一层的入口if (slowpath(behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}

根据变化后的behavior值和LOOKUP_RESOLVER值之间的关系导致该if语句内部只能进入第一次。解释了为什么开头说的该动态解析resolveMethod_locked为什么只执行一次次

具体实现

+(BOOL)resolveInstanceMethod:(SEL)sel {if ([NSStringFromSelector(sel) isEqualToString:@"instanceMethodTest:"]) {Method method = class_getInstanceMethod([self class], @selector(addDynamicInstanceMethod:));IMP methodIMP = method_getImplementation(method);const char * types = method_getTypeEncoding(method);class_addMethod([self class], sel, methodIMP, types);return YES;}return [super resolveInstanceMethod:sel];
}+(BOOL)resolveClassMethod:(SEL)sel {if ([NSStringFromSelector(sel) isEqualToString:@"classMethodTest:"]) {// 类方法都是存在元类中,所以添加方法需要往元类上添加Class metaClass = object_getClass([self class]);Method method = class_getClassMethod([self class], @selector(addDynamicClassMethod:));IMP methodIMP = method_getImplementation(method);const char * types = method_getTypeEncoding(method);class_addMethod(metaClass, sel, methodIMP, types);return YES;}return [super resolveClassMethod:sel];
}-(void)addDynamicInstanceMethod:(NSString *)value {NSLog(@"addDynamicInstanceMethod value = %@",value);
}+(void)addDynamicClassMethod:(NSString *)value {NSLog(@"addDynamicClassMethod value = %@",value);
}

2,备用接受者(快速转发)

当cache中没有找到imp,猎类的继承链里的方法列表都没有找到imp,并且resolve InstanceMethod / resolveClassMethod返回NO就进入快速消息转发。也就是本类没有能力去处理这个消息,那么就交给其他类去处理。

done:if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {return nil;}return imp;

从imp == (IMP)_objc_msgForward_impcache进入消息转发机制。
查看一下这个方法:
竟然是汇编实现的这就又印证了汇编速度更快的结论

	STATIC_ENTRY __objc_msgForward_impcache// No stret specialization.b	__objc_msgForwardEND_ENTRY __objc_msgForward_impcacheENTRY __objc_msgForwardadrp	x17, __objc_forward_handler@PAGEldr	p17, [x17, __objc_forward_handler@PAGEOFF]TailCallFunctionPointer x17END_ENTRY __objc_msgForward

具体实现

-(id)forwardingTargetForSelector:(SEL)aSelector {if ([NSStringFromSelector(aSelector) isEqualToString:@"instanceMethodTestFastForwarding:"]) {SubFromView * subFromView = [[SubFromView alloc]init];if ([subFromView respondsToSelector:aSelector]) {return  subFromView;}}return [super forwardingTargetForSelector:aSelector];
}+(id)forwardingTargetForSelector:(SEL)aSelector {if ([NSStringFromSelector(aSelector) isEqualToString:@"classMethodTestFastForwarding:"]) {if ([SubFromView respondsToSelector:aSelector]) {return  [SubFromView class];}}return [super forwardingTargetForSelector:aSelector];
}

我们在新建的SubFromView完成相应方法的实现,然后就将消息最终转发给了su bFromview实现。

3,完整消息转发(慢速转发)

//封装方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {NSString *method = NSStringFromSelector(aSelector);if ([method isEqualToString:@"sendMessage:"]) {//把这个方法存起来return [NSMethodSignature signatureWithObjCTypes:"v@:@"];}return [super methodSignatureForSelector:aSelector];}- (void)forwardInvocation:(NSInvocation *)anInvocation {//获得方法编号SEL sel = [anInvocation selector];//还来找备胎SpareWheel *sp = [SpareWheel new];//判断能否响应方法if ([sp respondsToSelector:sel]) {anInvocation.target = sp;}else {[super forwardInvocation:anInvocation];}
}

慢速转发需要同时实现methodSignatureForSelector和forwardInvocation两个函数,相当于是重新给该消息进行签名,然后调用forwardInvocation转发。[NSMethodSignature signatureWithObjCTypes:“v@😡”];,这里的"v@😡"是苹果官方的类型定义,

快速转发和慢速转发都会将消息转发给别的对象,它们的区别是什么?

  • 慢速转发可以转发给多个对象,而快速转发最多只能转发一个
  • 快速转发需要实现forwardingTargetForSelector这个方法,但是慢速必须同时实现methodSignatureForSelectorforwardInvocation方法。
  • 块速转发必须指定转发对象或者进行快速转发,而慢速转发作为最终步骤,可以不指定转发对象,也可以控制是否调用doesNotRecognizeSelector来控制抛异常。所以慢速转发可以避免闪退,如果最终没有可转发的对象,可以进行错误提示,提高用户体验。

总结:

  • 动态方法解析不处理,会进入消息转发流程

  • 消息转发流程有快速转发和慢速转发

  • 如果消息转发阶段,快速转发和慢速转发不处理,就进入doesNotRecognizeSelector默认抛出异常信息

在这里插入图片描述

相关文章:

iOS ------ 消息传递和消息转发

一,消息传递 在OC中,传递消息就是在对象上调用方法。 相对于C语言的方法就“静态绑定”的函数,在编译器就决定了运行时所要调用的函数。在OC中,如果向某对象传递消息,就会使用动态绑定机制来决定需要调用那个方法。调…...

计算机视觉之Vision Transformer图像分类

Vision Transformer(ViT)简介 自注意结构模型的发展,特别是Transformer模型的出现,极大推动了自然语言处理模型的发展。Transformers的计算效率和可扩展性使其能够训练具有超过100B参数的规模空前的模型。ViT是自然语言处理和计算…...

【深度学习】BeautyGAN: 美妆,化妆,人脸美妆

https://www.sysu-hcp.net/userfiles/files/2021/03/01/3327b564380f20c9.pdf 【深度学习】BeautyGAN: Instance-level Facial Makeup Transfer with Deep Generative Adversarial Network BeautyGAN: Instance-level Facial Makeup Transfer with Deep Generative Adversaria…...

RocketMQ~架构与工作流程了解

简介 RocketMQ 具有高性能、高可靠、高实时、分布式 的特点。它是一个采用 Java 语言开发的分布式的消息系统,由阿里巴巴团队开发,在 2016 年底贡献给 Apache,成为了 Apache 的一个顶级项目。 在阿里内部,RocketMQ 很好地服务了集…...

学习Python的IDE功能--(一)入门导览

项目视图是主要工具窗口之一。它包含项目目录、SDK 特定的外部库和临时文件。点击带条纹的按钮可以预览演示项目。您也可以按Alt1打开。点击以打开项目视图,展开项目目录以查看项目文件。双击以打开welcome.py。 切换到"学习"工具窗口继续学习本课次。…...

gdb调试多线程程序

目录 1、pstack查看各个线程的调用堆栈2、gdb调试多线程2.1 查看线程信息2.2 切换线程2.3 进入线程某层具体的调用堆栈2.4 调度器锁2.4.1 查看调度器锁模式 3、实战3.1 调试多线程崩溃3.2 调试多线程死锁 1、pstack查看各个线程的调用堆栈 命令: 1、查看进程id ps …...

实战GraphRAG(一):初步体验GraphRAG及其与RAG的对比

🌟实战GraphRAG(一):初步体验GraphRAG及其与RAG的对比 文章目录 🌟实战GraphRAG(一):初步体验GraphRAG及其与RAG的对比📖引言🔍一、GraphRAG与RAG的区别🚀二、GraphRAG使用示例1.安装GraphRAG2.运行索引器3.配置4.自动优化提示词5.运行索引管道6.使用查询引擎7…...

37、PHP 实现一个链表中包含环,请找出该链表的环的入口结点

题目&#xff1a; 题目描述 PHP 实现一个链表中包含环&#xff0c;请找出该链表的环的入口结点。 描述&#xff1a; 一个链表中包含环&#xff0c;请找出该链表的环的入口结点。 <?php /*class ListNode{var $val;var $next NULL;function __construct($x){$this->v…...

LIMS系统对实验室管理有哪些帮助?

LIMS系统对实验室管理提供了多方面的帮助&#xff0c;具体体现在以下几个方面&#xff1a; 1. 流程标准化与自动化 LIMS系统通过定义标准化的工作流程&#xff0c;如样品接收、测试分配、数据录入、结果审核和报告生成等&#xff0c;实现了实验室工作流程的自动化。这减少了人…...

在GPU上运行PyTorch

文章目录 1、查看GPU的CUDA版本2、下载CUDA版本3、安装cuDNN4、配置CUDA环境变量5、安装配置Anaconda6、使用Anaconda7、pycharm导入虚拟环境8、安装带GPU的PyTorch⭐9、总结 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#x…...

【内网穿透】打洞笔记

文章目录 前言原理阐述公网sshfrp转发服务 实现前提第一步&#xff1a;第二步第三步第四步 补充第五步&#xff08;希望隧道一直开着&#xff09;sftp传数据&#xff08;嫌云服务器上的网太慢&#xff09; 前言 租了一个云服务器&#xff0c;想用vscode的ssh远程连接&#xff…...

第59期|GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…...

算法2--贪心算法

1.老鼠和猫的交易 小老鼠准备了M磅的猫粮&#xff0c;准备去和看守仓库的猫做交易&#xff0c;因为仓库里有小老鼠喜欢吃的五香豆。 仓库有N个房间&#xff1b; 第i个房间有 J[i] 磅的五香豆&#xff0c;并且需要用 F[i] 磅的猫粮去交换&#xff1b; 老鼠不必交换该房间所有的五…...

本地部署 EVE: Unveiling Encoder-Free Vision-Language Models

本地部署 EVE: Unveiling Encoder-Free Vision-Language Models 0. 引言1. 快速开始2. 运行 Demo 0. 引言 EVE (Encoder-free Vision-language model) 是一种创新的多模态 AI 模型&#xff0c;主要特点是去除了传统视觉语言模型中的视觉编码器。 核心创新 架构创新&#xff…...

阿里云CDN- https(设计支付宝春节开奖业务)

HTTP相关概念 1. HTTP概述 http是最广泛的网络协议&#xff0c;是客户端与服务器之间的请求与应答的标准&#xff08;TCP&#xff09;&#xff0c;用于www服务器传输超文本到本地浏览器的传输协议&#xff0c;使浏览器更加高效&#xff0c;网络传输减少。 2.HTTPS概述 http…...

为何众多卖家选择加入亚马逊VC平台?他们的决策依据是什么?

众多卖家选择加入亚马逊VC平台&#xff0c;其背后蕴含着深思熟虑的决策逻辑。亚马逊VC平台作为一个专门为品牌供应商打造的销售平台&#xff0c;具有一系列独特且引人注目的优势。 首先&#xff0c;VC平台为卖家提供了品牌控制力的增强。在这个平台上&#xff0c;卖家能够更直接…...

Windows与Linux双机热备软件推荐

网络数据安全在如今信息化的时代越来越变得举足轻重&#xff0c;因此服务器维护和管理也成为企业健康稳定运营的一项重要工作。但实际情况是很多公司并没有配备专业的运维人员&#xff0c;一般都会通过一些管理软件维护或者主机托管给服务商。整理6款服务器的Windows与Linux双机…...

Mysql基础与安装

一、数据库的概念和相关的语法和规范 1、数据库的概念 数据库&#xff1a;组织&#xff0c;存储&#xff0c;管理数据的仓库。 数据库的管理系统&#xff08;DBMS&#xff09;&#xff1a;实现对数据有效组织&#xff0c;管理和存取的系统软件。 数据库的种类&#xff1a; m…...

线程的死锁和并发安全

在多线程编程中&#xff0c;线程的死锁和并发安全是两个重要的概念。理解这两个概念并正确地管理它们&#xff0c;对于编写高效且可靠的并发程序至关重要。 线程的死锁 死锁&#xff08;Deadlock&#xff09; 是指两个或多个线程相互等待对方释放已经持有的资源&#xff0c;导…...

docker 启动提示can not create sys fs cgroup cpuset....问题处理

docker 启动失败 报错 大概报错内容为 cgroup :no such file can not create /sys/fs/cgroup/cpuset … 问题是因为 /sys/fs/cgroup/ 没有被正确挂载 cgroup 是实现资源限制的工具 docker 能够进行限制cpu 内存 大小 依赖cgroup ll /sys/fs/cgroup/ 发现一个都系也没有 m…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...

xmind转换为markdown

文章目录 解锁思维导图新姿势&#xff1a;将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件&#xff08;ZIP处理&#xff09;2.解析JSON数据结构3&#xff1a;递归转换树形结构4&#xff1a;Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!

今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等&#xff0c;设置经线、纬线都以10间隔显示。 2、需要插入背会归线&#xf…...