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

【iOS】KVO

文章目录

  • 前言
  • 一、KVO使用
    • 1.基本使用
    • 2.context使用
    • 3.移除KVO通知的必要性
    • 4.KVO观察可变数组
  • 二、代码调试探索
    • 1.KVO对属性观察
    • 2.中间类
    • 3.中间类的方法
    • 3.dealloc中移除观察者后,isa指向是谁,以及中间类是否会销毁?
    • 总结
  • 三、KVO本质
    • GNUStep窥探KVO源码
      • 重写setter方法
      • 重写class方法
      • 重写delloc方法
      • 重写KVC方法
      • 成员变量使用KVC触发KVO
  • 总结


前言

KVO的全称 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

KVO是一种机制,它允许将其他对象的指定属性的更改通知给对象

在iOS官方文档中有这么一句话:
理解KVO之前,必须先理解KVC(即KVO是基于KVC基础之上)

In order to understand key-value observing, you must first understand key-value coding.
KVC是键值编码,在对象创建完成后,可以动态的给对象属性赋值,而KVO是键值观察,提供了一种监听机制,当指定的对象的属性被修改后,则对象会收到通知,所以可以看出KVO是基于KVC的基础上对属性动态变化的监听

我们知道NSNotificatioCenter也是一种监听方式,那么KVONSNotificatioCenter有什么区别呢?

  • 相同点:
    1、两者的实现原理都是观察者模式,都是用于监听

2、都能实现一对多的操作

  • 不同点:
    1、KVO监听对象属性的变化,同时只能通过NSString来查找属性名,较容易出错

2、NSNotification的发送监听(post)的操作我们可以控制,kvo由系统控制。

3、KVO可以记录新旧值变化

一、KVO使用

1.基本使用

KVO的基本使用分为三步

  • 注册观察addObserver:forKeyPath:options:context
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
  • 实现KVO回调observeValueForKeyPath:ofObject:change:context
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{if ([keyPath isEqualToString:@"name"]) {NSLog(@"%@",change);}
}
  • 移除观察者removeObserver:forKeyPath:context
[self.person removeObserver:self forKeyPath:@"nick" context:NULL];

2.context使用

我们注意到这些方法中都有参数context,我们来讲解一下

context 参数的主要作用是为 KVO 回调提供一个标识符或标记,这有助于区分同一属性上的不同观察者或在多个地方注册的同一个观察者

在官方文档中,针对参数context有如下说明:
在这里插入图片描述
通俗的讲,context上下文主要是用于区分不同对象的同名属性,从而在KVO回调方法中避免使用字符串进行区分,而是直接使用context进行区分,可以大大提升性能,以及代码的可读性

因此我们可以知道,context常用于标识,从而区分
不同对象的同名属性

context使用总结

  • 不使用context,使用keyPath区分通知来源
//context的类型是 nullable void *,应该是NULL,而不是nil
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:NULL];
  • 使用context区分通知来源
//定义context
static void *PersonNickContext = &PersonNickContext;
static void *PersonNameContext = &PersonNameContext;//注册观察者
[self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:PersonNickContext];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];//KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{if (context == PersonNickContext) {NSLog(@"%@",change);}else if (context == PersonNameContext){NSLog(@"%@",change);}
}

3.移除KVO通知的必要性

首先我们需要理解一下观察者与被观察者,例如下面这段代码:

[self.person addObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOldcontext:nil];

观察者将观察 Person 类的 name 属性的变化。在这个例子中,我们将使用 ViewController 作为观察者

在官方文档中,针对KVO的移除有以下几点说明
在这里插入图片描述

