iOS--block再学习

block再学习

  • 什么是block
    • block是带有自动变量的匿名函数
    • block语法
  • block的实现
    • block的实质
    • 截获自动变量
    • __blcok说明符
    • Block存储域
    • __block变量存储域
      • 使用__block变量用结构体成员变量__forwarding的原因
    • 截获对象

什么是block

Block时c语言的扩充功能,它允许开发者定义一段可重用的代码,并在需要时像变量一样使用这段代码。
对于block最重要的几个特点,总结如下:

  • Block本质上是一个OC对象,具有自己的isa指针。
  • 它可以看作是带有自动捕获变量能力的匿名函数。
  • Block可以捕获和存储它所在的环境中的变量和常量。

首先我们先从它可以看作是带有自动捕获变量能力的匿名函来了解和分析block ;

block是带有自动变量的匿名函数

首先我们先来了解一下自动变量的概念 ;

在 Objective-C(OC)中,自动变量(Automatic Variables)通常指的是在函数或方法内部定义的局部变量,这些变量在函数或方法被调用时自动在栈(stack)上分配内存,并在函数或方法执行完毕后自动释放内存。这些变量的作用域仅限于定义它们的函数或方法内部。

其实在这个地方我们可以把自动变量理解为局部变量 ;
在Objective-C中,Block可以捕获其定义范围内可见的局部变量,但是它们捕获这些变量的方式取决于这些变量的存储类型修饰符。修饰符之后仔讲 ;

匿名函数如它的名称一样,是一种没有名称的函数。

那我们为什么要用block,而不直接用函数呢,明明普通函数能实现,
以下是使用Block的一些主要原因:

  • 闭包(Closure)特性:
    Block可以捕获其定义范围内的变量和常量,包括外部函数的局部变量和全局变量。这使得Block可以访问和操作这些变量,就像它们是Block自己的局部变量一样。这种闭包特性使得Block能够封装和保存函数的状态,从而实现更复杂的逻辑。

  • 匿名性:
    Block没有名称,因此它们可以作为参数传递给其他函数或方法,或者作为属性存储在对象中。这使得Block可以方便地在代码之间传递和使用,而无需担心命名冲突或额外的命名空间管理。

  • 类型安全:
    虽然Block在语法上类似于C函数,但它们是Objective-C的类型,并且支持类型检查。这意味着可以在编译时捕获与Block类型相关的错误,从而提高代码的质量和可维护性。

  • 简洁性: Block可以内联定义在需要使用它们的代码块中,无需单独声明和定义函数。这使得代码更加简洁和易读,同时减少了函数调用的开销。

  • 异步编程:
    在Objective-C中,异步编程通常涉及到回调函数的使用。使用Block作为回调函数可以简化代码结构,并减少嵌套回调的复杂性。例如,使用Grand
    Central Dispatch (GCD)进行异步任务时,可以使用Block作为任务完成后的回调处理程序。

  • 响应式编程:
    Block可以与响应式编程模式结合使用,以处理异步事件和流数据。通过使用Block来处理事件和数据流,可以创建更加灵活和可响应的应用程序。

  • 与Objective-C对象的集成:
    Block可以与Objective-C对象无缝集成,并且可以轻松地在Block内部访问和操作对象属性和方法。这使得Block成为处理Objective-C对象和集合类的强大工具。

  • 内存管理:
    在Objective-C中,Block的内存管理可以通过__block修饰符和ARC(自动引用计数)来管理。这减少了手动管理内存的需要,并降低了内存泄漏和野指针的风险。

    简单的说,block提供了更多的灵活性和便利性,特别是在与Objective-C对象交互的上下文中。

    顺便说一下:带有自动变量的匿名函数这一概念并不指blocks,它还存在于许多其他程序语言中;

block语法

Block 声明和定义:

returnType (^blockName)(parameters) = ^(parameters) {  
    // Block 的实现代码  
};

  • returnType:Block 返回的类型,如果 Block 没有返回值,则为 void。
  • blockName:Block 的名称,可选,通常省略以创建匿名 Block。
  • parameters:Block 接收的参数列表,如果没有参数则为空。
  • ^:这是 Block 的字面量语法,用于开始定义 Block。

