【iOS】Runtime

news2024/10/5 17:26:04

文章目录

  • 前言
  • 一、Runtime简介
  • 二、NSObject库起源
    • isa
    • isa_t结构体
    • cache_t的具体实现
    • class_data_bits_t的具体实现
  • 三、[self class] 与 [super class]
  • 四、消息发送与转发
  • 五、Runtime应用场景


前言

之前分part学习了Runtime的内容,但是没有系统的总结,这篇博客用来总结学过的所有Runtime知识

一、Runtime简介

Runtime又叫运行时,是一套底层的C语言API,是iOS系统的核心之一

在编码阶段中,当我们向一个对象发送消息时,编译阶段只是确定了我们需要向接收者发送消息,但是接收者如何响应与处理这条消息是运行时决定的,我们来看一个例子

首先,让我们定义这些类:

#import <Foundation/Foundation.h>

// 基类 Animal
@interface Animal : NSObject
- (void)speak;
@end

@implementation Animal
- (void)speak {
    NSLog(@"Some generic animal sound");
}
@end

// Dog 类继承自 Animal
@interface Dog : Animal
@end

@implementation Dog
- (void)speak {
    NSLog(@"Woof!");
}
@end

// Cat 类继承自 Animal
@interface Cat : Animal
@end

@implementation Cat
- (void)speak {
    NSLog(@"Meow!");
}
@end

现在,我们编写一个主函数来创建不同的动物对象,并对它们调用 speak 方法:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建 Animal 类型的数组
        NSArray *animals = @[[[Dog alloc] init], [[Cat alloc] init], [[Animal alloc] init]];
        
        // 遍历数组中的每一个动物,并调用 speak 方法
        for (Animal *animal in animals) {
            [animal speak];
        }
    }
    return 0;
}

可以看到我们animal接受了speak这个方法,但是运行时会查找animal的实际类,并且动态地查找这个类或其父类中的 speak 方法实现

同时OC也是一门动态语言,这意味着它不仅需要一个编译器,更需要一个运行时系统来动态得创建类和对象、进行消息传递和转发

Objc 在三种层面上与 Runtime 系统进行交互:
在这里插入图片描述

  • 层面一:通过OC源代码
    我们只需要编写OC代码,Runtime系统会自动将我们写的代码在编译阶段转换为运行时代码
  • 层面二:通过Foudation框架的NSObject的类自定义方法
    在NSObject协议中有五种方法可以从Runtime中获取信息,并且让对象进行自我检查
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead");
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;

-class方法返回对象的类;
-isKindOfClass:-isMemberOfClass: 方法检查对象是否存在于指定的类的继承体系中;
-respondsToSelector: 检查对象能否响应指定的消息;
-conformsToProtocol:检查对象是否实现了指定协议类的方法;

在NSObject类中还有一个方法会返回SEL的IMP

- (IMP)methodForSelector:(SEL)aSelector;
  • 层面三:通过对 Runtime 库函数的直接调用
1. Class and Metaclass Functions

	•	objc_getClass(const char *name): 获取指定名称的类。
	•	objc_getMetaClass(const char *name): 获取指定名称的元类。
	•	objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes): 动态创建一个新的类。
	•	objc_registerClassPair(Class cls): 注册一个动态创建的类。

2. Method Functions

	•	class_addMethod(Class cls, SEL name, IMP imp, const char *types): 向类中添加一个方法。
	•	class_replaceMethod(Class cls, SEL name, IMP imp, const char *types): 替换类中的一个方法。
	•	class_getInstanceMethod(Class cls, SEL name): 获取实例方法。
	•	class_getClassMethod(Class cls, SEL name): 获取类方法。
	•	method_getName(Method m): 获取方法的选择器。
	•	method_getImplementation(Method m): 获取方法的实现。

3. Property and Ivar Functions

	•	class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types): 向类中添加一个实例变量。
	•	class_getInstanceVariable(Class cls, const char *name): 获取类中的实例变量。
	•	class_getProperty(Class cls, const char *name): 获取类中的属性。
	•	class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount): 向类中添加属性。

4. Selector Functions

	•	sel_registerName(const char *str): 注册一个选择器。
	•	sel_getUid(const char *str): 获取一个选择器。