删除观察者时,请记住以下几点:

  • 要求被移除为观察者(如果尚未注册为观察者)会导致NSRangeException。您可以对removeObserver:forKeyPath:context:进行一次调用,以对应对addObserver:forKeyPath:options:context:的调用,或者,如果在您的应用中不可行,则将removeObserver:forKeyPath:context:调用在try / catch块内处理潜在的异常。

  • 释放后,观察者不会自动将其自身移除。被观察对象继续发送通知,而忽略了观察者的状态。但是,与发送到已释放对象的任何其他消息一样,更改通知会触发内存访问异常。因此,您可以确保观察者在从内存中消失之前将自己删除。

  • 该协议无法询问对象是观察者还是被观察者。构造代码以避免发布相关的错误。一种典型的模式是在观察者初始化期间(例如,在init或viewDidLoad中)注册为观察者,并在释放过程中(通常在dealloc中)注销,以确保成对和有序地添加和删除消息,并确保观察者在注册之前被取消注册,从内存中释放出来。

KVO注册观察者 和移除观察者是需要成对出现的,如果只注册,不移除,会出现类似野指针的崩溃,如下图所示
在这里插入图片描述

崩溃的原因是,由于第一次注册KVO观察者后没有移除,再次进入界面,会导致第二次注册KVO观察者,导致KVO观察的重复注册,而且第一次的通知对象还在内存中,没有进行释放,此时接收到属性值变化的通知,会出现找不到原有的通知对象,只能找到现有的通知对象,即第二次KVO注册的观察者,所以导致了类似野指针的崩溃,即一直保持着一个野通知,且一直在监听

其实简单来讲就是可能当我们推出视图控制器时,视图控制器已经被销毁,同时我们的观察者是视图控制器,但是我们的视图控制器仍然是观察者,并没有被移除,因此当我们后续继续通过被观察者通知观察者时,就会出现观察者时已经被销毁的视图控制器,从而出现访问野指针的情况导致崩溃

4.KVO观察可变数组

KVO是基于KVC基础之上的,所以可变数组如果直接添加数据,是不会调用setter方法的,所有对可变数组的KVO观察下面这种方式不生效的,即直接通过[self.person.dateArray addObject:@“1”];向数组添加元素,是不会触发kvo通知回调的

在KVC官方文档中,针对可变数组的集合类型,有如下说明,即访问集合对象需要需要通过mutableArrayValueForKey方法,这样才能将元素添加到可变数组

    [_t addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];[_t.array addObject:@1];

这样不会出发通知,即使数组元素改变

我们应该使用mutableArrayValueForKey方法

    [_t addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew | [[self.t mutableArrayValueForKey:@"array"] addObject:@"1"];

二、代码调试探索

1.KVO对属性观察

现在有一个属性与成员变量,分别注册KVO并且直接修改他们的值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
发现只有age属性发生了变化
在这里插入图片描述

结论:
KVO只观察属性,不直接观察成员变量,这是因为setter方法的原因,但是使用KVC修改成员变量可以触发KVO

KVO 通常只能观察通过属性的 setter 方法修改的属性。这是因为当您为某个属性添加观察者时,Objective-C
运行时会动态创建该属性的一个特殊子类,并在这个子类中重写 setter 方法来插入属性变化通知的代码。由于直接修改成员变量不会触发
setter 方法
,因此不会产生 KVO 通知。

2.中间类

我们刚才提到了在运行时会创建一个中间类,接下来我们讲解一下这个中间类

根据官方文档所述,在注册KVO观察者后,观察对象的isa指针指向会发生改变

在注册观察者前后,对象的isa指针发生了变化
在这里插入图片描述

综上所述,在注册观察者后,实例对象的isa指针指向由kunkun类变为了NSKVONotifying_kunkun中间类,即实例对象的isa指针指向发生了变化
在这里插入图片描述

3.中间类的方法

既然生成了一个中间类,那么我们来查看一下这个中间类中有什么方法

#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{unsigned int count = 0;Method *methodList = class_copyMethodList(cls, &count);for (int i = 0; i<count; i++) {Method method = methodList[i];SEL sel = method_getName(method);IMP imp = class_getMethodImplementation(cls, sel);NSLog(@"%@-%p",NSStringFromSelector(sel),imp);}free(methodList);
}//********调用********
[self printClassAllMethod:objc_getClass("NSKVONotifying_kunkun")];