如:

int (^addBlock)(int, int) = ^(int a, int b) {  
    return a + b;  
};

使用 Block:

int sum = addBlock(3, 4); // sum 现在为 7

Block 类型声明:
当不使用 typedef 来声明 Block 变量类型时:

// 声明一个返回 int 类型,接受两个 int 类型参数的 Block 变量  
int (^addBlock)(int, int) = ^(int a, int b) {  
    return a + b;  
};  
  
// 调用 Block  
int result = addBlock(3, 4);  
NSLog(@"The result is: %d", result); // 输出 "The result is: 7"

使用 typedef 来声明 Block 变量类型时:

// 使用 typedef 声明 Block 类型别名  
typedef int (^IntToIntBlock)(int, int);  
  
// 使用类型别名声明 Block 变量  
IntToIntBlock multiplyBlock = ^(int a, int b) {  
    return a * b;  
};  
  
// 调用 Block  
int product = multiplyBlock(3, 4);  
NSLog(@"The product is: %d", product); // 输出 "The product is: 12"

第二种声明很简单,主要注意一下第一种声明,或者两者间的区别 ;
如:

int (^ func ()) (int) {
    
}

上面这个形式看起来就挺怪的,但实际上这是一个返回值为block类型的函数func;

block的实现

block的实质

block实际上是作为普通的c语言代码来处理的 ;
原代码:
在这里插入图片描述

通过clang转换后的可读源代码:
在这里插入图片描述

通过block使用的匿名函数实际上被作为简单的语言函数来处理;
_cself为指向block得变量 ;
从上面转换过来的源码可以看出block在c语言中的结构就是一个结构体 ;
结构体的声明如下:
在这里插入图片描述

这事一个嵌套的结构体,这里就顺便给出其中的两个结构体的声明 ;
在这里插入图片描述

在这里插入图片描述

isa指针在对象,类,元类中就了解了,这也说明了block是一种对象;flag是一种标志,具体不太清楚;Reserved是版本升级所需要的区域;FuncPtr是指向函数的指针,实际上这个函数也是block中的具体实现 ;
Block——size是Block的大小,也是结构体的大小 ;

在这里插入图片描述

这里的构造函数初始化了Block结构体的成员变量,Blcok中自动变量的捕获也在这里完成 ;
这里会想下面这样初始化在:
在这里插入图片描述

isa指向了这个对象的类;FuncPtr和Desc分别指向了它们的构造函数,完成成员变量的初始化 ;

主函数中的
在这里插入图片描述

转换后:
在这里插入图片描述

这段代码将在栈上生成的__main_block_impl_0结构体实例的指针赋值给变量blk ;
对应了:
在这里插入图片描述

而:
在这里插入图片描述

则对应着blk();

截获自动变量

先给出转换后的源代码:
在这里插入图片描述

对比一下前面的源代码,这里只有结构体和使用时调用的函数不一样 ;
先看结构体:
在这里插入图片描述

其实从这个结构我们也可以猜到Block对自动变量的捕捉是通过成员变量赋值实现的 ;

这里也解答了,为什么Block无法捕获c语言中的数组 ,因为在c语言中数组是无法直接赋值的,但可以通过指针实现 ;

顺便注意一下,Blcok语法表达式中没有使用的自动变量是不会被捕获的,甚至在Block结构体中都不会声明它的成员变量 ;

在这里插入图片描述

这里相当于setting方法;
在这里插入图片描述

这个相当于getting方法 ;

总的来说,“截获自动变量”意味着在执行Block语法时,Block表达式所使用的自动变量被保存到Block结构体中去了 ;

__blcok说明符

一般来说,Blcok会截获Block语法中使用过的自动变量,即使在Block外对这些自动变量进行重写,也不会改变Block中已经捕获的值;如果尝试在Block内部进行重写 ,编译器在编译时会自动检测并抛出错误;
这也意味着不便,有什么办法能在Block中直接改写Block中的自动变量呢 ;

这里有两种方法:
1.c语言中有部分变量是允许Block改写值的;

  • 静态变量
  • 全局变量
  • 静态全局变量

