【iOS KVO(下) KVO的内部结构和源码】

news2024/9/22 1:10:27

前言

学习KVO的过程,我分为了KVO的实现过程分析和内部结构的学习,学习了实现过程,接下来看KVO是通过何种内部结构实现如此通知📢和监听。

1 KVO的存储结构

KVO的实现过程离不开合理的存储结构,用到了如下几个类

  • GSKVOInfo
  • GSKVOPathInfo
  • GSKVOObervation

1.1 GSKVOInfo

KVO是基于NSObject类别实现的非正式协议,所以所有继承于NSObject的类都可以使用KVO,其中可以通过- (void*) observationInfo方法获取对象相关联的所有KVO信息,而返回值就是GSKVOInfo

@interface  GSKVOInfo : NSObject
{
  NSObject          *instance;  // Not retained.
  GSLazyRecursiveLock           *iLock;
  NSMapTable            *paths;
}
  • 它保存了一个对象的实例,重点 Not retained 由于没有持有,也不是weak,- 所以当释放之后,在调用会崩溃,需要在对象销毁前,移除所有观察者
    paths 用于保存keyPath 到 GSKVOPathInfo 的映射:

1.2 GSKVOPathInfo

GSKVOPathInfo 是一个自定义的用于管理KVO路径的类,它可以帮助我们在使用KVO时更加便捷地管理被观察对象的属性路径。

在iOS开发中,使用KVO机制进行观察时,我们可以通过注册被观察对象的属性路径来进行观察。属性路径由一个或多个属性名组成,用“.”连接起来表示属性之间的层次关系,例如 person.address.street 表示 person 对象的 address 属性的 street 子属性。

@interface  GSKVOPathInfo : NSObject
{
@public
  unsigned              recursion;
  unsigned              allOptions;
  NSMutableArray        *observations;
  NSMutableDictionary   *change;
}

它保存了一个keypath对应的所有观察者

  • observations 保存了所有的观察者(GSKVOObservation 类型)
  • allOptions 保存了观察者的options集合
  • change 保存了KVO触发要传递的内容

1.3 GSKVOObervation

在iOS开发中,使用KVO机制进行观察时,系统会自动为我们创建一个观察者对象,并在属性发生变化时调用该观察者的回调方法

它保存了单个观察者的所有信息

@interface  GSKVOObservation : NSObject
{
@public
  NSObject      *observer;      // Not retained (zeroing weak pointer)
  void          *context;
  int           options;
}
@end
  • observer :观察者对象,即注册 KVO 观察时传入的观察者对象。(Not retained)
  • keyPath :被观察的属性名。
  • context :上下文信息,用于在回调方法中区分不同的 KVO 观察。

1.3 有什么关联

GSKVOInfoGSKVOObservationGSKVOPathInfo 都是在 iOS 开发中使用 KVO 机制时可能会用到的自定义类或结构体,它们之间有一定的关联。

在 KVO 机制中,我们可以通过注册被观察对象的属性路径来进行观察。而 GSKVOPathInfo 类就是用于解析和管理这些属性路径的。每个 GSKVOPathInfo 对象代表一个属性路径。

在注册 KVO 观察时,系统会自动为我们创建一个观察者对象,并在属性发生变化时调用该观察者的回调方法。而在某些情况下,我们可能需要手动管理观察者对象,这时就可以使用 GSKVOObservation 结构体。每个 GSKVOObservation 对象代表一个 KVO 观察。

GSKVOInfo 类则是将 GSKVOPathInfoGSKVOObservation 结合起来,用于管理一个对象的所有 KVO 观察。每个 GSKVOInfo 对象代表一个被观察对象,它包含一个 GSKVOPathInfo 对象列表,表示该对象所有被观察的属性路径,以及一个 GSKVOObservation 结构体列表,表示该对象所有的 KVO 观察。

因此,可以说 GSKVOPathInfoGSKVOObservationGSKVOInfo 三者之间有一定的层级关系,它们共同构成了 KVO 机制的基本组成部分。

请添加图片描述

2 KVO如何实现属性变化产生通知?