输出:
在这里插入图片描述

那么我们的父类也有一个setAge方法,那么这里的这个方法是继承还是重写呢?
我们接下来打印父类的方法列表看一下
在这里插入图片描述
从这里说明继承的方法不会在子类中显示,所以NSKVONotifying_kunkun重写了set方法

综上所述,有如下结论:

  • NSKVONotifying_kunkun中间类重写了父类kunkunsetAge方法
  • NSKVONotifying_kunkun中间类重写了基类NSObjectclass 、 dealloc 、 _isKVOA方法
    其中dealloc是释放方法
    _isKVOA判断当前是否是kvo

我们这里再来设计一个函数来验证中间类与类的关系

创建一个函数来遍历所有已注册的类,并检查它们是否是指定类的子类。

void PrintSubclassesOfClass(Class parentClass) {int numClasses = objc_getClassList(NULL, 0);Class *classes = NULL;classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);numClasses = objc_getClassList(classes, numClasses);for (int i = 0; i < numClasses; i++) {Class cls = classes[i];Class superClass = class_getSuperclass(cls);while (superClass) {if (superClass == parentClass) {NSLog(@"%@ is a subclass of %@", NSStringFromClass(cls), NSStringFromClass(parentClass));break;}superClass = class_getSuperclass(superClass);}}free(classes);
}// 调用PrintSubclassesOfClass([_t class]);

在这里插入图片描述

由此发现中间类是类的子类,用到了isa swizzling技术

3.dealloc中移除观察者后,isa指向是谁,以及中间类是否会销毁?

    [_t addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];[_t removeObserver:self forKeyPath:@"age"];

这两段代码执行后分别打印其isa指向
在这里插入图片描述
由此可见移除观察者后isa又变回了原来的指向

同时我们再次调用子类查找函数

    [_t addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];[_t removeObserver:self forKeyPath:@"age"];PrintSubclassesOfClass([_t class]);

输出:
在这里插入图片描述
说明中间类仍然存在没有被销毁

这里可能是考虑到重用的技术,后面再次注册观察者就不用重复生成中间类

总结

综上所述,关于中间类,有如下说明:

  • 实例对象isa的指向在注册KVO观察者之后,由原有类更改为指向中间类
  • 中间类重写了观察属性的setter方法、class、dealloc、_isKVOA方法
  • dealloc方法中,移除KVO观察者之后,实例对象isa指向由中间类更改为原有类
  • 中间类从创建后,就一直存在内存中,不会被销毁

由此我们可以得到如下关系图
在这里插入图片描述

三、KVO本质

在前面铺垫了那么多,我们现在来讲讲KVO的实现流程

KVO的本质是改变setter方法的调用

首先我们知道了中间类重写了setter方法,我们来打印一下重写后的方法的IMP,也就是方法实际上会调用哪一个函数
在这里插入图片描述
当修改instance对象的属性时,会调用Foundation_NSSetXXXValueAndNotify函数

Foundation框架中还有很多例如_NSSetBoolValueAndNotify、_NSSetCharValueAndNotify、_NSSetFloatValueAndNotify、_NSSetLongValueAndNotify等等函数。
在这里插入图片描述

GNUStep窥探KVO源码

由于KVO的实现没有开源,因此我们无法查看KVO的源码

GNUStep是一个成熟的框架,适用于高级GUI桌面应用程序和服务器应用程序,它将Cocoa Objective-C软件库,以自由软件方式重新实现,能够运行在Linux和windows操作系统上。

GNUStepFoundation与apple的API相同,虽然具体实现可能不一样,但仍旧有借鉴意义。

重写setter方法

GNUStep有一个模板类叫做GSKVOSetter,针对不同的数据类型,都有一个不同的setter方法实现,列举其中一个方法:

- (void) setterChar: (unsigned char)val
{NSString  *key; // 定义一个用来存储属性名称的字符串Class     c = [self class]; // 获取当前对象的类// 定义一个函数指针,用来存储原始的 setter 方法的实现void      (*imp)(id,SEL,unsigned char);// 通过类和当前方法的选择器(_cmd),获取这个方法的原始实现,并转换为适当的函数指针类型imp = (void (*)(id,SEL,unsigned char))[c instanceMethodForSelector: _cmd];// 通过 _cmd 选择器获取与之关联的属性名,通常通过移除 set 前缀和小写化首字母实现key = newKey(_cmd); // 这个 newKey 函数的实现没有给出,假设它能从 setter 名生成属性名// 检查这个类是否为 key 提供自动 KVO 通知// 这个检查是由 automaticallyNotifiesObserversForKey: 方法进行,该方法默认返回 YESif ([c automaticallyNotifiesObserversForKey: key] == YES) // 通常总是返回 YES,除非在子类中被重写{[self willChangeValueForKey: key]; // 在改变值之前手动通知 KVO 系统属性即将变更(*imp)(self, _cmd, val); // 调用原始的 setter 方法实现来更新属性值[self didChangeValueForKey: key]; // 在改变值之后手动通知 KVO 系统属性已经变更}else{// 如果类表示不自动通知,则直接调用原始实现,不发送 KVO 通知(*imp)(self, _cmd, val);}RELEASE(key); // 释放之前为 key 分配的内存(这个假设 key 是动态分配的,但代码中没有显示这部分)
}

由此我们可以知道重写后的setter方法的主要步骤

      [self willChangeValueForKey: key]; // 在改变值之前手动通知 KVO 系统属性即将变更(*imp)(self, _cmd, val); // 调用原始的 setter 方法实现来更新属性值[self didChangeValueForKey: key]; // 在改变值之后手动通知 KVO 系统属性已经变更
  • 先调用willChangeValueForKey方法,
  • 再调用父类原来的setter方法
  • 最后调用didChangeValueForKey,其内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:);

我们用代码来验证一下调用顺序

- (void)setAge:(int)age {_age = age; // 直接赋值操作,确保使用下划线来访问实例变量,避免递归调用setterNSLog(@"调用成功:已将 age 设置为 %d", _age); // 打印信息
}
- (void)willChangeValueForKey:(NSString *)key {NSLog(@"willChangeValueForKey--begin");[super willChangeValueForKey:key];NSLog(@"willChangeValueForKey--end");
}- (void)didChangeValueForKey:(NSString *)key {NSLog(@"didChangeValueForKey--begin");[super didChangeValueForKey:key];NSLog(@"didChangeValueForKey--end");
}

在这里插入图片描述
符合我们上面所说的流程,同时在didChangeValueForKey方法中我们调用了observeValueForKeyPath:ofObject:change:context:,由此我们可以推测一下observeValueForKeyPath:ofObject:change:context:的实现代码
在这里插入图片描述

重写class方法

由于我们不想中间类暴露给用户,因此我们的程序同时重写了中间类的class方法

- (Class) class
{return class_getSuperclass(object_getClass(self));
}

由此我们class方法返回的就是原来的实例对象所属的类,而非中间类

重写delloc方法

- (void) dealloc
{// Turn off KVO for self ... then call the real dealloc implementation.[self setObservationInfo: nil];object_setClass(self, [self class]);[self dealloc];GSNOSUPERDEALLOC;
}

- (void) dealloc对象释放后,移除KVO数据,将对象重新指向原始类

重写KVC方法

- (void) setValue: (id)anObject forKey: (NSString*)aKey这是KVC中的方法,但是在GNUStep中也重写了这个方法

- (void) setValue: (id)anObject forKey: (NSString*)aKey
{Class     c = [self class];void      (*imp)(id,SEL,id,id);imp = (void (*)(id,SEL,id,id))[c instanceMethodForSelector: _cmd];if ([[self class] automaticallyNotifiesObserversForKey: aKey]){[self willChangeValueForKey: aKey];imp(self,_cmd,anObject,aKey);[self didChangeValueForKey: aKey];}else{imp(self,_cmd,anObject,aKey);}
}

