【iOS-分类,拓展和关联对象底层探究】

news/2024/4/24 12:59:20/文章来源:https://blog.csdn.net/weixin_61639290/article/details/130349820

前言

寒假分享会问题解决二

请添加图片描述
早在大一的OC的学习过程就知道了分类和拓展的区别和联系,分类不能添加成员变量,而拓展可以添加成员变量。分类是在运行时期实现的,而拓展只是编译器的时候就实现了。对于分类我们可以通过关联对象来为我们需要的分类添加成员变量及其实现。

分类和拓展的使用创建就不过多叙述,这里讲解之前的问题?结论是如何得出的?

1 分类的基本介绍

1.1 分类Category

分类是一种在现有类中添加方法的方式。使用分类,可以将一个类的功能分为多个逻辑部分,使得代码更加清晰、易于维护。

  • 可以为任意一个类添加方法,包括系统自带的类。
    • 创建PersonC和它的分类,在分类实现方法,在本类的对象调用
      请添加图片描述
      分类也遵循公开和私有方法,分类.h
@interface PersonC (p_Category)
- (void)p_category;
- (void)initPerson;@end

分类m

#import "PersonC+p_Category.h"@implementation PersonC (p_Category)
- (void)p_category {NSLog(@"这是PersonC的Category添加的方法");
}

在ViewComtroller调用

#import "ViewController.h"
#import "PersonC.h"
#import "PersonC+p_Category.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.PersonC *personC = [[PersonC alloc] init];[personC p_category];

请添加图片描述

  • 可以为类添加实例方法和类方法。
  • 分类中的方法可以访问原类的所有成员变量和方法。
    分类可以初始化原类成员变量,在原类里有PersonC变量,在分类方法初始化

本类h

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface PersonC : NSObject
@property (nonatomic, strong) id personC;
- (void)coverPerson_C;
@endNS_ASSUME_NONNULL_END

分类m

- (void)initPerson {self.personC = @"Category";NSLog(@"在Category设置原类的变量");NSLog(@"%@", self.personC);
}

请添加图片描述

  • 分类中的方法可以覆盖原类中同名的方法。
    分类方法的覆盖不需要在分类的h文件声明,你一旦重写了方法编译器就会自己调用新的覆盖方法
    请添加图片描述
- (void)coverPerson_C {NSLog(@"未被覆盖");
}
- (void)coverPerson_C {NSLog(@"这是被覆盖的方法");
}

请添加图片描述

  • 分类不能添加属性/成员变量,有属性列表, 所以分类可以声明属性 没有成员变量列表,不能声明成员变量,但是分类只会生成该属性对应的get和set的声明,没有去实现该方法
    请添加图片描述

请添加图片描述

1.2 分类结构体决定了什么?

在objc834可编译源码找到了分类的结构体

查询知道了这些结构体的成员变量或者方法代表了什么意思

struct category_t {const char *name; // 类别的名称classref_t cls; // 指向类别所属的类的指针WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods; // 类别中实例方法列表的指针,类型为WrappedPtr<method_list_t, method_list_t::Ptrauth>WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods; // 类别中类方法列表的指针,类型为WrappedPtr<method_list_t, method_list_t::Ptrauth>。struct protocol_list_t *protocols; // 类别中协议列表的指针,类型为struct protocol_list_t *。struct property_list_t *instanceProperties; // 类别中实例属性列表的指针,类型为struct property_list_t *。// Fields below this point are not always present on disk.struct property_list_t *_classProperties;  //类别中类属性列表的指针,类型为struct property_list_t *。这个字段只存在于某些特定的情况下。// 根据传入的参数isMeta决定返回实例方法列表还是类方法列表。method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;else return instanceMethods;}
// 根据传入的参数isMeta和hi,返回实例属性列表或类属性列表。hi表示当前二进制文件的头信息,用于判断是否需要解密。property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);// 根据传入的参数isMeta,返回协议列表。protocol_list_t *protocolsForMeta(bool isMeta) {if (isMeta) return nullptr;else return protocols;}
};

