KVC与KVO

news2024/11/18 15:40:36

KVO

什么是KVO

KVO全称Key Value Observing,其是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。观察者模式

由于KVO的实现机制,只针对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO

KVO可以监听单个属性的变化,也可以监听集合对象的变化。集合对象包含NSArrayNSSet。通过KVCmutableArrayValueForKey:等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO监听的方法。

Key-Value Observing 可翻译成健值观察,是观察者模式再iOS开发中的具体体现,同时也是OC动态性的具体表现。在开发过程中,如果需要外部动态的获得对象的某个属性变化的时机以及变化前后的值,这时候就可以使用KVO来完成。显然KVO也属于信息传递的一种方式。

KVO的基本使用

#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@property (nonatomic, strong) Student *student;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.student = [[Student alloc] init];
    self.student.age = 10;
    NSLog(@"%ld", self.student.age);
    
    [self.student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.student.age = 20;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"监听到%@的%@属性值改变了! change = %@ ,context = %@", object, keyPath, change, context);
}

@end

这里注意addObserver:forKeyPath:options:context:方法中的options

options:回调方法里收到被观察的属性的旧值或新值,枚举类型,系统为我们提供了4个方法

  • NSKeyValueObservingOptionOld:change中会包含key变化之前的值old
  • NSKeyValueObservingOptionNew:change中会包含key变化之后的值new
  • NSKeyValueObservingOptionInitial:change中不包含key的值,会在kvo注册时候立即发通知
  • NSKeyValueObservingOptionPrior:会在值发生改变前发出一次通知,改变后通知依然发出,也就是每个change会有两个通知。值变化之前发送通知的 change 中包含notificationIsPrior = 1; 值发生变化之后的的通知 change 不包含上面提到的notificationIsPrior ,可以跟 willChange 手动通知搭配使用
  • 我们也可以中间以竖线来进行多种选择NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew这样change既有new又有old

KVO使用注意事项

但是在使用KVO的时候很容易引起crash,所以需要多多注意:

  1. keyPath 不能为空字符串
  2. 注意在适合的地方removeObersver,如果观察实例比被观察实例先释放,这时候改变观察属性,会产生崩溃。
  3. 没有添加,直接移除观察关系,也会产生崩溃

注意这个移除观察者的方法:[xxx removeObserver:self forKeyPath:@"xxxx"];

  • observer:观察者
  • keyPath:被观察对象的属性

手动调用KVO

KVO没法实现对数组元素内部的监听,此时就需要我们手动调用KVO