5. Protocol Functions

	•	objc_getProtocol(const char *name): 获取指定名称的协议。
	•	objc_allocateProtocol(const char *name): 动态创建一个新的协议。
	•	objc_registerProtocol(Protocol *proto): 注册一个动态创建的协议。
	•	protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod): 向协议中添加方法描述。
	•	protocol_addProperty(Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty): 向协议中添加属性。

6. Object and Messaging Functions

	•	objc_msgSend(id self, SEL op, ... ): 发送消息。
	•	objc_msgSendSuper(struct objc_super *super, SEL op, ... ): 发送消息给父类。
	•	object_getClass(id obj): 获取对象的类。
	•	object_setClass(id obj, Class cls): 设置对象的类。

二、NSObject库起源

刚才说了我们有三种方式可以和Runtime进行交互,前两种方式都与NSObject有关,我们就从NSObject基类开始说起

我们通过源码可以得知NSObject的定义如下:

typedef struct objc_class *Class;

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

其内部只包含了一个名为isa的Class指针,同时Class指针实际上就是一个objc_class结构体,如何理解这个结构体呢,我们来看一下这个结构体的源码:
Objc2.0之前objc_class源码如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
    
} OBJC2_UNAVAILABLE;

可以看到在一个类中,有超类的指针,类名,版本的信息,同时还有指向成员变量列表的指针,指向方法列表的指针
我们可以通过动态的修改方法列表来达到使用分类向类中添加方法
关于分类的文章之前写过,现在发现一篇更好的,大家可以读一下
深入理解Objective-C:Category

同时在先前说过Category的底层结构体中是有属性列表的,但是为什么不能添加属性呢,这是因为当我们使用@property声明属性时,会自动添加实例变量,但是Category的底层结构体中没有实例变量列表,因此无法实现,同时还有一个原因是编译器不会为分类自动合成set与get方法,但最最主要的原因是rw中没有成员变量列表,不允许修改成员变量

objc2.0之后,objc_class的定义就变了:


typedef struct objc_class *Class;
typedef struct objc_object *id;

@interface Object { 
    Class isa; 
}

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}

将源码转换为类图就变成了下面这样子:
在这里插入图片描述
在源码中我们可以看出来所有的对象都包含一个isa_t类型的结构体,这是如何看出来的呢

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

从这两段代码我们可以看出来,objc_classobjc_object的子类,我们理解一下这两个结构体名字:
objc_object的意思是对象,也就是在OC中所有对象都有一个isa_t变量,objc_class的意思是类,但是他却继承于对象,那么说明我们的类实际上也是一个对象,也就是类对象

这也就说明了上面的结论:所有的对象都会包含一个isa_t类型的结构体。

objc_object被源码typedef成了id类型,这也说明了为什么任何类型都可以用id来表示,这是因为id类型是所有对象的父类

我们一步步来分析这里面的成员变量,首先是object类和NSObject类里面分别都包含一个objc_class类型的isa

isa

首先我们通过学习消息流程可以知道,当一个对象的方法被调用时,首先会根据isa指针找到相应的类,然后在该类的class_data_bits_t中去查找方法。class_data_bits_t是指向了类对象的数据区域。在该数据区域内查找相应方法的对应实现

同时当调用类方法是也会通过isa查找方法,此时isa指向的是元类(Meta Class),这里有问题可以看先前的博客,不再赘述

同时元类与类对象是唯一的

isa_t结构体

isa_t 是现代Objective-C运行时中的一个重要优化,它通过位域结构封装了 isa 指针,使得它不仅仅是一个指向类的指针,还携带了大量运行时所需的附加信息。通过这种设计,Objective-C运行时能够在保持高效内存使用的同时,提供丰富的对象管理功能。

总结就是isa_t比较抽象,笔者也讲不懂,但是里面用到了Tagged Pointer技术,大家可以去了解
深入理解 Tagged Pointer

cache_t的具体实现

cache_t出现objc_class中,我们来通过源码分析一下

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
}

typedef unsigned int uint32_t;
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

typedef unsigned long  uintptr_t;
typedef uintptr_t cache_key_t;

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
}