分类不能添加实例变量,这是由于分类的实现机制所决定
分类在编译期间并不会将实例变量的定义合并到类的定义中,因此无法在运行时为类的实例添加实例变量

1.3 关联对象的引入

关联对象是Objective-C提供的一种机制,可以在运行时为对象添加任意的键值对,因此可以用关联对象来模拟实例变量的效果
分类只能用关联对象添加属性,结构体category_t中的instanceProperties字段表示类别中的实例属性列表,而没有类别中的实例变量列表。

2.1 拓展 Extension

拓展是一种在编译期间为类添加实例变量和属性的方式。使用拓展,可以在不继承原有类的前提下,给类添加一些额外的状态和属性。拓展的特点包括:

  • 只能为自己编写的类添加实例变量和属性,无法为系统自带的类添加。
  • 只能为类添加实例变量和属性,无法添加方法。
  • 扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对系统的类使用扩展。
  • 可以为待扩展的类添加额外的 属性 变量 和方法声明
    • 注意私有属性写在类扩展
    • 扩展可以添加属性和成员变量
    • 扩展是本身没有自己的实现的,它和本类共享一个实现

3 关联对象

**关联对象是在运行时为对象动态添加键值对的一种机制。使用 objc_setAssociatedObject 函数可以为对象设置关联对象,即将指定的键值对添加到对象的关联对象表中。**该函数将关联对象与对象相关联,使得在后续的程序执行中,可以通过指定的键获取对象的关联对象值,从而实现一些额外的功能。
需要注意的是,**由于关联对象是在运行时动态添加的,因此会对对象的内存占用和性能产生一定的影响。**因此,应该尽可能地避免滥用关联对象,只在必要的时候使用。同时,在使用关联对象时,需要注意内存管理和线程安全问题,以避免程序出现意外行为。

3.1 关联对象的使用场景

  • 给分类添加属性:在Objective-C中,分类不能添加实例变量,因此也无法添加属性。但可以使用关联对象在运行时为分类对象添加属性,以实现类似属性的功能。(重点)
  • 给系统类添加属性:有时需要给系统类添加一些额外的属性,但这些类是不允许修改的,比如UIView、UIViewController等。这时可以使用关联对象来给这些类添加属性。
  • 为某些对象添加状态信息:有时需要为某些对象添加一些状态信息,但又不想将这些信息放在对象本身中,因为这会导致对象变得臃肿。这时可以使用关联对象来为对象添加状态信息,以避免污染对象本身的属性列表。
  • 拓展第三方库的功能:有时需要给第三方库中的类添加一些额外的功能或属性,但又不能修改这些类的源代码。这时可以使用关联对象来拓展这些类的功能,以实现自己的需求。

3.2 关联对象API

关联对象的实现,主要分为两部分:

  • 通过objc_setAssociatedObject设值流程
  • 通过objc_getAssociatedObject取值流程
  • 通过objc_removeAssociatedObjects移除关联对象

在objc843的可编译源码查找任意一个API 发现他们离的很近。
请添加图片描述

id
objc_getAssociatedObject(id object, const void *key)
{return _object_get_associative_reference(object, key);
}typedef void (*objc_hook_setAssociatedObject)(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy);void
objc_setHook_setAssociatedObject(objc_hook_setAssociatedObject _Nonnull newValue,objc_hook_setAssociatedObject _Nullable * _Nonnull outOldValue) {// See objc_object::setHasAssociatedObjects() for a replacement
}void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{_object_set_associative_reference(object, key, value, policy);
}void objc_removeAssociatedObjects(id object) 
{if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object, /*deallocating*/false);}
}

3.2.3 objc_setHook

  • 看到了一个陌生的面孔objc_setHook_setAssociatedObject,简单说一下带过。