KVO是通过isa-swizzling技术实现的(这句话是整个KVO实现的重点)。在运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa指向中间类。并且将class方法重写,返回原类的Class。所以苹果建议在开发中不应该依赖isa指针,而是通过class实例方法来获取对象类型。

在这里插入图片描述

我的理解

在使用 KVO 机制时,我们可以通过调用 addObserver:forKeyPath:options:context: 方法来注册观察者,并在被观察对象的属性发生变化时,系统会自动调用观察者的回调方法。那么系统是如何实现这一机制的呢?

KVO 机制实际上是通过 isa-swizzling 技术来实现的。当我们注册一个观察者时,系统会生成一个新的子类,继承自被观察对象的类,并将被观察对象的 isa 指针指向这个新生成的子类。这个子类会重写被观察对象的 setter 方法,在 setter 方法中调用原有的 setter 方法,并触发 KVO 机制。也就是说,在设置属性值时,会先调用被观察对象的 setter 方法,然后再调用观察者的回调方法。

因为被观察对象的 isa 指针被修改了,所以这个被观察对象就不再是原有类的实例,而是新生成子类的实例,这个子类中重写了被观察属性的 setter 方法。这样,当属性值发生变化时,就会调用这个新生成子类中的 setter 方法,从而触发 KVO 机制。

2.1 实现isa-swizzling的结构

实现isa-swizzling技术需要用到以下类

  • GSKVOReplacement
  • GSKVOBase
  • GSKVOSetter

2.1.1 GSKVOReplacement

GSKVOReplacement 是 GSKVOObservation 中的一个重要结构体,用于存储在 KVO 机制中替换掉原有 setter 方法的新方法的实现。

GSKVOReplacement 结构体就是用来存储这个被替换掉的原有 setter 方法的实现和新的 setter 方法的实现的

@interface  GSKVOReplacement : NSObject
{
  Class         original;       /* The original class */
  Class         replacement;    /* The replacement class */
  NSMutableSet  *keys;          /* The observed setter keys */
}
- (id) initWithClass: (Class)aClass;
- (void) overrideSetterFor: (NSString*)aKey;
- (Class) replacement;
@end

// 创建
- (id) initWithClass: (Class)aClass
{
  NSValue       *template;
  NSString      *superName;
  NSString      *name;

  original = aClass;

  /*
   * Create subclass of the original, and override some methods
   * with implementations from our abstract base class.
   * 创建原始的子类,并覆盖一些方法
	*使用来自我们抽象基类的实现。
   */
  superName = NSStringFromClass(original);      // original == Temp
  name = [@"GSKVO" stringByAppendingString: superName];    // name = GSKVOTemp
  template = GSObjCMakeClass(name, superName, nil);   // template = GSKVOTemp
  GSObjCAddClasses([NSArray arrayWithObject: template]);
  replacement = NSClassFromString(name);
  GSObjCAddClassBehavior(replacement, baseClass);

  /* Create the set of setter methods overridden.
  	创建一组被重写的setter方法。
   */
  keys = [NSMutableSet new];

  return self;
}

  • 这个类保存了被观察对象原始类信息 original
  • 创建一个原始类的子类 命名为GSKVO<原类名>,iOS系统使用命名规则为NSKVONotifying_原类名
  • 拷贝GSKVOBase类中的方法到新类中
  • 后续会通过object_setClass, 将被观察对象的isa指向这个新类,也就是isa-swizzling技术,而isa保存的是类的信息,也就是说被观察者对象就变成了新类的实例,这个新类用于实现KVO通知机制

2.1.2 GSKVOBase

这个类默认提供了几个方法,都是对NSObject方法的重写,而从上面得知,这些方法都要拷贝到新创建的替换类中。也就是被观察者会拥有这几个方法的实现

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

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

- (Class) class此方法用来隐藏替换类信息,应用层获取类的信息,仍然是原始类的信息. 所以苹果建议在开发中不应该依赖isa指针,而是通过class实例方法来获取对象类型。

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

- (Class) superclass此方法和class方法原理相同

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