通过源码我们知道了cache_t中存储了一个bucket_t的结构体,和两个unsigned int的变量。

  • mask:分配用来缓存bucket的总数。
  • occupied:表明目前实际占用的缓存bucket的个数。

同时我们看一下bucket_t结构体,他里面只有两个元素,一个是key,一个是IMP
cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表

当我们使用方法后,编译器会自动将方法的SEL存为Key,其实现IMP存进bucket_t中的Key对应的IMP中,这样就优化了方法调用的性能,不用每次调用方法时都去方法列表中查找

Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找

class_data_bits_t的具体实现

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

objc2.0之前我们的objc_class结构体中有十分多的元素,但是更新后就变得十分简洁,这些元素并没有消失,其实都存在了数据区域class_data_bits_t

同样来看源码:


struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
}

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

在这里插入图片描述
objc_class结构体中的注释写到 :
class_data_bits_t相当于 class_rw_t指针加上 rr/alloc 的标志

也就是说先前的属性、方法以及遵循的协议在obj 2.0的版本之后都放在class_rw_t中,那么ro是用来干什么的呢?

我们知道OC作为一门动态语言运行阶段分为编译器与运行期,在编译期类的结构中的 class_data_bits_t *data指向的是一个 class_ro_t *指针:
在这里插入图片描述