具体可以看看两端代码转换前后的对比:
在这里插入图片描述

在这里插入图片描述

这里发现全局变量的使用与c语言中无二,甚至不会有Block的捕获机制;

但静态变量在结构体中是以指针捕获的;至于为什么用指针,这是超出作用域使用变量的最简单方法 ;
那为什么普通的自动变量为什么不采用指针捕获;这是因为静态变量与其他变量之前生命周期的差别 ;

2.使用__block说明符:
和它类似的还有

  • static
  • auto
  • regist
    其中auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中 ;
    只有__block得存储域待会讲;
    先来看看使用__Block的自动变量在Block中的具体实现:
    在这里插入图片描述

在这里插入图片描述

对比之前的,我们发现多了一个__block_byref_val_0结构体,它和block的结构体很像,但多了一个forwarding指针指向自身 ;
看起来就是把val变量转换为一个结构体变量来捕获 ;
在这里插入图片描述

这里结构体的结构可以参考下图:
在这里插入图片描述

当使用Block时:
在这里插入图片描述

这里看起来有点复杂;那为什么有成员变量__forwarding?虽然后面讲,但我猜是通过它来延长自动变量的生命周期之类的 ;

另外要注意一点,__block变量的____block_byref_val_0结构体对象并不在__main_block_impl_0结构体中,__main_block_impl_0结构体中的是它的指针,而对象在主函数中,这样做就可以在多个Block中使用__blcok变量 ;

Block存储域

Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量,所谓结构体类型的自动变量,即栈上生成的该结构体的实例 ;
但我们知道,除了栈以外,存储域还有数据区域和堆 ;
所以Block对象的存储域也不仅仅在栈 ;
下面给出Block对象的类和存储域间的关系:
在这里插入图片描述

平时用到的Block对象大多在栈,当

  • 记述全局变量的地方有Block语法时
  • Block语法的表达式中不使用应截获的自动变量时

时,Block为——NSConcreteGlobalBlock类对象;

#import "ViewController.h"
#import <objc/runtime.h>
typedef int(^blk_t)(int);
blk_t glbblk  = ^(int count){return count ;} ;
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
            for (int rate = 0; rate < 10; ++rate) {
                blk_t blk = ^(int count){return count ;} ;
                Class blockClass = object_getClass((__bridge id _Nullable)((__bridge void *)(glbblk)));
                NSLog(@"%@",NSStringFromClass(blockClass)) ;
                printf("%d\n",blk(1)) ;
            }
    
        }
#import "ViewController.h"
#import <objc/runtime.h>
typedef int(^blk_t)(int);
blk_t glbblk  = ^(int count){return count ;} ;
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
            for (int rate = 0; rate < 10; ++rate) {
                blk_t blk = ^(int count){return count ;} ;
                Class blockClass = object_getClass((__bridge id _Nullable)((__bridge void *)(blk)));
                NSLog(@"%@",NSStringFromClass(blockClass)) ;
                printf("%d\n",blk(1)) ;
            }
    
        }

配置在全局变量上的Block,从变量作用域外也可以通过指针安全使用,但设置在栈上的Block,如果其所属的作用域结束,该Block就被废弃;同上,配置在栈上的__block变量也是如此 ;
那么我们在使用中,Block时如何超出变量作用域存在的:
Blocks提供了讲Block和__block变量从栈上复制到堆上的方法 ;这样就能保证Block不被废弃 ;
当Block被复制到堆上时,isa指针也就是类会被赋为_NSConcrete MallocBlock,__block变量也是如此,但它的成员变量forwarding可以同时访问栈上和堆上的__block变量 ;

这个所谓的复制方法,其实就是oc中经常使用的copy,当我们对一个Block对象使用copy时有三种情况:
在这里插入图片描述

不过在提到使用copy方法前,我们要知道,但ARC有效时,大多数情形下编译器都会恰当地进行判断,自动生成讲Block从栈上复制到堆上的代码 ;

除此之外的情形就必须手动复制了,比如像方法或函数的参数中传递Block时;但如果在方法或函数中适当地复制了传递过来的参数,那也不必手动复制了,如:

  • Cocoa框架的方法切方法名中含有usingBlock等时
  • GCD的API