IMP objc_setHook(Class cls, SEL selector, IMP newImplementation);
  • cls参数是要替换方法的类,selector参数是要替换的方法名,newImplementation参数是新的方法实现。该函数会将原始方法的实现保存起来,并将新的方法实现设置为该方法的实现。
  • objc_setHook 函数只能替换类中的实例方法,而不能替换类方法。如果需要替换类方法,可以使用 class_replaceMethod 函数

3.2.3 objc_setAssociatedObject -设值

在讲取值之前看一下如何使用关联对象的方法实现分类添加属性
在分类的h文件设置一个 my_name属性
请添加图片描述
在m文件里面重写set get方法,使用runtime的方法,记得添加#import <objc/runtime.h>头文件
请添加图片描述

在viewController即可当成正常Person的某个属性调用
请添加图片描述

看看 objc_setAssociatedObject

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{_object_set_associative_reference(object, key, value, policy);
}

使用 objc_setAssociatedObject 函数可以为对象设置关联对象,即将指定的键值对添加到对象的关联对象表中, 那么const void *key 和 value就是对应的键值对。

  • object: 要关联的对象,即给谁添加关联属性
  • const void *key:标识符,方便下次查找
  • id value:value
  • objc_AssociationPolicy policy:属性的策略,即nonatomic、atomic、assign、copy等

仔细观察,第四个参数的结构体里面出现了 和内存管理相关的关键字。

policyobjc_AssociationPolicy 类型的枚举值,用于指定关联对象的内存管理策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. *   The association is not made atomically. */OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. *   The association is not made atomically. */OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.*   The association is made atomically. */OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.*   The association is made atomically. */
};
  • OBJC_ASSOCIATION_ASSIGN:表示使用弱引用来关联对象。当关联对象所属的对象释放时,关联对象也会被自动释放。
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC:表示使用强引用来关联对象,并且不考虑多线程安全问题。当关联对象所属的对象释放时,关联对象会被自动释放。
  • OBJC_ASSOCIATION_COPY_NONATOMIC:表示使用复制来关联对象,并且不考虑多线程安全问题。当关联对象所属的对象释放时,关联对象会被自动释放。
  • OBJC_ASSOCIATION_RETAIN:表示使用强引用来关联对象,并且考虑多线程安全问题。当关联对象所属的对象释放时,关联对象会被自动释放。
    需要注意多线程安全问题,在不同线程中访问关联对象时,需要采取相应的线程安全措施

3.2.4 objc_setAssociatedObject的底层源码

点进去 objc_setAssociatedObject的实现发现还有一个方法。

请添加图片描述

_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)

这两个函数的参数看起来很像的样子,但是系统在外部调用objc_setAssociatedObject的原因是_object_set_associative_reference是一个私有的方法,而objc_setAssociatedObject则属于外部的公共接口,这提示我们尽量避免直接调用私有的函数/方法。

在这个函数的第四个参数 使用了uintptr_t