这与我们上面讲到的重写后的setter方法类似,实现在原始类KVC调用前后添加[self willChangeValueForKey: aKey][self didChangeValueForKey: aKey],而这两个方法是触发KVO通知的关键。
所以说KVO是基于KVC的,而KVC正是KVO触发的入口

成员变量使用KVC触发KVO

由此如果我们直接修改成员变量不会触发KVO,但是如果通过KVC修改成员变量就会触发KVO

在这里插入图片描述

[_t setValue:@5 forKey:@"height"];NSLog(@"@%d", _t->height);

在这里插入图片描述

总结

  • KVC是KVO的入口,网上许多人说成员变量无法被KVO观察,其实是可以的,只是需要调用KVC,但是面试时一般都会说KVO只能用来观察属性
  • KVO的实现主要就是通过isa swizzling技术交换isa指针,在运行时生成中间类,在中间类中重写setter方法从而通知触发KVO监听函数。
  • 重写后的setter方法调用顺序主要为willChangeValueForKey->setter方法->didChangeValueForKey
  • 同时移除观察者后中间类会一直存在等待重用
  • 参考博客
    iOS底层原理总结 - 探寻KVO本质
    KVO源码浅析
    iOS-底层原理 23:KVO 底层原理

相关文章:

【iOS】KVO

文章目录 前言一、KVO使用1.基本使用2.context使用3.移除KVO通知的必要性4.KVO观察可变数组 二、代码调试探索1.KVO对属性观察2.中间类3.中间类的方法3.dealloc中移除观察者后&#xff0c;isa指向是谁&#xff0c;以及中间类是否会销毁&#xff1f;总结 三、KVO本质GNUStep窥探…...

python json字符串怎么用format方法填充参数值报KeyError

python json字符串怎么用format方法填充参数值报KeyError 需求问题分析解决方案 需求 因为python中的字典和json中的一些变量有差异&#xff0c;比如&#xff1a;json中有null、true&#xff0c;在python中就不会被识别&#xff0c;只能转换成字符串&#xff0c;在通过loads()…...

C++新手村指南:入门基础

目录 C概念 C发展史 C关键字&#xff08;C98&#xff09; 命名空间 命名空间的定义 命名空间的使用 C中的输入&&输出 缺省参数 缺省参数的概念 缺省参数的分类 函数重载 函数重载概念 函数重载实现 引用 引用的概念 引用的特性 常引用 引用的使用场景…...

智慧旅游推动旅游服务智慧化转型:借助智能科技的力量,实现旅游资源的精准匹配和高效利用,为游客提供更加便捷、舒适的旅游环境

目录 一、引言 二、智慧旅游的定义与特点 &#xff08;一&#xff09;智慧旅游的定义 &#xff08;二&#xff09;智慧旅游的特点 三、智能科技在旅游服务中的应用 &#xff08;一&#xff09;大数据分析助力旅游决策 &#xff08;二&#xff09;人工智能实现个性化推荐…...

Hikyuu-PF-银行股轮动交易策略实现

今天&#xff0c;带来的是“如何使用 Hikyuu 中的投资组合来实现银行股轮动交易策略”。 这个策略的逻辑很简单&#xff1a;持续持有两支市净率最低银行股&#xff0c;然后每月换仓 定义回测周期与回测标的 同样&#xff0c;首先定义回测周期&#xff1a; # 定义回测日期 …...

【氮化镓】GaN功率器件在转换器设计中的挑战

I. 引言(INTRODUCTION) 宽带隙(WBG)器件的重要性: 引言部分首先强调了宽带隙(WBG)器件在高频、高效率电力电子技术中的关键作用。这些器件,包括碳化硅(SiC)和氮化镓(GaN),相较于传统的硅功率器件,具有显著的优势。宽带隙半导体材料的高击穿场强允许设计更薄的漂…...

DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2,1306310-00-8,是一种重要的多肽化合物