书上讲的必须手动复制的例子:

NSArray* array = [self getBlockArray] ;
    typedef void(^blk01_t)(void);
    blk01_t blk = [array objectAtIndex:0] ;
    blk () ;



- (id) getBlockArray {
    int val = 10 ;
    return [[NSArray alloc] initWithObjects:^{NSLog(@"%d",val);},^{NSLog(@"%d",val);}, nil];
}

但不太清楚的一点是我在实际运行的时候程序不会异常,而是会正常执行;

再回到调用copy方法,我们会发现不管Block配置在何处,用copy方法都不会引起任何问题,在不确定时调用copy方法即可 ;不过将Block从栈上复制到堆上是相当消耗cpu的;

如下:

 blk = [[[[blk copy] copy] copy] copy] ;

多次调用copy方法进行复制在ARC下是没有任何问题的 ;

__block变量存储域

当把使用了__block变量的Block从栈上复制到堆上时,__block
变量也会产生影响:
在这里插入图片描述

当把栈上的Block从栈上复制到堆上时,Block中使用的__block变量也会一并复制到堆上;当该Block已经复制到堆上时,复制Block对__block变量没有任何影响 ;
在这里插入图片描述
当多个Block中使用相同的__block变量时,任何一个Block从栈复制到堆,都会把__block变量也复制到堆上,即使其他Block再复制到堆上,也只会增加__blockde1引用计数;这和oc的内存管理是一样的 ;

在这里插入图片描述

同理,废弃Block也是一样的:
在这里插入图片描述

使用__block变量用结构体成员变量__forwarding的原因

不管__block变量配置在栈上还是在堆上,都能正确地访问变量
即通过Block的复制,__block变量也从栈复制到堆。此时可同时访问栈上的和堆上的__block变量。对于这句话,看

__block int val = 0 ;
    void (^blk) (void) = [^{ ++ val ;} copy] ;
    ++val ;
    blk () ;
    NSLog(@"%d",val) ;

其中Block中的val为复制到堆上的__block变量用结构体实例 ;
而外面的val为栈上的结构体实例 ;

当复制到堆上时,成员变量的forwarding的值替换为目标堆上的__block变量用结构体实例的地址;

所以其实void (^blk) (void) = [^{ ++ val ;} copy] ;
++val ;
这两句都可以转换为:

++ (val.__forwarding->val) ;

在这里插入图片描述

无论是在Block语法中,Block语法外使用__block变量,还是__block变量配置在栈上还是堆上,都可以顺利的访问同一个__block变量 ;

截获对象

书上的例子大概都是基于MRC实现的,不过我在手动管理时也没有成功,所以这部分和内存管理息息相关 ;

typedef void(^blk_t)(id);
    blk_t blk ;
    
    {
        id array = [[NSMutableArray alloc] init] ;
        blk = [^(id obj) {
            [array addObject:obj] ;
            NSLog(@"%ld",[array count] ) ;
        } copy] ;
    }
    
    blk([[NSObject alloc] init]) ;
    blk([[NSObject alloc] init]) ;
    blk([[NSObject alloc] init]) ;

这里的array,当在调用blk时,已经超出了他的作用域,按理来说应该被废弃,但这时发生了截获对象,可能类似于前面截获自动变量的值,但也有些不一样 ;与前者相比,最明显的区别在于:
在这里插入图片描述

因为涉及到了对象,简单的c语言结构体中不能含有__strong修饰的变量 ;但是oc可以通过引用计数进行内存管理 ;
所以在__main_block_desc_0结构体中增加的成员变量copy和dispose以及作为指针赋值给该成员变量的__main_block_copy_0函数(copy函数)和__main_block_dispose_0函数 (dispose函数);

这个时候代入内存管理来看 ;
copy函数使用_Blokc_object-assign函数(相当于retain)将对象类型对象赋值给Block用结构体成员变量array中并持有该对象 ;

dispose函数使用_Block_object_dispose函数(相当于release),释放赋值在Block用结构体成员变量array中的对像 ;

那么这些函数什么时候调用呢:在Block从栈复制到堆时,以及堆上的Block被废弃时会调用 ;
在这里插入图片描述