KVO在属性发生改变时的调用时自动的,如果想要手动控制这个调用时机,或想要自己实现KVO属性的调用,则可以通过KVO提供的方法进行调用。

  1. 如果想要手动调用或者自己实现KVO需要重写下面的方法。该方法返回YES表示允许系统自动调用KVO,NO表示不允许系统自动调用
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"date"]) {
        automatic = NO;//对该key禁用系统自动通知,若要直接禁用该类的KVO则直接返回NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}
  1. 另外我们需要重写setter方法:
- (void)setDate:(NSString *)date {
    if (date != _date) {
        [self willChangeValueForKey:@"date"];
        _date = date;
        [self didChangeValueForKey:@"date"];
    }
}

不过一般情况下手动出发KVO没有什么必要,这样的话即使在没有注册监听者前调用setter方法为属性赋值的时候都会调用道KVO的响应事件,打印结果如下:

在这里插入图片描述

所以我们不采用这种方法,直接在注册监听者之后再对被监听的属性重新赋值的时候在赋值操作前后分别调用willChangeValueForKeydidChangeValueForKey,例子如下(以对数组进行监听为例):

Apple.h文件中:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Apple : NSObject

@property (nonatomic, strong) NSMutableArray *arrayTest;

@end

NS_ASSUME_NONNULL_END


Apple.m文件中:
#import "Apple.h"

@implementation Apple

- (void)setDate:(NSString *)date {
    _date = date;
    NSLog(@"setDate:");
}
//由于数组的旧值不能被observeValueForKeyPath方法的change获取到,所以我们将打印旧值的操作移动到willChangeValueForKey中
- (void)willChangeValueForKey:(NSString *)key{
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey");
    if ([key isEqual:@"arrayTest"]) {
        NSLog(@"old value is: %@", self.arrayTest);
    }
}

- (void)didChangeValueForKey:(NSString *)key{
    NSLog(@"didChangeValueForKey - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey - end");
}

@end


ViewController.h文件中:
#import <UIKit/UIKit.h>
#import "Apple.h"

@interface ViewController : UIViewController

@property (nonatomic, strong) Apple *apple;

@end


ViewController.m文件中:
#import "ViewController.h"
#import "objc/runtime.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _apple = [[Apple alloc] init];
    self.apple.arrayTest = [[NSMutableArray alloc] init];

    [self.apple addObserver:self forKeyPath:@"arrayTest" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        
    [self.apple willChangeValueForKey:@"arrayTest"];
    [self.apple.arrayTest addObject:@"First!"];
    [self.apple didChangeValueForKey:@"arrayTest"];
    
}

//当属性变化时会激发该监听方法
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    //打印监听结果
    if ([keyPath isEqual:@"arrayTest"]) {
        //NSLog(@"old value is: %@", [change objectForKey:@"old"]);
        NSLog(@"new value is: %@", [change objectForKey:@"new"]);
    }  
}

@end

打印结果如下:

在这里插入图片描述
可以发现我们已经监听到数组的旧值和新值了。

KVO本质

KVO是基于runtime机制实现的

在运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指针指向中间类。并且将class方法重写,返回原类的class,例子如下:

前提:我们创建一个继承自NSObject类的Apple类,其中定义了一个名为date的字符串属性,并引入其类的头文件:

ViewController.h文件中:
#import <UIKit/UIKit.h>
#import "Apple.h"

@interface ViewController : UIViewController
//声明apple属性,因为KVO只能监听属性
@property (nonatomic, strong) Apple *apple;

@end

ViewController.m文件中:
#import "ViewController.h"
#import "objc/runtime.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _apple = [[Apple alloc] init];
    NSLog(@"类对象 -%@", object_getClass(self.apple));
    NSLog(@"方法实现 -%p", [self.apple methodForSelector:@selector(setDate:)]);
    NSLog(@"元类对象 -%@", object_getClass(object_getClass(self.apple)));
        
    //开启对apple属性的键值监听
    [self.apple addObserver:self forKeyPath:@"date" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        
    NSLog(@"类对象 -%@", object_getClass(self.apple));
    NSLog(@"方法实现 -%p", [self.apple methodForSelector:@selector(setDate:)]);
    NSLog(@"元类对象 -%@", object_getClass(object_getClass(self.apple))); 
}

@end

打印结果如下:

在这里插入图片描述

从打印结果的图中,我们可以清晰地看到:

  • apple指向的类对象和元类对象以及对应监听的属性的set方法都发生了改变
  • 添加KVO后,apple中的isa指向了NSKVONotifying_Apple类对象
  • 添加KVO后,setDate:的实现调用的是:Foundation中的_NSSetObjectValueAndNotify方法

isa-swizzling(类指针交换):
就是把当前某个实例对象的isa指针指向一个新建造的中间类,在这个新建造的中间类上面做hook方法或者别的事情,这样不会影响这个类的其他实例对象,仅仅影响当前的实例对象。
下图对于这个类指针交换讲解的就非常生动:

在这里插入图片描述

NSKVONotifying_Apple内部实现


- setName:最主要的重写方法,set值时调用通知函数
- class:返回原来类的class
- dealloc
- _isKVOA判断这个类有没有被KVO动态生成子类

- (void)setDate:(NSString *)date {

}