一、试剂信息 名称&#xff1a;DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2CAS号&#xff1a;1306310-00-8结构式&#xff1a; 二、试剂内容 DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2是一种重要的多肽化合物&#xff0c;其CAS号为1306310-00-8。该多肽包含一个DO…...

CopyClip for Mac - 高效复制粘贴,轻松管理剪贴板

CopyClip for Mac&#xff0c;一款专为Mac用户打造的剪贴板管理工具&#xff0c;让你在复制粘贴的日常任务中&#xff0c;享受到前所未有的高效与便捷。 它常驻在菜单栏中&#xff0c;时刻准备为你服务。一旦你复制了内容&#xff0c;CopyClip就会自动将其保存至历史记录中&…...

[windows系统安装/重装系统][step-1]U盘启动盘制作,微软官方纯净系统镜像下载

前言 U盘至少8GB吧我这刚好有个空闲的U盘8GB容量&#xff0c;制作启动盘且放入一个最新win10官方镜像足够 不是天天装系统&#xff0c;至少USB2.0 (我用的2.0的一个闲置U盘)即可&#xff0c;当然平时传资料什么的3.0会快些 U盘启动盘仅需要制作一次&#xff0c; U盘启动盘制…...

AI换脸原理(4)——人脸对齐(关键点检测)参考文献2DFAN:代码解析

注意,本文属于人脸关键点检测步骤的论文,虽然也在人脸对齐的范畴下。 1、介绍 在本文中,重点介绍了以下几项创新性的成果,旨在为人脸关键点检测领域带来新的突破。 首先,成功构建了一个卓越的2D人脸关键点检测基线模型。这一模型不仅集成了目前最优的关键点检测网络结构,…...

Sarcasm detection论文解析 |使用 BERT 进行中间任务迁移学习的刺检测

论文地址 论文地址&#xff1a;https://www.mdpi.com/2227-7390/10/5/844#/ github&#xff1a;edosavini/TransferBertSarcasm (github.com) 论文首页 笔记框架 使用 BERT 进行中间任务迁移学习的讽刺检测 &#x1f4c5;出版年份:2022 &#x1f4d6;出版期刊:Mathematics &…...

docker系列9:容器卷挂载(下)

传送门 docker系列1&#xff1a;docker安装 docker系列2&#xff1a;阿里云镜像加速器 docker系列3&#xff1a;docker镜像基本命令 docker系列4&#xff1a;docker容器基本命令 docker系列5&#xff1a;docker安装nginx docker系列6&#xff1a;docker安装redis docker系…...

QT ERROR: Unknown module(s) in QT: xlsx怎么办

现象描述 QT编译c代码的时候&#xff0c;报这种QT ERROR: Unknown module(s) in QT: xlsx&#xff0c;应该如何解决&#xff1f; 这里&#xff0c;我简单记录一下自己的解决问题过程。有可能&#xff0c;对遇到同样的问题的你&#xff0c;也有所帮助 第一步 检查perl是否安装…...

npm install 卡在reify:rxjs: timing reifyNode的解决办法

今天要逆向跑一个electron&#xff0c;但是npm install一直卡在 reify:element-plus: timing reifyNode:node_modules/lodash Completed in 6664ms这里一动不动&#xff0c;一番研究之后发现可能跟用的镜像有关系&#xff0c;我原本是官方镜像&#xff0c;总感觉第三方镜像有一…...

VScode 无法连接云服务器

试了很多方法&#xff0c;比如更换VScode版本&#xff0c;卸载重装&#xff0c;删除配置文件 重启电脑&#xff0c;都无法成功。最后重置电脑后才连接上&#xff0c;但是重启服务器后又出现该问题。 方法一&#xff1a;修改环境 方法二&#xff1a;把vscode卸载干净重下...

Kafka 面试题(二)

1. 简述Kafka 的工作流程 &#xff1f; Kafka的工作流程涉及多个关键组件和步骤&#xff0c;确保了消息的可靠传输和处理。以下是Kafka工作流程的简要概述&#xff1a; 生产者发布消息&#xff1a;生产者&#xff08;Producer&#xff09;是Kafka工作流程的起点&#xff0c;它…...

