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

news2024/12/25 15:01:33

前言

寒假分享会问题解决二

请添加图片描述
早在大一的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;
@end

NS_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/44094390
    if (!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 对应的ObjectAssociationMap
            auto refs_it = associations.find(disguised);
            // 如果找到对应的 ObjectAssociationMap 对象关联表
            if (refs_it != associations.end()) {
                // 获取 refs_it->second 里面存放了association类型数据
                auto &refs = refs_it->second;
                // 根据key查询对应的association
                auto 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()) {
                // 获取 association
                association = 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.coloradmin.cn/o/463010.html

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

相关文章

什么蓝牙耳机好?经销商分享线下热销蓝牙耳机品牌排行

开实体店铺多年&#xff0c;对数码行业多有研究&#xff0c;每天都会试用各种数码产品。网友们在发帖咨询什么蓝牙耳机好&#xff0c;为此我整理了实体店铺最受欢迎的蓝牙耳机品牌排行&#xff0c;大家在选购时可以作为备选&#xff1a; 第一款&#xff1a;JEET Air2蓝牙耳机 …

Mac M1/M2 安装nvm管理多版本node

Mac M1/M2 安装nvm管理多版本node Mac m1、Mac&#xff0c;Ventura 13.2&#xff0c;M2安装NVM使用homebrew安装nvm安装NodeMac nvm install failed python: not found解决方法(终端中执行以下命令)&#xff1a; Mac m1、Mac&#xff0c;Ventura 13.2&#xff0c;M2安装NVM 使…

2023最新XXL-JOB定时器教程

1.创建一个名为xxl_job的数据库,执行sql # # XXL-JOB v2.4.0 # Copyright (c) 2015-present, xuxueli.CREATE database if NOT EXISTS xxl_job default character set utf8mb4 collate utf8mb4_unicode_ci; use xxl_job;SET NAMES utf8mb4;CREATE TABLE xxl_job_info (id int(…

算法记录 | Day42 动态规划

01 背包 0-1 背包问题 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 **0-1 背包问题的特点&#xff1a;**每种物品有且仅有 1 件&…

28. Python logging日志模块下(适合小白)

28. Python logging日志模块下&#xff08;适合小白&#xff09; 文章目录 28. Python logging日志模块下&#xff08;适合小白&#xff09;1. %占位符格式化语法知识回顾2. basicConfig函数的参数3. format参数&#xff1a;设置输出的格式3.1 添加%(asctime)s字段输出日志发生…

电子商务网站上的API攻击如何泄漏PII

本稳重点分享&#xff1a; 以影子 API为目标的 API 攻击 电子商务网站上的 API 攻击如何泄漏 PII 对 API 运行时安全性重要性的看法 API 渗透测试指南 以影子 API为目标的 API 攻击 首先是DarkReading最近的一个研究的报告&#xff0c;该报告显示&#xff0c;大约50亿&am…

20+ Prompt工具网站汇总;我用AI工具开了一家「无人公司」;如何10分钟上线一个AI导航网站;第一部AIGC中英双语图文辞典 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『MidJourney Prompt工具网站』加速生成与优化&#xff0c;持续更新中 ShowMeAI知识星球 | 资源标签&#xff1a;找工具 这是一个总结…

【unity专题篇】——GUI(IMGUI)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

tcp,udp一些列问题

&#xff08;tcp,udp基本介绍&#xff0c;三握四挥等&#xff09;七层模型主要知识点等 OSI七层模型其功能简介 分层机制体现了分治的思想&#xff0c;每一层为上一层提供保障屏蔽异构。 物理层&#xff1a;规定了一系列的物理、电气、接口标准&#xff0c;传输的是比特流&…

DAY05_面向对象基础

面向对象并不是一个技术&#xff0c;而是一种指导思想。 为什么要用面向对象编程&#xff1f; 因为生活中&#xff0c;我们解决问题时&#xff0c;就是采用这种指导思想去解决的。所以&#xff0c;我们写程序去解决问题时&#xff0c;如果也能采用这种指导思想就会使得程序变…

LVS+keepalived 群集

Keepalived及其工作原理 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题 在一个LVS服务集群中通常有主服务器&#xff08;MASTER&#xff09;和备份服务器&#xff08;BACKUP&#xff09;两种角色的服务器&#xff…

链接伪类选择器(上)

知识点&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible" content"IEedge"> <meta name"viewport" c…

java方法的重载

java中有很多方法是可以通过重载的方式实现的&#xff0c;这是 Java语言的一大特色&#xff0c;但是同时也为开发人员带来了一些麻烦&#xff0c;不知道怎么去调用这些方法&#xff0c;而且还容易出现一种情况就是&#xff1a;明明已经调用过了一个方法&#xff0c;但为什么又要…

Ansys Zemax | 如何模拟双折射偏振器件

这篇文章介绍了什么是双折射现象、如何在OpticStudio中模拟双折射 (birefringence)、如何模拟双晶体的双折射偏振器以及如何计算偏振器的消光比。&#xff08;联系我们获取文章附件&#xff09; 什么是双折射现象 一般的光学材料都是均匀的各向同性的&#xff0c;也就是说无论光…

等级保护、风险评估和安全测评分别是什么?

2022-06-17 15:17 迈入“等保2.0时代”以后&#xff0c;我国对于等级保护的要求更为严格和具体。等级保护、风险评估和安全测评这三个词&#xff0c;也因此总是出现在人们的视野之中&#xff0c;还总是被混淆。那这三者究竟分别是什么呢&#xff1f;如何区分它们&#xff1f;它…

如何以产品经理思维打造一所高品质学校?

学校的建设与管理真不是一件容易事。2023年03月17日&#xff0c;山东菏泽市曹县一家长投诉某中学课业繁重&#xff0c;孩子经常写作业到半夜&#xff1b;2023年4月4日&#xff0c;张先生在华龙网重庆网络问政平台投诉万州区某中学伙食差&#xff0c;指出“发灰的洋葱&#xff0…

本地运行 minigpt-4

1.环境部署 参考官方自带的README.MD&#xff0c;如果不想看官方的&#xff0c;也可参考MiniGPT-4&#xff5c;开源免费可本地进行图像对话交互的国产高级大语言增强视觉语言理解模型安装部署教程 - openAI 当然&#xff0c;所有的都要按照作者说明来&#xff0c;特别是版本号…

练好基本功,优秀工程师成长第一步

计算机基础作用 举例1&#xff1a;若是我们要开发大规模应用系统&#xff0c;如电商服务系统&#xff0c;要考虑很多 1. 这个服务应用要用什么语言来编写&#xff1f; 2. 是采用单体进程&#xff0c;还是用多个进程来协同工作&#xff1f; 3. 如何管理长期使用的内存空间&a…

( 栈和队列) 155. 最小栈 ——【Leetcode每日一题】

❓155. 最小栈 难度&#xff1a;中等 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。…

WiFi电子标签简介

WiFi电子标签系统概述&#xff1a; WIFI电子办公标牌系统是一种先进的无线自动更新系统&#xff0c;取代了传统的纸质标牌/桌牌需要人工更换的方式。WIFI ESL系统只需要一个电子办公标志设备&#xff0c;让它在办公或会议空间工作&#xff0c;快速改变人员或会议信息。这是一种…