- (Class)class {
    return [Apple class];
}

- (void)dealloc {
    // 收尾工作
}

- (BOOL)_isKVOA {
    return YES;
}

isa指向中间类之后如何调用方法:

调用监听的属性的设置方法,例如:setDate:,都会先调用NSKVONotify_Apple对应的属性设置方法
调用非监听属性的设置方法,如print方法,就会通过NSKVONotify_Apple的superClass来找到Apple类对象,在调用其Apple类对象中的test方法

为什么要重写class方法:

如果没有重写class方法,当该对象调用class方法时,会在自己的方法缓存列表,方法列表,父类缓存,方法列表一直向上去查找该方法,因为class方法是NSObject中的方法,如果不重写最终可能会返回NSKVONotifying_Apple,就会将该类暴露出来

setter的实现不同

截图中我们可以看到set方法的实现在调用KVO后变成调用_NSSetObjectValueAndNotify这样一个C函数
我们不知道其本身是什么样,不过我们可以进行测试:

Apple.h文件中:
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Apple : NSObject

@property (nonatomic, copy) NSString *date;

@end

NS_ASSUME_NONNULL_END


Apple.m文件里:
#import "Apple.h"

@implementation Apple

- (void)setDate:(NSString *)date {
    _date = date;
    NSLog(@"setDate:");
}

- (void)willChangeValueForKey:(NSString *)key{
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey");
}

- (void)didChangeValueForKey:(NSString *)key{
    NSLog(@"didChangeValueForKey - begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey - end");
}

@end


ViewController.h文件里:
#import <UIKit/UIKit.h>
#import "Apple.h"

@interface ViewController : UIViewController

@property (nonatomic, strong) Apple *apple;

@end


ViewController.m文件里:
#import "ViewController.h"
#import "objc/runtime.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _apple = [[Apple alloc] init];
    self.apple.date = @"7Days!";
        
    [self.apple addObserver:self forKeyPath:@"date" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        
    self.apple.date = @"25Days!";
    
    
}

//当属性变化时会激发该监听方法
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    //打印监听结果
    if ([keyPath isEqual:@"date"]) {
        NSLog(@"old value is: %@", [change objectForKey:@"old"]);
        NSLog(@"new value is: %@", [change objectForKey:@"new"]);
    }
    
}

@end

要实现该测试样例必须要注意:

  • 监听者必须实现observeValueForKeyPath:ofObject:change:context:方法
  • Apple.m中实现的那三个方法是测试的关键

该样例的打印结果如下:

在这里插入图片描述

我们发现:

  • 先调用willChangeValueForKey方法
  • 接着调用原来的setDate方法
  • 最后调用didChangeValueForKey方法,并且通知监听者属性值已经改变,然后监听者执行observeValueForKeyPath:ofObject:change:context:处理监听事务。

KVO部分相关问题

  1. KVO的本质是什么?
  • 利用runtime的API动态生成一个子类,并让实例对象的isa指向这个全新的子类
  • 当修改实例变量对象的属性时候,在全新子类的set方法中会调用Foundation_NSSetXXXValueAndNotify函数
  • willChangeValueForKey
  • 调用原来的setter
  • didChangeValueForKey:内部会触发监听器的监听方法
  1. 手动触发KVO(详见上方讲解)
  2. 直接修改成员变量会触发KVO嘛?
    答案是不会

接着我们总结一下KVO的应用场景:

  1. 需要接收动态变化的时候
  2. 例如在AVFounditon中获取AVPlayer的播放进度,播放状态,也需要使用KVO来观察。