请添加图片描述
uintptr_t 是一种无符号整数类型,它的大小足以存储指针类型变量的值,通常被用来在不同的数据类型之间进行指针类型的转换

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{// This code used to work when nil was passed for object and key. Some code// probably relies on that to not crash. Check and handle it explicitly.// rdar://problem/44094390if (!object && !value) return;
//判断runtime版本是否支持关联对象if (object->getIsa()->forbidsAssociatedObjects())_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));// 将 object 封装成 DisguisedPtr 目的是方便底层统一处理DisguisedPtr<objc_object> disguised{(objc_object *)object};// 将 policy和value 封装成ObjcAssociation,目的是方便底层统一处理ObjcAssociation association{policy, value};// retain the new value (if any) outside the lock.// 根据policy策略去判断是进去 retain 还是 copy 操作association.acquireValue();bool isFirstAssociation = false;//用来判断是否是,第一次关联该对象{// 实例化 AssociationsManager 注意这里不是单例AssociationsManager manager;// 实例化 全局的关联表 AssociationsHashMap 这里是单例AssociationsHashMap &associations(manager.get());if (value) {// AssociationsHashMap:关联表 ObjectAssociationMap:对象关联表// 首先根据对象封装的disguised去关联表中查找有没有对象关联表// 如果有直接返回结果,如果没有则根据`disguised`去创建对象关联表// 创建ObjectAssociationMap时当(对象的个数+1大于等于3/4,进行两倍扩容)
try_emplace方法的作用就是去表中查找Key相应的数据,不存在就创建:auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});if (refs_result.second) {/* it's the first association we make */// 表示第一次关联该对象isFirstAssociation = true;}/* establish or replace the association */// 获取ObjectAssociationMap中存储值的地址auto &refs = refs_result.first->second;// 将需要存储的值存放在关联表中存储值的地址中// 同时会根据key去查找,如果查找到`result.second` = false ,如果找不到就创建`result.second` = true// 创建association时,当(association的个数+1)超过3/4,就会进行两倍扩容auto result = refs.try_emplace(key, std::move(association));if (!result.second) {// 交换association和查询到的`association`// 其实可以理解为更新查询到的`association`数据,新值替换旧值association.swap(result.first->second);}} else { // value没有值走else流程// 查找disguised 对应的ObjectAssociationMapauto refs_it = associations.find(disguised);// 如果找到对应的 ObjectAssociationMap 对象关联表if (refs_it != associations.end()) {// 获取 refs_it->second 里面存放了association类型数据auto &refs = refs_it->second;// 根据key查询对应的associationauto it = refs.find(key);if (it != refs.end()) {// 如果找到,更新旧的association里面的值association.swap(it->second);// value= nil时释放关联对象表中存的`association`refs.erase(it);if (refs.size() == 0) {// 如果该对象关联表中所有的关联属性数据被清空,那么该对象关联表会被释放associations.erase(refs_it);}}}}}// Call setHasAssociatedObjects outside the lock, since this// will call the object's _noteAssociatedObjects method if it// has one, and this may trigger +initialize which might do// arbitrary stuff, including setting more associated objects.// 首次关联对象调用setHasAssociatedObjects方法// 通过setHasAssociatedObjects方法`标记对象存在关联对象`设置`isa指针`的`has_assoc`属性为`true`if (isFirstAssociation)object->setHasAssociatedObjects();// release the old value (outside of the lock).// 释放旧值因为如果有旧值会被交换到`association`中// 原来`association`的新值会存放到对象关联表中association.releaseHeldValue();
}

在对象的isa结构体里有这样一个指针变量用来判断该对象是否含有关联对象uintptr_t has_assoc 为1则是有
在这里插入图片描述
就是在下面的代码实现的

// 首次关联对象调用setHasAssociatedObjects方法
// 通过setHasAssociatedObjects方法`标记对象存在关联对象`设置`isa指针`的`has_assoc`属性为`true`
if (isFirstAssociation)object->setHasAssociatedObjects();

请添加图片描述

过程总结
_object_set_associative_reference方法主要有下列两步操作:

  • 根据object在全局关联表(AssociationsHashMap)中查询ObjectAssociationMap,如果没有就去开辟内存创建ObjectAssociationMap
  • 将根据key查询到相关的association(即关联的数据 value和policy),如果查询到直接更新里面的数据,如果没有则去获取空的asociation类型然后将值存放进去

代码如何理解

  • 创建一个AssociationsManager 管理类,获取唯一全局静态哈希Map
  • 判断是否存在关联对象值:try_emplace方法的作用就是去表中查找Key相应的数据,不存在就创建
    • 存在 :创建一个空的 ObjectAssociationMap 去取查询的键值对,如果发现没有这个 key 就先插入一个 空的 BucketT标记对象存在关联对象,用当前 策略 policy 和 值 value 组成了一个 ObjcAssociation 替换之前空的BucketT
      标记 ObjectAssociationMap 为 第二次
      ( LookupBucketFor这个方法就是 根据Key去表中查找Bucket,如果已经缓存过,返回true,否则返回false)
    • 不存在 :
      根据DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器->清理迭代器