Spring Cloud Kubernetes 本地开发环境调试

一、Spring Cloud Kubernetes 本地开发环境调试 上面文章使用 Spring Cloud Kubernetes 在 k8s 环境中实现了服务注册发现、服务动态配置&#xff0c;但是需要放在 k8s 环境中才能正常使用&#xff0c;在本地开发环境中可能没有 k8s 环境&#xff0c;如何本地开发调试呢&#…...

基于二维CS-SCHT变换和扩频方法的彩色图像水印嵌入和提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................. % figure; % subplot(121);…...

设计模式——行为型模式——策略模式(含实际业务使用示例、可拷贝直接运行)

目录 策略模式 定义 组成和UML图 代码示例 实际业务场景下策略模式的使用 策略模式优缺点 使用场景 JDK中使用策略模式示例 参考文档 策略模式 定义 策略模式定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换&#xff0c;且算法的变化…...

Rust:foo(x)、foo(x),还是foo(x.clone())?

一、一个实际问题 用一个线性代数库的求逆矩阵函数时&#xff0c;让我很不爽&#xff0c;我必须按照下面的形式写调用代码&#xff1a; ...if let Some(inv_mat) try_inverse(mat.clone()) {...}...注意 try_inverse 函数的参数传递形式&#xff0c;函数参数是 mat.clone() 而…...

「JavaEE」多线程案例1:单例模式阻塞队列

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;JavaEE &#x1f387;欢迎点赞收藏加关注哦&#xff01; 多线程案例分析 &#x1f349;单例模式&#x1f34c;饿汉模式&#x1f34c;懒汉模式&#x1f34c;指令重排序 &#x1f349;阻塞队列&a…...

pdf2htmlEX:pdf 转 html,医学指南精细化处理第一步

pdf2htmlEX&#xff1a;pdf 转 html&#xff0c;医学指南精细化处理第一步 单文件转换多文件转换 代码&#xff1a;https://github.com/coolwanglu/pdf2htmlEX 拉取pdf2htmlEX 的 Docker&#xff1a; docker pull bwits/pdf2htmlex # 拉取 bwits/pdf2htmlex不用进入容器&…...

【webrtc】MessageHandler 6: 基于线程的消息处理:StunRequest实现包发送和超时重传

G:\CDN\rtcCli\m98\src\p2p\base\stun_request.cc使用OnMessage 实现包的发送和包的超时重传StunRequest 一个StunRequest 代表是一个独立的请求的发送STUN消息 要不是发送前构造好的,要不就是按照需要构建的使用StunRequestManager: 每一个STUNRequest 携带一个交互id 写入m…...

《Python编程从入门到实践》day22

# 昨日知识点回顾 方法重构、驾驶飞船左右移动、全屏显示 飞船不移动解决&#xff0c;问题出在移动变量x更新 # Ship.pysnipdef update(self):"""根据移动标志调整飞船的位置"""# 更新飞船而不是rect对象的x值# 如果飞船右移的标志和飞船外接…...

介绍 ffmpeg.dll 文件以及ffmpeg.dll丢失怎么办的五种修复方法

ffmpeg.dll 是一个动态链接库文件&#xff0c;属于 FFmpeg运行库。它在计算机上扮演着非常重要的角色&#xff0c;因为它提供了许多应用程序和操作系统所需的功能和组件。当 ffmpeg.dll 文件丢失或损坏时&#xff0c;可能会导致程序无法正常运行&#xff0c;甚至系统崩溃。下面…...

AI换脸原理(6)——人脸分割介绍

一、介绍 人脸分割是计算机视觉和图像处理领域的一项重要任务,它主要涉及到将图像中的人脸区域从背景或其他非人脸区域中分离出来。这一技术具有广泛的应用场景,如人脸识别、图像编辑、虚拟背景替换等。 在计算机视觉(CV)领域,经典的分割技术可以主要划分为三类:语义分…...