#pragma mark - 监听
- (void)currentItemAddObserver {
    // 监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
    [self.player.currentItem addObserver:self forKeyPath:@"status" options: (NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew) context:nil];
    // 监控缓冲加载情况属性
    [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    // 缓冲不足暂停了
    [self.player.currentItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    // playbackLikelyToKeepUp
    [self.player.currentItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    // rate
    [self.player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}

最后总结一下KVO的实现原理

  1. addObserver:forKeyPath:options:context:context调用的时候,会自动生成并注册一个该对象(被观察的对象)对应类的子类,取名NSKVONotify_Class,并且将该对象的isa指针指向这个新的中间类。
  2. 在该子类内部实现4个方法-被观察属性的set方法、class方法、isKVOdelloc
  3. 最关键的是set方法中,先调用willChangeValueForKey,再给成员变量赋值,最后调用didChangeValueForKeywillChangeValueForKeydidChangeValueForKey需要成对出现才能生效,在didChangeValueForKey中会去调用观察者的observeValueForKeyPath: ofObject: 方法。
  4. 重写class方法,这样避免外部感知子类的存在,同时防止在一些使用isKindOfClass判断的时候出错。
    5. isKVO方法作为实现KVO功能的一个标识。
  5. delloc里面还原isa指针。

KVC

定义在NSKeyValueCoding.h中,是一个非正式的协议。KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量

NSKeyValueCoding中提供了KVC通用的访问方法,分别是getter方法valueForKeysetter方法setValue:forKey,以及其衍生的keyPath方法,这两个方法是各个类通用的。并且由KVC提供默认的实现,我们也可以自己重写对应的方法来改变实现。

基本使用

赋值方法

- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;

取值方法

- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;

传参nil

如果对非对象传递一个nil值,KVC会调用setNIlValueForKey方法
我们可以重写这个方法来避免

不重写setNIlValueForKey时:

    _apple = [[Apple alloc] init];
    [self.apple setValue:nil forKey:@"flagTest"];

在这里插入图片描述
程序崩溃了。

重写setNIlValueForKey后:

首先在Apple.m文件中重写setNIlValueForKey方法为如下(默认的该方法接收到nil之后会打印日志报错):
- (void) setNilValueForKey:(NSString *)key {
    return;
}

//再去执行该代码
_apple = [[Apple alloc] init];
[self.apple setValue:nil forKey:@"flagTest"];

重写后程序没有报错。

处理非对象

  • setValue时,如果要赋值的对象是基本类型,需要将值封装成NSNumber或者NSValue类型
  • valueForKey时,返回的是id类型的对象,基本数据类型也会被封装成NSNumber或者NSValue

valueForKey可以自动将值封装成对象,但是setValue:forKey:却不行。我们必须手动讲值类型转换成NSNumber/NSValue类型才能进行传递

KVC获取值的过程

setValue:forKey

在KVO里面,我们使用setValue:forKey这一部分会导致触发KVO监听的过程,KVC触发调用了will和did

先用一张图展示一下setValue:forKey的过程:

在这里插入图片描述

  • 程序回先通过setter方法对属性进行设置
  • 如果没有找到set方法,KVC机制会检查+(Bool)accessInstanceVariablesDirectly(直接访问实例变量)方法有没有返回YES(默认返回YES
  • 如果重写方法成了NO,调用setValueForUndefinedKey:(为未定义项设置值)抛出异常
  • 返回YES就去找成员变量并直接赋值,按照_key,_isKey,key,iskey的顺序找,没找到就抛出异常

赋值顺序的例子如下:

//接口成员变量定义
@interface Apple : NSObject {
    @public
    int cnt;
    int isCnt;
    int _isCnt;
    int _cnt;
}

	//初始化和为成员变量赋值
	_apple = [[Apple alloc] init];
    _apple -> cnt = 10;
    _apple -> _cnt = 11;
    _apple -> _isCnt = 12;
    _apple -> isCnt = 13;
    
    [_apple setValue:@15 forKey:@"cnt"];
    
    NSLog(@"%@", [_apple valueForKey:@"cnt"]);
    NSLog(@"%d %d %d %d", _apple -> cnt, _apple -> _cnt, _apple -> _isCnt, _apple -> isCnt);

打印结果为:

在这里插入图片描述
我们可以看到,最后成员变量_cnt的值由11变成了15,符合我们刚才说的赋值顺序,就是先找_cnt去赋值,而不是先去找cnt赋值。

valueForKey

先上流程图参考:

在这里插入图片描述

  • 先后顺序搜索getKey、key 、isKey、_getKey、_key五个方法,若某一个方法被实现,取到的即是方法返回的值,后面的方法不再运行。如果是BOOL或者int等类型,会将其包装成一个NSNumber对象
  • 如果五个方法都没有,还是会访问accessInstanceVariablesDirectly方法有没有返回YES(该方法默认返回YES
  • 如果重写方法成了NO,抛异常
  • 返回YES就去找成员变量并取值,取值顺序为_key、_isKey、key、isKey

NSObject(NSKeyValueCoding)

KVC的Api都声明在NSObject的分类NSKeyValueCoding中,所以如果想使用KVC务必确认该对象是NSObject的子类。

其中比较重要的API有:

@property (class, readonly) BOOL accessInstanceVariablesDirectly;

- (void)setValue:(nullable id)value forKey:(NSString *)key;

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

- (id)valueForKey:(NSString *)key;

- (nullable id)valueForKeyPath:(NSString *)keyPath;

key&keyPath的区别,如果某个属性是一个对象,需要设置该属性的某个属性的时候,就可以使用keyPath,一步到位来设置或者获取属性,关于这些的详细使用详见上方的讲解。

KVC的使用场景

  1. crash防护,可以自定义valueForUndefinedKey:从而实现crash到控制台打印的友好处理方式
  2. jsonmodel(这个在接受网络请求来的数据的时候非常好用)
  3. KVO的实现
  4. 访问和修改私有变量(KVC的本质是操作方法列表以及在内存中查找实例变量。我们可以利用这个特性访问类的私有变量。同样如果不想让外界的类使用KVC的方法访问本类的成员变量,可以将accessInstanceVariablesDirectly属性设置为NO
  5. 修改一些控件的内部属性(很多UI控件都是由内部UI控件组合而成的,但是Apple(苹果官方)没有提供访问这些控件的API,这样我们就无法正常地访问和修改这些空间的样式。而KVC在大多数情况下可以解决这个问题)

另外我们在除了利用KVC 动态地对单取值和设值之外,还可以进行多值操作:

KVC可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

同样,也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给相应对象的属性赋值(其实这里有点像jsonModel):

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
	NSDictionary *dic = @{@"name" : @"book", @"age" : @"66", @"sex" : @"male"};
	//这个类里面定义了一些属性,这些属性名和传进来的字典的key一致且属性名数量大于等于字典中的key
    StudentModel *model = [[StudentModel alloc] init];
    
    [model setValuesForKeysWithDictionary:dic];
    NSLog(@"%@",model);
    NSDictionary *modelDic = [model dictionaryWithValuesForKeys:@[@"name", @"age", @"studentSex"]];
    NSLog(@"modelDic : %@", modelDic);

如果model 属性和dic不匹配,可以重写方法 -(void)setValue:(id)value forUndefinedKey:(NSString *)key

//比如我们需要赋值的属性是studentSex,而传进来的字典的相关对应的key为sex,我们可以在该方法里面将key为sex的value赋值给studentSex属性进行补救
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if([key isEqualToString:@"sex"]) {
        self.studentSex = (NSString *)value;
    }
}

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

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

相关文章

Centos部署Springboot项目详解

准备启动jar包&#xff0c;app.jar放入指定目录。 一、命令启动 1、启动命令 java -jar app.jar 2、后台运行 nohup java -jar app.jar >/dev/null 2>&1 & 加入配置参数命令 nohup java -Xms512M -Xmx512M -jar app.jar --server.port9080 spring.profiles…

k8s webhook实例,java springboot程序实现 对Pod创建请求添加边车容器 ,模拟istio实现日志文件清理

k8s webhook实例&#xff0c;java springboot程序实现 对Pod创建请求添加边车容器 &#xff0c;模拟istio实现日志文件清理 大纲 背景与原理实现流程开发部署my-docker-demo-sp-user服务模拟业务项目开发部署my-sidecar服务模拟边车程序开发部署服务my-docker-demo-k8s-opera…

代码随想录算法训练营day14 | 二叉树的递归遍历和迭代遍历

目录 144. 二叉树的前序遍历 94. 二叉树的中序遍历 145. 二叉树的后序遍历 递归看这篇文章&#xff1a;三道题套路解决递归问题 | lyls blog 前序遍历的和后序遍历的迭代方法代码思路相同&#xff1b;因为栈为先进后出&#xff0c;所以前序遍历的入栈顺序为中右左&#xff1…

查看GPU使用的最佳方式

1. watch -n 1 nvidia-smi (最有名,没有之一) nvidia自带了一个nvidia-smi的命令行工具,会显示GPU使用情况 ​​​​​​​ 作为监控 GPU 的工具就显得有点过于简陋了。比如 Process name 栏只显示命令行的程序名,不显示参数,这样输出结果就是一堆 python 和 .../Minico…

封神,谷歌架构师分享 出gRPC 与云原生应用开发 Go 和 Java 为例文档

前言 随着微服务和云原生相关技术的发展&#xff0c;应用程序的架构模式已从传统的单体架构或分层架构转向了分布式的计算架构。尽管分布式架构本身有一定的开发成本和运维成本&#xff0c;但它所带来的收益是显而易见的。 在实现分布式应用程序时&#xff0c;我们必须考虑两…

加热炉优化燃烧控制系统开发

首先给大家推荐本书&#xff1a;      认识 加热炉是是把坯料加热到均匀的、适合轧制的温度(奥氏体组织)。温度提高以后&#xff0c;首先是提高钢的塑性&#xff0c;降低变形抗力&#xff0c;使钢容易变形。 钢坯加热模型。如T12钢室温下变形抗力约为600Mpa&#xff0c;加热…

ide运行报错“Command line is too long”解决办法

有时运行程序时&#xff0c;ide会报如下错&#xff0c;如何解决呢&#xff0c;其实提示已经告诉你解决办法了。点击图中的蓝色超链接&#xff1a; “JAR MAINIFEST” 或 “CLASSPATH FILE” 都能解决问题 这是因为命名的方法名太长了才会报这个错 方法2 如果没有弹窗&#xf…

DisplayPort Alt Mode On Type-c协议_Ver2.0(学习笔记)

1.简介 USB-C设想支持使用Type-C连接器和电缆的Alt Mode&#xff0c;其中各种Type-C连接器引脚可以重新配置&#xff0c;以支持超出Type-C范围的接口。 Type-C&#xff08;以及它所利用的USB PD&#xff09;提供了一个Discovery Process&#xff0c;用于发现连接的USB设备中对…

【Yolo系列目标检测模型】详细整理!更新中

文章目录 1、YOLOv11.1、概述1.2、实现方案1&#xff09;整体思路2&#xff09;网络结构3&#xff09;损失函数4&#xff09;推理/预测过程5&#xff09;优缺点 2、YOLOv22.1、概述/改进整体思路2.2、YOLOv2的改进点/tricks1&#xff09;Multi-Scale Training2&#xff09;High…

智安网络|常见的网络安全陷阱:你是否掉入了其中?

在数字化时代&#xff0c;网络安全成为了一个重要的议题。随着我们越来越多地在互联网上进行各种活动&#xff0c;诸如在线银行交易、社交媒体分享和在线购物等&#xff0c;我们的个人信息也更容易受到攻击和滥用。虽然有许多关于网络安全的指导和建议&#xff0c;但仍然有许多…

代码扫描平台SonarQube搭建保姆级教程

代码扫描工具SonarQube&#xff0c;想必大家也有了解&#xff0c;就是那种不明觉厉的存在&#xff0c;最近刚好有需求&#xff0c;需要自己搭建&#xff0c;下面就给大家记录下搭建的过程。 不了解的&#xff0c;可以先看看 https://github.com/SonarSource/sonarqube 有一个初…

【浩鲸科技】济南Java后端面经

本文目录 写在前面试题总览题目解析1.说一下SpringBoot中常用的注解2.Redis中的基本数据类型3.TCP的网络协议4.java中常见的锁5.Hashmap的底层数据结构、底层源码、扩容机制源码6.java面向对象的特点 写在前面 关于这个专栏&#xff1a; 本专栏记录一些互联网大厂、小厂的面试实…

短视频矩阵系统源码开发流程​

一、视频矩阵系统源码开发流程分为以下几个步骤&#xff1a; 四、技术开发说明&#xff1a; 产品原型PRD需求文档产品交互流程图部署方式说明完整源代码源码编译方式说明三方框架和SDK使用情况说明和代码位置平台操作文档程序架构文档 一、抖音SEO矩阵系统源码开发流程分为以…

什么是 MyBatis?

经过前几篇博客的学习 Spring 系列的基本操作已经实现的差不多了&#xff0c;接下来&#xff0c;我们来学习更重要的知识&#xff0c;将前端传递的数据存储起来&#xff0c;或者查询数据库里面的数据。 一、MyBatis 是什么&#xff1f; MyBatis 是一款优秀的持久层框架&…

Vue中$route和$router的区别

$router&#xff1a;用来操作路由&#xff0c;$route&#xff1a;用来获取路由信息 $router其实就是VueRouer的实例&#xff0c;对象包括了vue-router使用的实例方法&#xff0c;还有实例属性&#xff0c;我们可以理解为$router有一个设置的含义&#xff0c;比如设置当前的跳转…

二叉树的基本操作函数(先、中、后、层次遍历)

//链式二叉树数据结构 typedef struct Node { TElemType data;//数据域 struct Node* lchild, * rchild;//左右孩子 }Node, * Tree; #include <queue> #define TElemType int //方便 应用的时候 改//链式二叉树数据结构 typedef struct Node {TElemType data;//数据域s…

ceph集群---使用RBD块存储

文章目录 创建和删除池RBD设备的配置及使用RBD 块设备数据的导出和导入 块存储接口是一种主流的存储访问接口&#xff0c;也是常见的存储形态&#xff0c;比如服务器下的/dev/sdx都是块存储设备。你可以像使用磁盘一样来使用Ceph提供的块存储设备。 在创建块存储设备之前&#…

Python缔造超级马里奥世界,带你畅游冒险与创造的奇境!

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 马里奥大叔这次发现了一个很大的宝藏&#xff0c;这次他终于可以不当马里奥大叔&#xff01; 可以当一次有钱的富人喽&#xff0c;让我们一起来玩一玩吧&#xff01; 帮马里奥大叔闯关并收集到金币。只有所有的金币都收集完…

删除(取消)亚马逊的云服务器Amazon EC2的操作方法、教程

删除&#xff08;取消&#xff09;亚马逊的云服务器Amazon EC2的操作方法、教程 关于Amazon EC2服务器 1.亚马逊的服务器只能终止&#xff0c;不能删除&#xff0c;终止后卷&#xff08;存储、硬盘会自动关闭&#xff0c;如果没有快照不可恢复&#xff09; 2.终止的服务器会存…

简单工厂模式VS策略模式

简单工厂模式VS策略模式 今天复习设计模式&#xff0c;由于简单工厂模式和策略模式太像了&#xff0c;重新整理梳理一下 简单工厂模式MUL图&#xff1a; 策略模式UML图&#xff1a; 1、简单工厂模式中只管创建实例&#xff0c;具体怎么使用工厂实例由调用方决定&#xff0c…