什么时候栈上的Block会复制到堆:

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时 ;

上面的这些情况下,栈上的Block被复制到堆上,但其实可以总结为_Block_copy函数被调用时Block从栈复制到堆上 ;
相对的,在释放复制到堆上的Block后,谁都不持有Block,而使其被废弃时调用dispose函数,这相当于对象的dealloc实例方法 ;

通过这种构造,通过使用附有__strong修饰符的自动变量,Block截获的对象能够超出变量作用域存在 ;也就是引用计数管理 ;

因此,Block中使用对象类型成员变量时,除了一下情形外,推介调用Block的copy实例方法 ;

  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

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

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

相关文章

宝塔面板和 LNMP 环境下反代 HFish 蜜罐平台的正确方法

最近明月在热心站长好友的支持下搭建了安全、简单、有效并永久免费的蜜罐平台 HFish,因为 HFish 默认是以 https://IP:端口 的 Web 链接形式提供访问的,这会暴露蜜罐平台的真实服务器 IP 不说,还非常不便于快速的访问(反正明月是记不住 IP 的),所以就需要给部署好的 HFis…

Python爬取与可视化-豆瓣电影数据

引言 在数据科学的学习过程中&#xff0c;数据获取与数据可视化是两项重要的技能。本文将展示如何通过Python爬取豆瓣电影Top250的电影数据&#xff0c;并将这些数据存储到数据库中&#xff0c;随后进行数据分析和可视化展示。这个项目涵盖了从数据抓取、存储到数据可视化的整个…

《精通ChatGPT:从入门到大师的Prompt指南》第4章:避免常见错误

第4章&#xff1a;避免常见错误 在使用ChatGPT进行Prompt编写时&#xff0c;常见的错误可能会大大影响生成内容的质量和准确性。本章将详细讨论这些错误&#xff0c;并提供如何避免它们的建议。 4.1 不明确的指令 在使用ChatGPT时&#xff0c;一个常见的问题是指令不够明确。…

中电联系列二:rocket手把手教你理解中电联协议!

分享《一套免费开源充电桩物联网系统&#xff0c;是可以立马拿去商用的&#xff01;》 前 言 T/CEC102《电动汽车充换电服务信息交换》分为四个部分&#xff1a; ——第1部分&#xff1a;总则&#xff1b; ——第2部分&#xff1a;公共信息交换规范&#xff1b; ——第3部分&a…

微信机器人实现OCR识别录入数据

介绍 采用微信的hook插件&#xff0c;然后解析微信发来的数据图片&#xff0c;通过ocr识别 然后将数据落入execl表格中。同时有权限的人可以导出数据表格即可。 流程图 代码片 文本消息处理流程_robot.py elif msg.type 0x01: # 文本消息# 管理员列表dba_user_list [wxid_…

MathType7.8永久破解版下载 让数学学习变得简单有趣!

大家好&#xff0c;我是科技评论家。今天给大家推荐一款非常实用的数学公式编辑器——MathType 7.8&#xff01;&#x1f4f1;&#x1f4b0; 在数字化时代&#xff0c;学术研究、教学和科研领域中的数学公式编辑需求越来越高。而MathType 7.8作为一个广受欢迎的数学公式编辑器&…

Spring Boot整合Redis通过Zset数据类型+定时任务实现延迟队列

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

DALL·E2最详细解读篇章

CLIP被证明其可以学习到鲁棒的图像特征&#xff0c;可以有效的捕获图像的语义和风格&#xff0c;且具有很强的zero-shot能力。另外&#xff0c;Diffusion是目前最优的生成式框架&#xff0c;其推动了图像、视频生成任务的最先进性能。Classifier-Free Diffusion指导技术以样本多…

Junit 单元测试 详解,包你掌握

Java单元测试----Junit详解 1 什么是 Junit JUnit 是一个广泛使用的 Java 单元测试框架。它用于编写和运行可重复的测试&#xff0c;以验证 Java 程序的行为是否符合预期 也许有人会好奇&#xff0c;之前学的 Selenium 和 Junit 有什么关系&#xff1f;答案就是没关系&#…