【C++并发编程】(二)线程的创建、分离和连接

文章目录 &#xff08;二&#xff09;线程的创建、分离和链接创建线程&#xff1a;示例线程的分离&#xff08;detach&#xff09;和连接&#xff08;join&#xff09; &#xff08;二&#xff09;线程的创建、分离和链接 创建线程&#xff1a;示例 线程&#xff08;Thread&a…...

利用生成式AI重新构想ITSM的未来

对注入 AI 的生成式 ITSM 的需求&#xff0c;在 2023 年 Gartner AI 炒作周期中&#xff0c;生成式 AI 达到预期值达到顶峰后&#xff0c;三分之二的企业已经将生成式 AI 集成到其流程中。 你问为什么这种追求&#xff1f;在预定义算法的驱动下&#xff0c;IT 服务交付和管理中…...

完美解决AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘

遇到这种错误通常是因为matplotlib的后端配置问题。在某些环境中&#xff0c;尤其是在某些特定的IDE或Jupyter Notebook环境中&#xff0c;可能会因为后端配置不正确而导致错误。错误信息提示 module backend_interagg has no attribute FigureCanvas 意味着当前matplotlib的后…...

CMakeLists.txt语法规则:条件判断中表达式说明一

一. 简介 前面学习了 CMakeLists.txt语法中的 部分常用命令&#xff0c;常量变量&#xff0c;双引号的使用。 前面一篇文章也简单了解了 CMakeLists.txt语法中的条件判断&#xff0c;文章如下&#xff1a; CMakeLists.txt语法规则&#xff1a;条件判断说明一-CSDN博客 本文…...

中电云主机怎样登入创建的网站/能让手机流畅到爆的软件

1.Spring容器的实例化 1.1. 用构造器去实例化bean <!-- id/name属性用于执行bean的名称 用于从spring中获取这个对象, class用于指定bean的类型,会自动调用无参数构造器创建对象 --><beanid"calendarobj1"class"java.util.GregorianCalendar" /&…...

城市形象设计vi手册/百度seo新规则

gpart 分区工具 查看已经连接的设备rootlsgxbsd:~ # camcontrol devlist 1. 删除ad0上所有分区# gpart destroy -F /dev/da1注: 没有数据情况才可以这样 2. 创建gpt分区信息表# gpart create -s GPT /dev/da1 创建新的GPT分区表# gpart create -s MBR /dev/da1 创建新的MBR分区…...

和先锋影音和做的网站/青岛seo服务

dubbo provider抛出一个自定义异常&#xff0c;consuemer获取失败dubbo 源码有一个统一的异常处理策略public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {try {Result result invoker.invoke(invocation);if (result.hasException() &a…...

五星酒店网站建设方案/广西网络优化seo

一、单纯纹绣技术纹绣因人而异,突出个性设计,并且应用轻柔的手法,自然的色泽,进而达到即美化眉眼唇,又不留明显的修饰痕迹。1、绣眉目的&#xff1a;塑造眉型,改善稀疏或散乱眉型,视觉效果改善脸型,美化眉眼额头。适用人群&#xff1a;眉毛稀疏,散乱,下垂;或长期画眉者2、绣眼线…...

武汉关键词优化推广/seo关键词优化培训

2019独角兽企业重金招聘Python工程师标准>>> 【编者按】作者 Aaron Volkmann 是 CERT Division 高级研究员&#xff0c;通过提出了一种集成安全系统到 CI/CD 的方法&#xff0c;让机构保持快速部署到生产环境能力的同时&#xff0c;也大幅度降低安全隐患&#xff0c…...

安卓开发技术/seo站长工具综合查询

一、数据类型1、列表&#xff1a;# 列表中的每个元素都是可变的# 列表的元素是有序的&#xff0c;# 列表用中括号表示ab [ ]# ab.insert(‘位置’&#xff0c;‘元素‘) ## 在指定位置插入某个元素# ab.append() # 在末尾增加元素# ab.remove(1) # 删除‘1‘这个元素# ab.po…...