关联对象的数据结构可以理解为是两层map的调用(截取别人的图)

关联对象的数据结构

3.2.5 objc_setAssociatedObject取值

取值流程的原型如下请添加图片描述
objc_getAssociatedObject调用了_object_get_associative_reference。进入_object_get_associative_reference方法,关联对象取值就是比较简单的了就是查表

要能看懂取值和设值的源码,上面的关联对象的数据结构的图片还是比较重要的

id
_object_get_associative_reference(id object, const void *key)
{// 创建空的关联对象ObjcAssociation association{};{// 实例化 AssociationsManager 注意这里不是单例AssociationsManager manager;// 实例化 全局的关联表 AssociationsHashMap 这里是单例AssociationsHashMap &associations(manager.get());// iterator是个迭代器,实际上相当于找到object和对应的ObjectAssociationMap(对象关联表)AssociationsHashMap::iterator i = associations.find((objc_object *)object);if (i != associations.end()) {// 获取ObjectAssociationMap(对象关联表)ObjectAssociationMap &refs = i->second;// 迭代获取key对应的数据ObjectAssociationMap::iterator j = refs.find(key);if (j != refs.end()) {// 获取 associationassociation = j->second;// retain 新值association.retainReturnedValue();}}}// release旧值,返回新值return association.autoreleaseReturnedValue();
}
  • 创建一个 AssociationsManager 管理类,获取唯一的全局静态哈希Map
  • 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器,如果这个 迭代查询器 != associations.end(), 即不是最后一个, 那么获取 : ObjectAssociationMap (这里有策略 policy 和 值 value)
    ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value,release旧值,返回新值。

3.2.6 objc_removeAssociatedObjects 移除关联对象

函数原型如图所示
请添加图片描述

  • 通常情况下,我们不需要手动调用 _object_remove_assocations 方法来移除对象的关联对象。Objective-C 运行时框架会在对象释放时自动移除其所有的关联对象。
  • 当一个对象的引用计数变为 0 时,系统会调用 dealloc 方法释放对象内存空间。在 dealloc 方法中,会调用 _object_remove_assocations 函数来移除对象的所有关联对象。因此,我们通常无需手动调用该函数。
  • 需要注意的是,在使用关联对象时,如果我们设置了对象的关联对象为弱引用(OBJC_ASSOCIATION_ASSIGN 或 OBJC_ASSOCIATION_WEAK),则在关联对象所引用的对象被释放时,关联对象的值会自动被设置为 nil。因此,在这种情况下,我们不需要手动调用 _object_remove_assocations 函数来移除关联对象。

调用流程:dealloc --> _objc_rootDealloc --> rootDealloc --> object_dispose --> objc_destructInstance --> _object_remove_assocations

// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
// 与设置/获取关联引用不同,此函数对性能敏感,因为原始isa对象(如OS对象)不能跟踪它们是否有关联对象。
void
_object_remove_assocations(id object, bool deallocating)
{ObjectAssociationMap refs{};{AssociationsManager manager;AssociationsHashMap &associations(manager.get());AssociationsHashMap::iterator i = associations.find((objc_object *)object);if (i != associations.end()) {refs.swap(i->second);// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.//如果我们没有回收,那么SYSTEM_OBJECT关联会被保留。bool didReInsert = false;if (!deallocating) {for (auto &ref: refs) {if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {i->second.insert(ref);didReInsert = true;}}}if (!didReInsert)associations.erase(i);}}// Associations to be released after the normal ones.// 在正常关联之后释放关联。SmallVector<ObjcAssociation *, 4> laterRefs;// release everything (outside of the lock).// 释放锁外的所有内容。for (auto &i: refs) {if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {// If we are not deallocating, then RELEASE_LATER associations don't get released.//如果我们不是在释放,那么RELEASE_LATER关联不会被释放if (deallocating)laterRefs.append(&i.second);} else {i.second.releaseHeldValue();}}for (auto *later: laterRefs) {later->releaseHeldValue();}
}

总结

  • 关联对象其实就是 ObjcAssociation 对象
  • 关联对象由AssociationsManager管理并在 AssociationsHashMap 存储
  • 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap 中
  • ObjectAssociationMap 则是用于存储关联对象的数据结构

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

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

相关文章

线程池四种拒绝机制 实现 及执行日志

目录 目录 目录 创建线程池 测试代码 运行线程 全量代码 日志 AbortPolicy 报出异常模式 DiscardPolicy 放弃机制啥也不处理 DiscardOldestPolicy 放弃机制&#xff0c;放弃列队最早进入的 CallerRunsPolicy 交给主线程执行 创建线程池 public static ExecutorServi…

项目范围控制:如何控制项目范围的变化?

一个成功的项目需要在进度、成本和质量之间取得平衡。控制项目交付范围是实现这个平衡的关键。然而&#xff0c;项目范围是会变化的&#xff0c;因此控制项目范围变化是必要的。 如何控制项目范围的变化&#xff1f; 1、了解项目的交付范围 项目经理、团队成员、利益相关者和…

我用什么写Python?

入门教程、案例源码、学习资料、读者群 请访问&#xff1a; python666.cn 大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 通常来说&#xff0c;每个程序员都有自己趁手的兵器&#xff1a;代码编辑器。你要是让他换个开发环境&#xff0c;恐怕开发效率至少下降三成…

c/c++:char*定义常量字符串,strcmp()函数,strcpy()函数,寻找指定字符,字符串去空格

c/c&#xff1a;char*定义常量字符串&#xff0c;strcmp()函数&#xff0c;strcpy()函数&#xff0c;寻找指定字符&#xff0c;字符串去空格 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的话&#xff0c; 我所…

Python爬虫基础之二

Python爬虫基础包括HTTP协议、HTML、CSS和JavaScript语言基础、requests库的使用、Beautiful Soup库的使用、xpath和正则表达式的使用等。此外&#xff0c;还应该了解反爬虫机制和爬虫的一些常见问题及解决方法。 上一篇文章讲解了有关条件判断语句、循环语句等相关知识&#…

陆奇-奇绩创坛-chatGPT新范式,新时代,新机会

奇绩创坛-新范式&#xff0c;新时代&#xff0c;新机会 01-新范式 新范式的新拐点 新范式的历史环境 新范式的社会影响 新范式的缔造者&#xff1a;Sam Altman和OpenAI 新范式的动力引擎 新范式的演化路径 02-新时代 新时代的宏观发展格局 新时代的中国机会 新时代的OpenAI生…

IT项目管理之软件测试

1. 定义 软件测试是使用人工或者自动的手段来运行或者测定某个软件系统的过程&#xff0c;其目的在于检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别。 在软件投入使用前&#xff0c;要经过一系列的严格测试&#xff0c;才能保证交付质量。 2. QC & QA &a…

开源模型ModelScope的初探使用

泛AI开发者的一站式模型服务产品平台 阿里达摩院推出了一个开源的模型共享平台&#xff0c;包括计算机视觉、多模态、自然语言处理等多个领域上手即用的模型&#xff0c;如果AI相关模型感兴趣的同学&#xff0c;或者想基于基础模型做业务场景的同学&#xff0c;都可以用这个平…

C++三大特性—继承 “访问控制”

本文主要阐述关于C继承中基类与派生类之间的访问关系 继承方式与访问方式 继承定义格式&#xff1a; 派生类可以继承定义在基类的成员&#xff0c;但是派生类的成员函数不一定有权访问从基类继承来的成员    访问限定符的作用&#xff1a;控制派生类从基类继承而来的成员是否…

学习系统编程No.23【信号实战】

引言&#xff1a; 北京时间&#xff1a;2023/4/23&#xff0c;最近学习状态不怎么好&#xff0c;总是犯困&#xff0c;没精力的感觉&#xff0c;可能是病没有好彻底的原因&#xff0c;也可能是我内心因为生病而认为摆烂理所应当&#xff0c;反正最后导致摆烂&#xff0c;课现在…

android之 Launcher改造仿桌面排版的效果

一&#xff0c;背景 1.1 新接手一个灯光控制项目&#xff0c;其页面和效果还是比交复杂的&#xff0c;其中一个功能就是仿苹果桌面来排版灯具&#xff0c;支持拖拽&#xff0c;分组&#xff0c;分页。 拖动图标的时候判断是否空白位置还是已经有占位了&#xff0c;有的话就把…

体验了多款国产类ChatGPT产品后,我选择了道合顺的【ChatIC】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

springboot实用配置

springboot实用配置 &#xff08;一&#xff09;打包与运行&#xff08;二&#xff09;配置高级1.临时属性设置2.配置文件分类3.自定义配置文件 &#xff08;三&#xff09;多环境开发&#xff08;四&#xff09;日志1.日志基础2.日志输出格式控制3.日志文件 &#xff08;一&am…

14、RPC与gRPC

目录 一、rpc基础1 - rpc入门2 - 基础的rpc通信3 - 基于接口的RPC服务 二、rpc编码1 - gob编码2 - json on tcp3 - json on http&#xff08;待补充&#xff09; 三、prtotobuf编码1 - prtotobuf概述2 - protobuf编译器3 - 序列化和反序列化4 - 基于protobuf的RPC&#xff08;待…

vue项目 解决el-table自适应高度,vue页面不显示多条滚动条,超出的部分让el-table内部出现滚动条(推荐使用第二种解决方案)

一、需求 后台管理系统&#xff1a;最常见的页面都是由—>左侧菜单、头部tabView页签、主体数据渲染页面&#xff08;AppMain&#xff09;&#xff1b;而一般AppMain页面又分为&#xff1a; 搜索区域、table数据&#xff08;分页&#xff09;&#xff0c;可能也会存在底部&a…

QGIS数据可视化学习笔记01——一般的图层样式调整和在地图上添加图表

说明&#xff1a;QGIS数据可视化章节用的数据为QGIS绘制一张地图学习章节的数据&#xff0c;传送门&#xff1a;https://blog.csdn.net/qq_47188967/article/details/130196284 一、 一般的图层样式调整 首先呢&#xff0c;在QGIS中打开上一章节用的数据&#xff0c;界面如下&…

基于飞桨 PaddleVideo 的骨骼行为识别模型 CTR-GCN

main.pysame_seedsparse_argsmain ensemble.pyconfigs 文件夹Joint&#xff08;J&#xff09;的配置文件ctrgcn_fsd_J_fold0.yamlctrgcn_fsd_J_fold1.yaml Joint Angle&#xff08;JA&#xff09;的配置文件ctrgcn_fsd_JA_fold0.yaml paddlevideo 文件夹utils 文件夹__init__.p…

MATLAB符号运算(七) 更新中...

目录 1、实验目的&#xff1a; 2、实验内容&#xff1a; 1、实验目的&#xff1a; 1&#xff09;掌握定义符号对象和创建符号表达式的方法&#xff1b; 2&#xff09;掌握符号运算基本命令和规则&#xff1b; 3&#xff09;掌握符号表达式的运算法则以及符号矩阵运算&#xf…

[JavaEE初阶] 类加载机制

在真正的战争到来之前,尽可能地变得强大吧~ 文章目录 前言1. 类加载1.1 类加载的过程1.2 类加载的时机1.3 双亲委派模型 前言 这个问题是面试经典题,让我们来求甚解吧~ 1. 类加载 1.1 类加载的过程 如下图 加载,找到.class文件,读取文件内容验证,验证.class文件的格式是否…

SQL优化(3):order by优化

MySQL的排序&#xff0c;有两种方式&#xff1a; Using filesort : 通过表的索引或全表扫描&#xff0c;读取满足条件的数据行&#xff0c;然后在排序缓冲区sort buffer中完成排序操作&#xff0c;所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。 Using index :…