递归【2】(组合回溯(生成括号)、子集回溯(背包问题))

括号对 &#xff08;组合型回溯&#xff09; 分解成子问题&#xff0c;每一次添加括号分两步&#xff1a; if左括号小于n&#xff0c;加左括号&#xff0c;然后k(index1), if左括号大于有括号&#xff0c;加右括号&#xff0c;k(index1),然后收尾括号单独考虑&#xff0c;到…

设计模式之过滤器模式FilterPattern(十)

一、过滤器模式 过滤器模式&#xff08;Filter Pattern&#xff09;或标准模式&#xff08;Criteria Pattern&#xff09;是一种设计模式&#xff0c;这种模式允许开发人员使用不同的标准来过滤一组对象&#xff0c;通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模…

linux centos redis-6.2.6一键安装及配置密码

linux centos redis-6.2.6一键安装及配置密码 redis基本原理一、操作阶段&#xff0c;开始安装 redis基本原理 redis作为非关系型nosql数据库&#xff0c;一般公司会作为缓存层&#xff0c;存储唯一会话id&#xff0c;以及请求削峰作用 一、数据结构 Redis支持多种数据结构&a…

拯救者Legion Y9000X IRX9 2024(83FD)原装出厂Windows11系统镜像下载

lenovo联想2024款拯救者Y9000X IRX9 笔记本电脑【83FD】OEM预装Win11系统安装包&#xff0c;恢复开箱状态&#xff0c;自带恢复重置还原功能 链接&#xff1a;https://pan.baidu.com/s/1i_sVcnXF4qgsuj02rebe-Q?pwdyefp 提取码&#xff1a;yefp 联想原装WIN11系统自带所有…

Sentinel不使用控制台基于注解限流,热点参数限流

目录 一、maven依赖 二、控制台 三、基于注解限流 四、热点参数限流 五、使用JMeter验证 一、maven依赖 需要注意&#xff0c;使用的版本需要和你的SpringBoot版本匹配&#xff01;&#xff01; Spring-Cloud直接添加如下依赖即可&#xff0c;baba已经帮你指定好版本了。…

Android限制参数传递之StringDef注解的使用

文章目录 1. 引言2. 注解 StringDef2.1 举例2.2 StringDef源码解释 3. 其他类似注解 IntDef、LongDef4. 总结 1. 引言 在参数传递时&#xff0c;如果你想限制传入的参数只能是特定的几个值&#xff0c;该怎么做呢&#xff1f; 除了把参数类型定义为枚举值&#xff0c;还可以使…

0603作业

/* * function: 直接插入排序* param [ in] * param [out] * return */ void insert_sort_linklist(linklist* head,datatype data){linklist* phead;while(NULL!p->pnext && p->pnext->param …

【学永远不嫌晚】Linux操作系统,linux教程,动力节点linux,老杜linux

碎碎念 总是遇到一些恶心的事情 看最新教程 老师安装的是 vm17 pro&#xff0c;想着也去安装&#xff0c;搜了一大堆&#xff0c;都指向官网下载。 https://support.broadcom.com/group/ecx/productdownloads?subfamilyVMwareWorkstationPro 安装显示没有 entitlement&#…

SpringCloud 服务调用 spring-cloud-starter-openfeign

spring-cloud-starter-openfeign 是 Spring Cloud 中的一个组件&#xff0c;用于在微服务架构中声明式地调用其他服务。它基于 Netflix 的 Feign 客户端进行了封装和增强&#xff0c;使其与 Spring Cloud 生态更好地集成。 1. Feign Feign 是一个声明式的 Web Service 客户端…

【Python】一文向您详细介绍 `__dict__` 的作用和用法

【Python】一文向您详细介绍 __dict__ 的作用和用法 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕…

网络安全形势与WAF技术分享

我一个朋友的网站&#xff0c;5月份时候被攻击了&#xff0c;然后他找我帮忙看看&#xff0c;我看他的网站、网上查资料&#xff0c;不看不知道&#xff0c;一看吓一跳&#xff0c;最近几年这网络安全形势真是不容乐观&#xff0c;在网上查了一下资料&#xff0c;1、中国信息通…