- (void) setValue: (id)anObject forKey: (NSString*)aKey这个方法是属于KVC中的,重写这个方法,实现在原始类KVC调用前后添加[self willChangeValueForKey: aKey]和[self didChangeValueForKey: aKey],而这两个方法是触发KVO通知的关键。
所以说KVO是基于KVC的,而KVC正是KVO触发的入口。

- (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);
    }
}

2.1.3 GSKVOSetter

@interface  GSKVOSetter : NSObject
- (void) setter: (void*)val;
- (void) setterChar: (unsigned char)val;
- (void) setterDouble: (double)val;
- (void) setterFloat: (float)val;
- (void) setterInt: (unsigned int)val;
- (void) setterLong: (unsigned long)val;
#ifdef  _C_LNG_LNG
- (void) setterLongLong: (unsigned long long)val;
#endif
- (void) setterShort: (unsigned short)val;
- (void) setterRange: (NSRange)val;
- (void) setterPoint: (NSPoint)val;
- (void) setterSize: (NSSize)val;
- (void) setterRect: (NSRect)rect;
@end

这个类和上面重写KVC方法原理相同,将来会替换被观察者keypath的setter方法实现。会在原始setter方法前后添加[self willChangeValueForKey: aKey][self didChangeValueForKey: aKey]`

isa-swizzling小结

通过isa-swizzling技术, 替换被观察的类信息,并且hook被观察keyPath setter方法,在原始方法调用前后添加[self willChangeValueForKey: aKey]和[self didChangeValueForKey: aKey],从而达到监听属性变化的功能

3 KVO的实现过程源码

3.1 - (void) addObserver: (NSObject*)anObserver… 添加观察者.方法

- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOInfo             *info;
  GSKVOReplacement      *r;
  NSKeyValueObservationForwarder *forwarder;
  NSRange               dot;

  setup();
  [kvoLock lock];

  // Use the original class
  r = replacementForClass([self class]);

  /*
   * Get the existing observation information, creating it (and changing
   * the receiver to start key-value-observing by switching its class)
   * if necessary.
   */
  info = (GSKVOInfo*)[self observationInfo];
  if (info == nil)
    {
      info = [[GSKVOInfo alloc] initWithInstance: self];
      [self setObservationInfo: info];
      object_setClass(self, [r replacement]);
    }

  /*
   * Now add the observer.
   */
  dot = [aPath rangeOfString:@"."];
  if (dot.location != NSNotFound)
    {
      forwarder = [[NSKeyValueObservationForwarder alloc]
        initWithKeyPath: aPath
           ofObject: self
         withTarget: anObserver
        context: aContext];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: forwarder];
    }
  else
    {
      [r overrideSetterFor: aPath];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

  [kvoLock unlock];
}

KVO的入口,添加观察者方法,主要做了以下几件事

  • replacementForClass,创建替换类,并且加入到全局classTable中,方便以后使用
    获取对象的监听者数据GSKVOInfo,如果没有就创建新的
info = (GSKVOInfo*)[self observationInfo];
  if (info == nil)
    {
      info = [[GSKVOInfo alloc] initWithInstance: self];
      [self setObservationInfo: info];
      object_setClass(self, [r replacement]);
    }

  • 接下来分为两种情况
    • 1️⃣ 如果利用了点语法(self.keyPath.keyPath), 会利用NSKeyValueObservationForwarder递归创建子对象监听,子对象在将监听到的变化转发到上层,以后再具体分析
if (dot.location != NSNotFound)
    {
      forwarder = [[NSKeyValueObservationForwarder alloc]
        initWithKeyPath: aPath
           ofObject: self
         withTarget: anObserver
        context: aContext];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: forwarder];
    }
 
    • 2️⃣ 默认情况(keyPath)直接监听对象的某个属性,则会调用overrideSetterFor方法,hook属性的setter方法,将setter方法的实现替换为相应参数类型的GSKVOSetter中的方法实现
 else
    {
      [r overrideSetterFor: aPath];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

然后调用[info addObserver: anObserver forKeyPath: aPath options: options context: aContext];方法,将新的监听保存起来。

3.2 GSKVOInfo 中的添加的方法

- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOPathInfo         *pathInfo;
  GSKVOObservation      *observation;
  unsigned              count;

  if ([anObserver respondsToSelector:
    @selector(observeValueForKeyPath:ofObject:change:context:)] == NO)
    {
      return;
    }
  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo == nil)
    {
      pathInfo = [GSKVOPathInfo new];
      // use immutable object for map key
      aPath = [aPath copy];
      NSMapInsert(paths, (void*)aPath, (void*)pathInfo);
      [pathInfo release];
      [aPath release];
    }

  observation = nil;
  pathInfo->allOptions = 0;
  count = [pathInfo->observations count];
  while (count-- > 0)
    {
      GSKVOObservation      *o;

      o = [pathInfo->observations objectAtIndex: count];
      if (o->observer == anObserver)
        {
          o->context = aContext;
          o->options = options;
          observation = o;
        }
      pathInfo->allOptions |= o->options;
    }
  if (observation == nil)
    {
      observation = [GSKVOObservation new];
      GSAssignZeroingWeakPointer((void**)&observation->observer,
    (void*)anObserver);
      observation->context = aContext;
      observation->options = options;
      [pathInfo->observations addObject: observation];
      [observation release];
      pathInfo->allOptions |= options;
    }

  if (options & NSKeyValueObservingOptionInitial)
    {
      /* If the NSKeyValueObservingOptionInitial option is set,
       * we must send an immediate notification containing the
       * existing value in the NSKeyValueChangeNewKey
       */
      [pathInfo->change setObject: [NSNumber numberWithInt: 1]
                           forKey:  NSKeyValueChangeKindKey];
      if (options & NSKeyValueObservingOptionNew)
        {
          id    value;

          value = [instance valueForKeyPath: aPath];
          if (value == nil)
            {
              value = null;
            }
          [pathInfo->change setObject: value
                               forKey: NSKeyValueChangeNewKey];
        }
      [anObserver observeValueForKeyPath: aPath
                                ofObject: instance
                                  change: pathInfo->change
                                 context: aContext];
    }
  [iLock unlock];
}

此方法主要是保存观察者信息

  • 查询相应的GSKVOPathInfo --GSKVOObservation如果有就更新,如果没有就创建新的并保存
  • 如果options中包含NSKeyValueObservingOptionInitial,则立马调用[anObserver observeValueForKeyPath: aPath ofObject: instance change: pathInfo->change context: aContext];发送消息给观察者
  • 其中获取当前值通过KVC中的[instance valueForKeyPath: aPath];获取

3.3 willChangeValueForKey 和 didChangeValueForKey

这两个方法分别添加在setter和KVC 赋值前后,用来保存属性值的变化,以及发送消息给观察者

  • willChangeValueForKey
    主要记录属性值的 oldValue保存到pathInfo->change中,如果options包含NSKeyValueObservingOptionPrior,则会遍历所有观察者,立马发送消息给观察者NSKeyValueObservingOptionPrior 表示属性值修改前后都会收到通知
  • didChangeValueForKey
    根据options保存属性的新旧值,遍历所有的观察者,发送消息

3.4 移除观察者

此方法主要用来移除相应keyPath的观察者,方法实现很简单,根据参数传入的anObserver和aPath在前面介绍的数据结构中查询并移除

/*
 * removes the observer
 */
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
  GSKVOPathInfo *pathInfo;

  [iLock lock];
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo != nil)
    {
      unsigned  count = [pathInfo->observations count];

      pathInfo->allOptions = 0;
      while (count-- > 0)
        {
          GSKVOObservation      *o;

          o = [pathInfo->observations objectAtIndex: count];
          if (o->observer == anObserver || o->observer == nil)
            {
              [pathInfo->observations removeObjectAtIndex: count];
              if ([pathInfo->observations count] == 0)
                {
                  NSMapRemove(paths, (void*)aPath);
                }
            }
          else
            {
              pathInfo->allOptions |= o->options;
            }
    }
    }
  [iLock unlock];
}

3.5 NSKeyValueObservationForwarder

NSKeyValueObservationForwarder 是一个私有类,用于在 KVO 机制中实现观察者的转发。它主要用于管理观察者和被观察对象之间的关系,并在被观察对象的属性值发生变化时,通知观察者。

@interface NSKeyValueObservationForwarder : NSObject
{
  id                                    target;
  NSKeyValueObservationForwarder        *child;
  void                                  *contextToForward;
  id                                    observedObjectForUpdate;
  NSString                              *keyForUpdate;
  id                                    observedObjectForForwarding;
  NSString                              *keyForForwarding;
  NSString                              *keyPathToForward;
}

NSKeyValueObservationForwarder 类的作用在 KVO 机制中是非常重要的。它实现了观察者和被观察对象之间的关系,保证了观察者可以及时地接收到属性值的变化通知,并且在被观察对象销毁时,可以自动移除所有的观察者,避免内存泄漏。

3.5.1 NSKeyValueObservationForwarder的转发机制

NSKeyValueObservationForwarder 类中的主要方法是 observeValueForKeyPath:ofObject:change:context: 和 dealloc。observeValueForKeyPath:ofObject:change:context: 方法用于在被观察对象的属性值发生变化时,通知所有的观察者。dealloc 方法用于在被观察对象销毁时,移除所有的观察者。

例如 对于keyPath的访问过程,按照key1.key2.key3的路径访问 NSKeyValueObservationForwarder会完成如下事情

请添加图片描述
其中观察者会依次相互引用

- (void) observeValueForKeyPath: (NSString *)keyPath
                       ofObject: (id)anObject
                         change: (NSDictionary *)change
                        context: (void *)context
{
  if (anObject == observedObjectForUpdate)
    {
      [self keyPathChanged: nil];
    }
  else
    {
      [target observeValueForKeyPath: keyPathToForward
                            ofObject: observedObjectForUpdate
                              change: change
                             context: contextToForward];
    }
}

4 KVO总结

  • 主要用了isa-swizzling,修改了观察者的类信息,并且hooksetter方法,当setter方法调用时发送消息给所有观察者
  • 观察者、被观察者的引用都是Not Retain, 所以对象释放前一定要移除观察者。
  • 消息的发送主要由[self willChangeValueForKey: key], [self didChangeValueForKey: key]触发,并且必须成对出现,automaticallyNotifiesObserversForKey方法用来控制,是否要主要添加上述的两个方法,默认返回值为YES,如果返回NO则不会自动添加,也就是说setter的调用以及KVC修改都不会触发通知
  • KVO内部多次用到了KVC
    1️⃣ 重写 setValue:forKey
    2️⃣ 使用valueForKey --- valueForKeyPath获取属性的值,尤其是在使用点语法的时候,只有valueForKeyPath可以获得深层次的属性值。

所以KVO是基于KVC而实现的。

5 KVO和KVC的联系

KVO 和 KVC 都是 Cocoa 框架中的重要机制,它们可以帮助开发者更加方便地操作对象的属性和状态。

  • KVO(Key-Value Observing)是一种基于观察者模式的机制,用于监听对象属性的变化。当一个对象的属性发生变化时,系统会自动通知所有观察这个属性的对象,让它们能够及时地获取到最新的属性值。KVO 的实现依赖于 Objective-C 的动态特性,通过 isa-swizzling 技术实现。

KVO 的使用非常简单,只需要在被观察对象上调用 addObserver:forKeyPath:options:context: 方法,指定观察者对象和被观察的属性,就可以开始监听属性的变化了。当属性的值发生变化时,系统会自动调用观察者对象的 observeValueForKeyPath:ofObject:change:context: 方法,将变化的信息传递给观察者。

  • KVC(Key-Value Coding)是一种通过键值来访问对象属性的机制。通过 KVC,开发者可以直接使用字符串来访问对象的属性,而不需要手动调用 getter 和 setter 方法。KVC 的实现依赖于 Objective-C 的 Runtime 特性,通过方法调用和消息转发机制实现。

KVC 的使用非常灵活,可以用于访问普通对象的属性、集合对象的元素、属性路径等。开发者只需要使用字符串指定要访问的属性或者属性路径,就可以获取或者设置对象的属性值了。KVC 还支持一些高级的特性,比如自动集合操作、键值验证、键值观察等。

KVO 和 KVC 都是 Cocoa 框架中非常重要的机制,它们可以帮助开发者更加方便地操作对象的属性和状态。KVO 主要用于监听对象属性的变化,而 KVC 主要用于通过键值来访问对象属性。在实际开发中,我们通常会同时使用 KVO 和 KVC,以便更好地处理对象的状态和变化

总结

参考
KVO的结构和源码内部实现
KVO KVC的八股文
KVC/KVO的实现

KVC学习了主要的实现过程,KVO的学习是先学会实现的过程,调用什么方法,熟悉过程。然后学习KVO的源码的内部实现才能看懂对应的源码过程。

KVO的学习比较KVC更为复杂,KVO的内部结构和源码更为复杂,2天的学习完全不够 还需要重写阅读博客一步一步在学习过程

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

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

相关文章

Linux + 香橙派 + V4L2 + http 实现远程监控摄像头在网页端显示

项目场景&#xff1a; 项目需求&#xff0c;需要做一个基于边缘端的人脸识别远程监控摄像头并在网页前端展示 &#xff0c;这里采用国产香橙派作为边缘计算终端&#xff0c;安装ubuntu系统&#xff0c;系统中采用v4l2接口对摄像头进行获取&#xff0c;当客户端通过网页进行请求…

oracle 数据库创建表空间、用户、数据库实例、授权、数据导入

在安装完oracle 数据库后默认会创建一个orcl的数据库实例。除了这种方式外还可以通过别的方式创建数据库 一般导入数据通过以下五个步骤 创建导入的数据库实例&#xff0c;已有的话可以忽略创建表空间 2.1 创建临时表空间 2.2 创建数据表空间创建用户给用户授权导入数据库数据…

Camtasia 2023新功能添加了新的视觉效果、滤镜和其他重大改进

总部位于密歇根州的 TechSmith 推出了Camtasia 2023&#xff0c;这是其适用于 Mac 和 Windows 的专业屏幕录制和视频编辑工具的重要新版本。 2023 版本侧重于两个特定领域&#xff1a;提供增强的视觉效果和实现各种用户功能请求。 Camtasia 2023 的亮点包括能够在截屏视频中实…

基于Vue的个人网站的设计与实现

统是基于面向对象编程的web应用程序。主要实现的功能有前台的在线留言、音乐播放、下载、收藏、用户注册和后台的背景音乐管理、相册信息管理、网站新闻管理、留言管理等功能。 设计开发一个更能满足网民需求的交流载体&#xff0c;使大家在网上可以提供学习交流分享的平台&…

【Linux】Linux下安装Mysql(图文解说详细版)

文章目录 前言第一步&#xff0c;进到opt文件夹下面&#xff0c;为什么&#xff1f;因为opt文件夹相当于Windows下的D://software第二步&#xff0c;用yum安装第三步&#xff0c;设置mysql的相关配置第四步&#xff0c;设置远程连接。第五步&#xff0c;更改mysql的语言第六步&…

聚观早报|拼多多回应总部迁爱尔兰;微软宣布全面开放聊天机器人

今日要闻&#xff1a;拼多多回应总部迁至爱尔兰&#xff1b;京东60亿元在京建4000套员工房&#xff1b;微软宣布全面开放必应聊天机器人&#xff1b;一加首款可折叠手机将于8月推出&#xff1b;TikTok推出针对出版商的广告产品 拼多多回应总部迁至爱尔兰 5 月 4 日消息&#x…

服务远程调用、ribbon负载均衡、nacos注册中心

1.服务拆分和远程调用 任何分布式架构都离不开服务的拆分&#xff0c;微服务也是一样。 1.1.服务拆分原则 这里我总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将…

Nacos原理(注册中心和配置中心)

服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务&#xff0c;原则上都应存在或者支持多个提供者&#xff0c;这是由微服务的分布式属性决定的。更进一步&#xff0c;为了支持弹性扩缩容特性&#xff0c;一个微服务的提供者的数量和分布往往是动态变化…

【面试系列】如何保证接口的幂等性

为什么需要实现幂等性 在接口调用时一般情况下都能正常返回信息不会重复提交&#xff0c;不过在遇见以下情况时可以就会出现问题&#xff0c;如&#xff1a; 前端重复提交表单&#xff1a;在填写一些表格时候&#xff0c;用户填写完成提交&#xff0c;很多时候会因网络波动没…

C++之编译链接

目录 线程基础静态链接静态链接装载与动态链接库与运行库 关于隔离&#xff1a; 物理地址是实在存在的虚拟地址是虚拟的&#xff0c;并不存在&#xff0c;每个进程都有自己独立的虚拟空间&#xff0c;而且每个进程只能访问自己的地址空间&#xff0c;这样就有效地做到了进程的…

字符设备驱动开发

1、字符设备驱动简介 字符设备是 Linux 驱动中最基本的一类设备驱动&#xff0c;字符设备就是一个一个字节&#xff0c;按照字节 流进行读写操作的设备&#xff0c;读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI&#xff0c; LCD 等等都是字符设备&#xff0c…

不断积累,步步高升:记录我的Python学习历程-python中f-string介绍

最近在看python基础的时候&#xff0c;又发现一个之前没怎么掌握的字符串格式化的小技巧&#xff0c;f-string f-string 格式化 f-string是Python3.6引入的一种字符串格式化方式&#xff0c;它提供了一种简洁、直观的方式来将变量值嵌入到字符串中。在 f-string 中&#xff0c;…

2022 Hubei Provincial Collegiate Programming Contest B. Potion(easy version)

题目链接 Output For each testcase, if Twilight Sparkle couldn’t make the specific mixture, print a single integer: −1. Otherwise, print the minimum number of operation 1 to do that. Example input 3 3 5 1 1 2 6 1 1 5 7 1 1 output 4 3 -1 题目大意 题目保证…

顺序表---(数据结构的开始)

目录 前言&#xff1a; 1.线性表的性质 2.静态数组or动态数组 2.1静态数组 2.2动态数组 3.结构体的创建 4*接口函数的详细讲解 4.1初始化结构体 4.2尾插 4.3打印数据 4.4用完后销毁创建的堆空间 4.5 尾删 4.6头插 4.7头删 4.8查找 4.9任意位置插入 4.10任意位…

正则表达式规则

元字符&#xff1a; 正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思&#xff0c;他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思。以下是一些元字符的介绍&#xff1a; 运算符优先级&#xff1a; 正则表达式从左到右进行计算&#xff0c;…

idea一些不常见但是非常有用的sql

1.这个功能适合生产环境遇到故障&#xff0c;然后本地&#xff0c;但是前提是jvm配置了如下参数&#xff0c;还有一个就是要保证线上代码和本地的一致&#xff0c;这样就可以本地调式远程代码 在linux 运行 java -agentlib:jdwptransportdt_socket,servery,suspendn,address500…

牛客 BM19 寻找峰值

描述 给定一个长度为n的数组nums&#xff0c;请你找到峰值并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回任何一个所在位置即可。 1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于 2.假设 nums[-1] nums[n] -∞ 3.对于所有…

中台产品经理01:中台落地工具MSS模型

众所周知&#xff0c;每家企业的内部经营管理都是大相径庭的&#xff0c;就算相同行业的两家企业其内部也会有显著的特殊性&#xff0c;因此企业对于自身中台的建设需求也一定是不同的&#xff0c;可以说中台建设必须是为企业量身定制的。 而在每家企业的中台建设中&#xff0…

Visual Studio 2022 搭建GLFW OpenGL开发环境

最近工作需要 需要写一个全景的视频播放器 网上搜了下大概解决方案是 ffmpegopengl b站有很多视频 按照视频 搭建了OpenGL的开发环境 先去GLFW的网站下载 windows平台的库文件 为什么使用GLFW 因为GLFW是跨平台的 我下的是64位版本解压后有目录如下 包含了动态库和静态…

医院不良事件管理系统源码,PHP语言开发,填写上报、流转审批、发生原因分析定位

医疗安全不良事件管理系统源码提供不良事件的上报、事件审核处理、时间按分析、事件跟踪与持续改进&#xff0c;事件提醒、权限控制、外部上报等功能。从报告内容填写上报、流转审批、发生原因分析定位、处置对策的制定、统计汇总等方面&#xff0c;提供了不良事件处理的全过程…