在Objc运行时会调用realizeClass方法:

  1. class_data_bits_t 调用 data 方法,将结果从 class_rw_t 强制转换为 class_ro_t 指针,这一步是为了class_rw_tro能被正确赋值
  2. 初始化一个 class_rw_t 结构体
  3. 设置结构体ro的值以及flag
  4. 最后设置正确的data,也就是返回最后的rw结构体(因为原本data指向的是ro
    我们来看一下更改后的图片

在这里插入图片描述
此时realizeClass方法运行后我们的rw结构体已经被初始化,同时ro已经被赋值,但是此时的方法,属性以及协议列表均为空,这时需要 realizeClass 调用 methodizeClass 方法来将类自己实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

同时我们可以再通过这里讲讲我们的消息查找,如果动态修改了方法会生成rw_e结构体,查找方法时会优先去rw_e中查找,否则去ro中查找

三、[self class] 与 [super class]

我们来看一道题目

下面代码输出什么?

 @implementation Son : Father
    - (id)init
    {
        self = [super init];
        if (self)
        {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
    return self;
    }
    @end

self和super的区别:

self是类一个隐藏参数,每个方法的实现的第一个参数为self

super则负责告诉编译器,调用方法时,去调用父类的方法,而不是本类中的方法

也就是说[super class]调用了objc_msgSendSuper方法,而不是objc_msgSend

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )


/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

objc_msgSendSuper方法中,我们会从父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver,而不是super_class

那么objc_msgSendSuper最后就转变成

// 注意这里是从父类开始msgSend,而不是从本类开始,谢谢@Josscii 和他同事共同指点出此处描述的不妥。
objc_msgSend(objc_super->receiver, @selector(class))

/// Specifies an instance of a class.  这是类的一个实例
    __unsafe_unretained id receiver;   


// 由于是实例调用,所以是减号方法
- (Class)class {
    return object_getClass(self);
}

由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = selfself就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son

四、消息发送与转发

这部分内容之前已经学的十分详细了,可以直接看之前写的博客
【iOS】消息流程分析

五、Runtime应用场景

同时我们讲完了Runtime,我们自然要知道如何应用Runtime,我们来看一下Runtime的一些应用

  • (1) 实现多继承Multiple Inheritance
  • (2) Method Swizzling
  • (3) Aspect Oriented Programming
  • (4) Isa Swizzling
  • (5) Associated Object关联对象
  • (6) 动态的增加方法
  • (7) NSCoding的自动归档和自动解档
  • (8) 字典和模型互相转换

其中大多数应用之前博客都有讲大家可以自行查找,同时Isa Swizzling对应的应用是KVO的原理,至于字典模型相互转换之后在学习JsonModel源码中会讲

参考博客:
神经病院 Objective-C Runtime 入院第一天—— isa 和 Class神经病院 Objective-C Runtime 入院第一天—— isa 和 Class
深入解析 ObjC 中方法的结构
深入理解Objective-C:Category

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

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

相关文章

自然语言处理(NLP)—— 主题建模

1. 主题建模的概念 主题建模&#xff08;Topic Modeling&#xff09;是一种用于发现文档集合&#xff08;语料库&#xff09;中的主题&#xff08;或称为主题、议题、概念&#xff09;的统计模型。在自然语言处理和文本挖掘领域&#xff0c;主题建模是理解和提取大量文本数据隐…

vue-$set修改深层对象的值

背景&#xff1a; 点击编辑按钮&#xff0c;打开修改预算的抽屉&#xff0c;保存后更新此行数据的预算&#xff0c;以前是调接口刷新表格&#xff0c;这次的数据是由前端处理更新&#xff0c;由于数据层级比较深&#xff0c;使用$set来修改两层嵌套对象 使用方法&#xff1a; …

upload-labs-第五关

目录 第五关 1、构造.user.ini文件 2、构造一个一句话木马文件&#xff0c;后缀名为jpg 3、上传.user.ini文件后上传flag.jpg 4、上传成功后访问上传路径 第五关 原理&#xff1a; 这一关采用黑名单的方式进行过滤&#xff0c;不允许上传php、php3、.htaccess等这几类文件…

3D按F3为什么显示不出模型?---模大狮模型网

对于3D建模软件的用户来说&#xff0c;按下F3键通常是用来显示或隐藏模型的功能之一。然而&#xff0c;有时当按下F3键时&#xff0c;却无法正确显示模型&#xff0c;这可能会让用户感到困惑。模大狮将探讨这种情况发生的可能原因以及解决方法&#xff0c;帮助设计师们更好地理…

快速搭建高效运营体系,Xinstall App下载自动绑定助您一臂之力

在互联网的浪潮中&#xff0c;App的推广与运营面临着诸多挑战。如何在多变的互联网环境下迅速搭建起能时刻满足用户需求的运营体系&#xff0c;成为了众多企业关注的焦点。今天&#xff0c;我们就来聊聊如何通过Xinstall的App下载自动绑定功能&#xff0c;轻松解决App推广与运营…

[word] word文档字体间距怎么调整? #其他#经验分享

word文档字体间距怎么调整&#xff1f; 调整word文档字体间距的方法&#xff1a; 打开一个预先写好文字的文档。选中所有文字&#xff0c;点击字体右下角的“更多选项”。 在弹出的“字体”对话框中进入“字符间距”选项卡&#xff0c;在“间距”选项中即可设置字体间距。 设…

智慧视觉怎么识别视频?智慧机器视觉是通过什么步骤识别视频的?

智慧视觉功能怎么识别视频&#xff1f;智慧视觉是搭载在智能设备比如手机、AI盒子、机器视觉系统上的一个应用程序或特性&#xff0c;采用计算机视觉和人工智能的技术来识别图像或视频中的内容。如果想了解视频识别&#xff0c;就要明白智慧视觉功能会涉及的以下几个关键步骤和…

知识图谱的应用---智慧司法

文章目录 智慧司法典型应用 智慧司法 智慧司法是综合运用人工智能、大数据、互联网、物联网、云计算等信息技术手段&#xff0c;遵循司法公开、公平、公正的原则&#xff0c;与司法领域业务知识经验深度融合&#xff0c;使司法机关在审判、检查、侦查、监管职能各方面得到全面的…

OpenShift 4 - OpenShift Service Mesh 3 预览

《OpenShift / RHEL / DevSecOps 汇总目录》 了解 OpenShift Service Mesh 3 的变化 OpenShift Service Mesh 是一套在 OpenShift 上安装部署、跟踪监控 Istio 运行环境的实现。红帽在 2023 年底推出了技术预览版的 OpenShift Service Mesh 3&#xff0c;它和目前的 OpenShif…

经典神经网络(10)PixelCNN模型、Gated PixelCNN模型及其在MNIST数据集上的应用

经典神经网络(10)PixelCNN模型、Gated PixelCNN模型及其在MNIST数据集上的应用 1 PixelCNN PixelCNN是DeepMind团队在论文Pixel Recurrent Neural Networks (16.01)提出的一种生成模型&#xff0c;实际上这篇论文共提出了两种架构&#xff1a;PixelRNN和PixelCNN&#xff0c;两…

鸿蒙开发的南向开发和北向开发

鸿蒙开发主要分为设备开发和应用开发两个方向&#xff0c;也叫南向开发和北向开发&#xff1a; 鸿蒙设备开发(南向开发&#xff09;&#xff0c;要侧重于硬件层面的开发&#xff0c;涉及硬件接口控制、设备驱动开发、鸿蒙系统内核开发等&#xff0c;目的是使硬件设备能够兼容并…

Linux环境---在线安装MYSQL数据库

Linux环境—在线安装MYSQL数据库 一、使用步骤 1.安装环境 Mysql 驱动 8.0 需要 jdk1.8 才行。 JDK版本&#xff1a;1.8 参考文档 MYSQL版本&#xff1a;8.0.2 下载链接: https://pan.baidu.com/s/1MwXIilSL6EY3OuS7WtpySA?pwdg263 操作系统&#xff1a;CentOS 1.1 建立存…

Python数据分析II

目录 1.HS-排序返回前n行 2.HS-相关性 3.缺失值处理 4.时间 5.时间索引 6.分组聚合 7.离散分箱 8.Concat关联(索引关联) 9.Merge关联(字段关联) 10.join合并(左字段,右索引) 11.行列转置及透视表 12.数据可视化-面向过程 13.数据可视化-面向对象 14.快速生成柱状…

github有趣项目:Verilog在线仿真( DigitalJS+edaplayground)

DigitalJS https://github.com/tilk/digitaljs这个项目是一个用Javascript实现的数字电路模拟器。 它旨在模拟由硬件设计工具合成的电路 像 Yosys&#xff08;这里是 Github 存储库&#xff09;&#xff0c;它有一个配套项目 yosys2digitaljs&#xff0c;它可以转换 Yosys 将文…

STCunio数字电源带PID数字闭环(带详细的代码说明文档)

STCunio&#xff0c;即 system on chip unusual i/o,采用类似 arduino 构架设计&#xff0c;即使没有单片机知 识的设计师和艺术家们能够很快地通过它学习电子和传感器的基础知识&#xff0c;并应用到他们的设 计当中。设计中所要表现的想法和创意才是最主要的&#xff0c;至于…

创新指南 | 5个行之有效的初创企业增长策略

本文探讨了五种初创企业实现快速增长的有效策略&#xff1a;利用网络效应通过激励和资本化用户增长&#xff1b;通过持续提供高质量内容建立信任和权威的内容营销&#xff1b;利用简单有效的推荐计划扩展用户群&#xff1b;采用敏捷开发方法快速适应市场变化和客户反馈&#xf…

基于springboot实现社区养老服务系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现社区养老服务系统演示 摘要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本社区养老服务系统就是在这样的大环境下诞生&#xff0c;其可以帮助…

签名安全规范:解决【请求对象json序列化时,时间字段被强制转换成时间戳的问题】

文章目录 引言I 签名安全规范1.1 签名生成的通用步骤1.2 签名运算(加密规则)1.3 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)1.4 允许的请求头字段1.5 签名校验工具II 注解校验签名2.1 获取请求数据,并校验签名数据2.2 解决时间格式被强制转换成时间戳的问题…

2024年数据防泄密软件精选,五款热门防泄密软件集锦

在信息爆炸的今天&#xff0c;企业数据的安全性已成为不可忽视的关键问题。 随着数字化转型的加速&#xff0c;数据泄露的风险也随之增加&#xff0c;这对企业的核心竞争力构成了严重威胁。 为了构建坚不可摧的数据防线&#xff0c;选择高效可靠的数据防泄密软件显得尤为重要…

爬取基金收盘价并用pyecharts进行展现

爬取基金收盘价并用pyecharts进行展现 一、用到的第三方包 因为使用到了一些第三方的包&#xff0c;包还是比较大的如果直接从社区下载比较费劲&#xff0c;所以建议配置国内镜像源&#xff0c;这里以清华的镜像源为例。 pip config set global.index-url https://pypi.tuna…