【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 底层原理

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

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

相关文章

IDEA基于Maven构建项目

IDEA基于Maven构建项目 一、Maven简介 Apache Maven 是一个软件项目管理和理解工具。基于项目对象模型的概念&#xff08;POM&#xff09;&#xff0c;Maven 可以从中心信息中管理项目的构建、报告和文档。 Apache Maven 可以用于构建和管理任何基于 Java 的项目。 下载地址…

EPAI手绘建模APP颜色、贴图、材质、样式

⑦ 颜色选择页面 1) 颜色环选色。 图 65 颜色选择器-颜色环 2) RGB选色。 图 66 颜色选择器-RGB 3) HSL选色。 图 67 颜色选择器-HSL 4) 国风颜色库选色。 图 68 颜色选择器-国风 5) CSS颜色库选色。 图 69 颜色选择器-CSS 6) 历史颜色&#xff1a;保存最近使用的多个颜色&…

OpenCV如何使用 GDAL 读取地理空间栅格文件(72)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV的周期性噪声去除滤波器(70) 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目录 目标 代码&#xff1a; 解释&#xff1a; 如何使用 GDAL 读取栅格数据 注意 …

ElasticSearch自动补全

一、拼音分词器&#xff1a; 当用户在搜索框输入字符时&#xff0c;我们应该提示出与该字符有关的搜索项&#xff0c;如图&#xff1a; 这种根据用户输入的字母&#xff0c;提示完整词条的功能&#xff0c;就是自动补全了。 GET /_analyze {"text":"我爱螺蛳粉…

.Net MAUI 搭建Android 开发环境

一、 安装最新版本 VS 2022 安装时候选择上 .Net MAUI 跨平台开发 二、安装成功后,创建 .Net MAUI 应用 三、使用 VS 自带的 Android SDK 下载 ,Android镜像、编译工具、加速工具 四、使用Vs 自带的 Android Avd 创建虚拟机 五、使用 Android 手机真机调试

【小菜鸟之---Ansible基础详解】

文章目录 1 【Ansible简介】1.1简介1.2 Ansible 特点1.3 Ansible的工作机制1.4Ansible任务工作模式 2【安装部署】2.1安装命令2.2 Ansible配置文件2.3主机清单配置2.4 基于ssh免密登录2.5常用命令 3【Ansible常用模块】3.1 ping模块3.2 shell模块3.3 command模块3.4 copy模块3.…

百度下拉框负面信息如何删除?

百度头条360等搜索引擎&#xff0c;作为人们获取信息的主要途径之一。然而&#xff0c;一些知名的企业或个人可能会面临在搜索的下拉框中出现负面信息的问题&#xff0c;这可能对其声誉和形象造成不良影响。小马识途营销顾问根据自身从业经验&#xff0c;针对这类情况提出以下建…

一、写给Android开发者之harmony入门

一、创建新项目 对比 android-studio&#xff1a;ability类似安卓activity ability分为两种类型(Stage模型) UIAbility和Extensionability&#xff08;提供系统服务和后台任务&#xff09; 启动模式 1、 singleton启动模式&#xff1a;单例 2、 multiton启动模式&#xff1…

【软件测试理论002】认识软件缺陷、缺陷生命周期、缺陷分类

目录 1 认识软件缺陷 1.1 什么是软件缺陷 1.2 缺陷存在哪些方面 1.3 软件缺陷示例 1.4 软件缺陷的表现形式 1.5 软件缺陷产生的原因 1.6 软件缺陷的根源 1.7 软件缺陷修复的费用 2 软件缺陷的信息分类 2.1 软件缺陷的生命周期 2.2 软件缺陷的信息 2.3 软件缺陷分类…

Node.js -- mongoose

文章目录 1. 介绍2. mongoose 连接数据库3. 插入文件4. 字段类型5. 字段值验证6. 文档处理6.1 删除文档6.2 更新文档6.3 读取文档 7. 条件控制8. 个性化读取9. 代码模块化 1. 介绍 Mongoose是一个对象文档模型库&#xff0c;官网http://www.mongoosejs.net/ 方便使用代码操作mo…

CNN实现卫星图像分类(tensorflow)

使用的数据集卫星图像有两类&#xff0c;airplane和lake&#xff0c;每个类别样本量各700张&#xff0c;大小为256*256&#xff0c;RGB三通道彩色卫星影像。搭建深度卷积神经网络&#xff0c;实现卫星影像二分类。 数据链接百度网盘地址&#xff0c;提取码: cq47 1、查看tenso…

Rust Rocket创建第一个hello world的Web程序 Rust Rocket开发常用网址和Rust常用命令

一、Rust Rocket简介 Rust Rocket 是一个用 Rust 语言编写的 Web 应用框架&#xff0c;它结合了 Rust 的安全性和性能优势&#xff0c;以及 Web 开发的便利性。以下是 Rust Rocket 框架的一些优点&#xff1a; 安全性&#xff1a;Rust 是一种注重安全性的编程语言&#xff0c;…

Redis-分片机制

概述 业务需要&#xff1a;由于单台redis内存容量是有限的&#xff0c;无法实现海量的数据实现缓存存储 概念&#xff1a;由多个redis节点协助工作的机制就是redis的分片机制 作用&#xff1a;为了实现redis扩容 特点&#xff1a;分片机制把该机制中包含的多台redis缓存服务…

PostgreSQL和openGauss优化器对一个关联查询的SQL优化改写

PostgreSQL和openGauss数据库优化器在merge join关联查询的SQL优化改写 PostgreSQL 查询计划openGauss 查询计划拓展对比 看腻了文章就来听听视频讲解吧&#xff1a;https://www.bilibili.com/video/BV1oH4y137P7/ 数据库类型数据库版本PostgreSQL16.2openGauss6.0 创建测试表…

【Android】Android应用性能优化总结

AndroidApp应用性能优化总结 最近大半年的时间里&#xff0c;大部分投在了某国内新能源汽车的某款AndroidApp开发上。 由于该App是该款车上&#xff0c;常用重点应用。所以车厂对应用性能的要求比较高。 主要包括&#xff1a; 应用冷启动达到***ms。应用热(温)启动达到***ms应…

C语言 | Leetcode C语言题解之第70题爬楼梯

题目&#xff1a; 题解&#xff1a; int climbStairs(int n) {double sqrt5 sqrt(5);double fibn pow((1 sqrt5) / 2, n 1) - pow((1 - sqrt5) / 2, n 1);return (int) round(fibn / sqrt5); }

Vue通过下拉框选择字典值,并将对应的label以及value值提交到后端

产品品种从字典中获取 产品性质也是从字典中获取 字典当中的保存 dict_type表 dict_data表 在表单提交的方法中 1.因为做的产品性质是多选&#xff0c;它会以数组的方式提交&#xff0c;所以需要先将Json格式转变为String JSON.stringify(this.form.nature) 2.提交表单&…

Java基于Spring Boot框架的课程管理系统(附源码,说明文档)

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

基于uniapp vue3.0 uView 做一个点单页面(包括加入购物车动画和左右联动)

1、实现效果&#xff1a; 下拉有自定义组件&#xff08;商品卡片、进步器、侧边栏等&#xff09;源码 2、左右联动功能 使用scroll-view来做右边的菜单页&#xff0c;title的id动态绑定充当锚点 <scroll-view :scroll-into-view"toView" scroll-with-animation…

【链表】:链表的带环问题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 链表的带环问题在链表中是一类比较难的问题&#xff0c;它对我们的思维有一个比较高的要求&#xff0c;但是这一类…