【iOS】—— Block总结

news2024/11/17 0:27:42

Block总结

    • 1. Block的使用规范
    • 2. __block修饰符
        • __block修饰符的底层原理
    • 3. Block的类型
      • NSGlobalBlock
      • NSStackBlock
      • NSMallocBlock
    • 4. Block的实现及本质
      • 初始化部分
      • 调用部分
      • 本质
    • 5. Block的捕获与内存管理
      • 捕获变量
      • 捕获对象
      • 内存管理
    • 6. 循环引用
      • 什么是循环引用
      • 循环引用解决方法
        • 1. 强弱共舞
        • 2. 手动中断循环
        • 3. 参数形式解决循环引用(block传参)
      • 循环引用的案例

1. Block的使用规范

Block完整格式:声明变量+定义

//block声明
void (^blockName) (int a, int b);
//block实现
int ^(int a, int b){

 };
 int (^blockName)(int numA, int numB) = ^int (int a, int b){
      return a+b;
  };

Block变量类似于函数指针。

用途:自动变量(局部变量)

  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

截获自动变量:带有自动变量的值在Block中表现为“截获自动变量”。

值得注意是 :在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获

Block判空:

在iOS中对一个 Block 进行判空实际上是在检查这个 Block变量是否有指向创建出来Block对象。

正如下面的例子:

 void (^myBlock)(void);
  if (myBlock) {
      NSLog(@"2");
  } else {
      NSLog(@"1");
  }
  NSLog(@"%p", myBlock);

运行结果:说明该Block为空
在这里插入图片描述
这个是Block实际函数指针的结构体:

 struct __block_impl {
  void *isa;//用于保存Block结构体的实例指针
  int Flags;//标志位
  int Reserved;//今后版本升级所需的区域大小
  void *FuncPtr;//函数指针,FuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{…}的部分
};

而对于判空的底层代码逻辑则要跟更详细一点:

 // 判空并执行 Block
  if (myBlock != NULL && myBlock->FuncPtr != NULL) {
      myBlock->FuncPtr();
  } else {
      printf("Block is NULL!\n");
  }

因此判断Block是否为空有两个步骤:检查 Block 变量是否为 nil检查 Block 中的函数指针是否为 NULL

2. __block修饰符

block可以截获变量,但是在block内部不能修改变量的值。

因此使用__block修饰符修饰变量,对需要在block内部赋值的变量,使用修饰符,确保可以对变量进行修饰。

id tyarray = @[@"blk", @"123", @"234"];
id __block arr = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
    arr = tyarray;
    NSLog(@"%@", arr);
};
__block修饰符的底层原理

我们都知道block捕获了带有__block修饰符的变量时可以修改变量的值,但是具体是怎么做到的?

以下面的变量为例:

 __block int b = 10;
 void (^block2)(void) = ^{
     NSLog(@"%d",b);
 };
 b = 100;
 block2();

原理:首先被__block修饰的变量b,声明变为b的__Block_byref_b_0结构体,加上__block修饰符的话捕获到的block内的变量为__Block_byref_b_0类型的结构体。

接下来看一下**__Block_byref_val_0**结构体的底层:

 struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};
  • __isa指针:Block_byref_age_0中也有isa指针也就是说__Block_byref_b_0本质也一个对象。
  • __forwarding__forwarding__Block_byref_b_0**结构体类型的,并且__forwarding存储的值为(__Block_byref_age_0 )&b,即结构体自己的内存地址。
  • __flags :0
  • __size sizeof(__Block_byref_b_0)即__Block_byref_b_0所占用的内存空间。
  • b :真正存储变量的地方,这里存储局部变量10。

接着将 __Block_byref_b_0结构体b存入__main_block_impl_0结构体中,并赋值给__Block_byref_b_0 *age
之后调用block,首先取出__main_block_impl_0中的b,通过b结构体拿到__forwarding指针,__forwarding中保存的就是__Block_byref_b_0结构体本身,这里也就是b(__Block_byref_b_0),在通过__forwarding拿到结构体中的b(10)变量并修改其值。

总结:block为什么能够修改变量的值?因为block把变量包装成了一个带有指针的对象,然后把b封装在结构体里面,block内部存储的变量为结构体指针,也可以通过指针找到内存地址修改变量的值。

3. Block的类型

Block的存储域
在这里插入图片描述

三种类型对应不同的区域:

在这里插入图片描述

NSGlobalBlock

一个Block没有访问外部的局部变量,或者访问的是全局变量,或者是静态局部变量。此时的Block是全局Block,数据存储在全局区。

    //block1没有引用到局部变量
    int a = 10;
    void (^block1)(void) = ^{
         NSLog(@"hello world");
    };
    NSLog(@"block1:%@", block1);

    //    block2中引入的是静态变量
    static int a1 = 20;
    void (^block2)(void) = ^{
        NSLog(@"hello - %d",a1);
    };
    NSLog(@"block2:%@", block2);


运行结果如下:
在这里插入图片描述

NSStackBlock

在捕获了局部变量之后,Block就会变成NSStackBlock,数据存储在栈中。

 int a1 = 20;
 void (^block2)(void) = ^{
     NSLog(@"hello - %d",a1);
 };
 NSLog(@"\nblock2:%@", block2);

运行结果如下:但是刚开始打印的时候,并不是NSStackBlock,原因是:**在ARC环境下系统会自动将block进行拷贝操作。**只要换成MRC就行了。
在这里插入图片描述

NSMallocBlock

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

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block 赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或 Grand Central Dispatch的 API 中传递 Block 时。
    int a = 10;
    void (^block1)(void) = ^{
        NSLog(@"%d",a);
    };
    NSLog(@"block1:%@", [block1 copy]);

    __block int b = 10;
    void (^block2)(void) = ^{
        NSLog(@"%d",b);
    };
    NSLog(@"block2:%@", [block2 copy]);


运行结果如下:
在这里插入图片描述
简单来说,没有捕获自动变量的就是数据区,捕获了自动变量但是没有进行copy操作就是栈区,copy之后就变成了堆区。

4. Block的实现及本质

初始化部分

初始化部分就是Block结构体

 //Block结构体
struct __main_block_impl_0 {
  struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
  struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
    impl.isa = &_NSConcreteStackBlock;  
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0结构体也就是Block结构体包含了三个部分:

  • 成员变量impl;
  • 成员变量Desc指针;
  • __main_block_impl_0构造函数;

struct __block_impl结构:包含Block实际函数指针的结构体

 struct __block_impl {
  void *isa;//用于保存Block结构体的实例指针
  int Flags;//标志位
  int Reserved;//今后版本升级所需的区域大小
  void *FuncPtr;//函数指针
};
  • _block_impl包含了Block实际函数指针FuncPtr,FuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{…}的部分
  • 还包含了标志位Flags,在实现block的内部操作时可能会用到。
  • 今后版本升级所需的区域大小Reserved。
  • __block_impl结构体的实例指针isa。

struct __main_block_desc_0结构:

 static struct __main_block_desc_0 {
  size_t reserved;//今后版本升级所需区域大小
  size_t Block_size;//Block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

Block构造函数__main_block_impl_0

作为构造函数注意和Block结构体是一个名字。
负责初始化__main_block_impl_0结构体(也就是Block结构体struct __block_impl)的成员变量

   //可以看到里面都是一些赋值操作
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

调用部分

函数原型 ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
逐步解析这段代码:

  • ((__block_impl *)myBlock)->FuncPtr:这部分将 myBlock 转换为 __block_impl 指针类型,并访问 FuncPtr 成员。它获取了块实现内部存储的函数指针。
  • ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr):在这里,函数指针被转换为一个函数类型,该函数接受一个类型为 __block_impl* 的参数,并返回 void。它将函数指针转换为可以调用的实际函数类型。
  • ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock):最后,使用 myBlock 作为参数,调用了所得到的函数指针。它使用块实现对象调用该函数。

本质

  1. 用一句话来说,Block是个对象(其内部第一个成员为isa指针)
  2. 在初始化函数里面:
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
    impl.isa = &_NSConcreteStackBlock;  
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc; 

impl.isa = &_NSConcreteStackBlock;
_NSConcreteStackBlock相当于该block实例的父类.将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中,这也证明了 block出生就是在栈上。

5. Block的捕获与内存管理

捕获变量

  • 全局变量:不捕获
  • 局部变量:捕获值
  • 静态全局变量:不捕获
  • 静态局部变量:捕获指针
  • const修饰的局部变量:捕获值
  • const修饰的静态局部常量:捕获指针

捕获对象

BLOCK 可以捕获对象,其中需要知道两个方法。

在捕获对象的时候代码出现了_main_block_copy_0_main_block_depose_0

  • main_block_copy_0作用就是调用_Block_object_assign,相当于retain,将对象赋值在对象类型的结构体变量main_block_impl_0中。在栈上的Block复制到堆时会进行调用。
  • main_block_dispose_0调用_Block_object_dispose,相当于release,释放赋值在对象类型的结构体变量中的对象。 在堆上的Block被废弃时会被调用。

内存管理

捕获外部变量引用计数的变化

 NSObject *objc = [NSObject new];
 NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1

 void(^strongBlock)(void) = ^{
     NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
 };
 strongBlock();

 void(^__weak weakBlock)(void) = ^{
     NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
 };
 weakBlock();
 void(^mallocBlock)(void) = [weakBlock copy];
 mallocBlock();

运行结果:
在这里插入图片描述

  • 第一个为1,就是简单创建引用计数+1
  • 第二个为3,strongBlock是在堆区的block,这里捕获objc时会对外部变量+1,这里将栈区的objc拷贝进堆区时,又进行了+1,所以为3。
  • 第三个为4,weakBlock是栈区的block,捕获objc没有进行拷贝就直接+1,所以为4。
  • 第四个为5,[weakBlock copy]进行了拷贝,因此引用计数再+1,所以为5。

6. 循环引用

什么是循环引用

对象持有导致对象不能及时的正常释放,容易造成内存泄漏。

在这里插入图片描述

#import "ViewController.h"
typedef void (^TBlock)(void);
@interface ViewController ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) TBlock block;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
  
    self.name = @"View";
    self.block = ^() {
       NSLog(@"%@", self.name);
    };
    self.block();
    
}

@end

self持有了block,block持有了self,导致循环引用。 编译器也会提示:Capturing 'self' strongly in this block is likely to lead to a retain cycle

如果单方面取消一方的持有即可取消循环。

  // 不会引起循环引用
    void(^blk1)(void);
    blk1 = ^() {
        NSLog(@"%@", self.name);
    };
    blk1();

这个案例就没有出现循环引用是因为当前self,也就是ViewController并没有对block进行强持有,block的生命周期只在viewDidLoad方法内,viewDidLoad方法执行完,block就会释放。

循环引用解决方法

之前提到过weak,可以解决循环引用问题。

weak的使用

  __weak typeof(self) weakSelf = self;

typeof(self)typeof 是一个运算符,用于获取表达式的类型。在这种情况下,表达式是 self,它代表当前对象的引用。

  __weak typeof(self) weakSelf = self;
  self.block = ^(){
      NSLog(@"%@", weakSelf.name);
  } ;
  self.block();

此时self持有blockblock弱引用self,弱引用会自动变为nil,强持有中断,所以不会引起循环引用。

之前学习GCD的时候将其他线程麻烦的操作执行完之后回到主线程,如果在执行其他线程的时ViewController被销毁,就会导致内部的函数来不及打印,导致想打印的数值为空。

  。。。。。。。
	__weak typeof(self) weakSelf = self;
    self.block = ^(){
    // 延迟2秒钟
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf.name);
        });
    };
    self.block();
    [self fakeDealloc];
}
- (void)fakeDealloc {
    NSLog(@"调用了dealloc 模拟ViewController模拟销毁");
    // 模拟viewController被销毁
    self.name = nil;
}

运行结果:
在这里插入图片描述

1. 强弱共舞

为了解决上面的问题,由此引出了强弱共舞

#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) void (^block)(void);
@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   // Do any additional setup after loading the view.
   
   self.name = @"View";
   __weak typeof (self) weakself = self;
   self.block = ^{
       __strong __typeof(weakself) strongself = weakself;
       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
               NSLog(@"Block executed after 2 seconds. Name: %@", strongself.name);
       });
   };
   self.block();
   [self dealloc1];
}

- (void)dealloc1 {
   NSLog(@"Object is deallocating.");
}
@end

则一切就会正常打印。

因为__weak会自动置为nil,所以这里使用__strong(strong-weak-dance)暂时延长 self的生命周期,使得可以正常打印。

为什么强弱共舞能够避免循环引用,不是也调用了self? 因为这里strongself是一个临时的变量,出了作用域也跟着释放了,所以不会出现循环引用🐮
简单分析:

  • 在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self->block->strongWeak->weakSelf-> self。
  • weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放。
2. 手动中断循环
//手动中断
    self.name = @"ViewController";
    __block ViewController *vc = self;
    __weak typeof (self) weakself = self;
    self.block = ^{
        __strong __typeof(weakself) strongself = weakself;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongself.name);
            vc = nil;
        });
    };
    self.block();
    [self dealloc1];

这里借助临时变量vc之后,持有关系变为:self->block->vc->selfvc在block使用完成之后就被置为nilblock不构成对self的持有关系了,因此这里就不构成循环引用问题。

3. 参数形式解决循环引用(block传参)
 //Block传值
  self.name = @"ViewController";
  self.block1 = ^(ViewController *vc) {
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          NSLog(@"%@", vc.name);
      });
  };
  self.block1(self);
  [self dealloc1];

self作为参数参入block中,进行指针拷贝,并没有对self进行持有。

因为使用捕获self实际上会将self转换为弱引用,从而避免了循环引用。

循环引用的案例

  • 静态变量持有
   // staticSelf_定义:
  static ViewController *staticSelf_;

  - (void)blockWeak_static {
      __weak typeof(self) weakSelf = self;
      staticSelf_ = weakSelf;
  }

weakSelf虽然是弱引用,但是staticSelf_静态变量,并对weakSelf进行了持有,staticSelf_释放不掉,所以weakSelf也释放不掉!导致循环引用!

  • __strong持有问题
 - (void)block_weak_strong {

    __weak typeof(self) weakSelf = self;

    self.doWork = ^{
        __strong typeof(self) strongSelf = weakSelf;
        NSLog(@"B objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)strongSelf)));

        weakSelf.doStudent = ^{
            NSLog(@"%@", strongSelf);
            NSLog(@"B objc -----retainCount : %lu", CFGetRetainCount(((__bridge CFTypeRef)strongSelf)));
        };

       weakSelf.doStudent();
    };

   self.doWork();
}

  • 在doWork内部,__strong typeof(self) strongSelf = weakSelf;
  • 用强引用持有了weakSelf,和前的情况类似,strongSelf的生命周期也就在doWork方法内;
  • 这里需要注意的是,doStudent这个内部block调用了外部变量,所以他会从栈block copy到堆中,从而导致strongSelf的引用计数增加,无法释放掉,进而导致循环引用!

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

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

相关文章

抓包工具Charles

1、抓包的目的 遇到问题需要进行分析 发现bug需要定位 检查数据传输的安全性 接口测试时,开发给的需求文档不详细 在弱网环境下APP的测试 2、Charles是java语言编写的程序,本质是一个代理服务器,通过拦截服务端和客户端的http请求&#xff0…

谷粒商城实战笔记-63-商品服务-API-品牌管理-OSS获取服务端签名

文章目录 一,创建第三方服务模块thrid-party1,创建一个名为gulimall-third-party的模块2,nacos上创建third-party命名空间,用来管理这个服务的所有配置3,配置pom文件4,配置文件5,单元测试6&…

西瓜视频下载助手,支持批量下载视频!

我们每天都在接触海量的视频内容,但想要保存自己喜爱的视频却常常受限于平台的各种限制。因此,今天给大家带来一个超级实用的神器——西瓜视频下载助手。 西瓜视频下载助手(电脑) 工具支持西瓜视频的下载和今日头条的视频下载&a…

kafka详解及应用场景介绍

Kafka架构 Kafka架构,由多个组件组成,如下图所示: 主要会包含:Topic、生产者、消费者、消费组等组件。 服务代理(Broker) Broker是Kafka集群中的一个节点,每个节点都是一个独立的Kafka服务器…

如何使用EXCEL访问WinCC中的实时数据实现报表

如果项目已经做好了,不想改动现有项目。那么可以使用 EXCEL 通过 OPC 方式访问 WinCC 项目的数据。预先定义好 EXCEL 表格样式,通过以下方式实现。通过以下步骤打开 EXCEL 中的 VB 编辑器 引用 WinCC 提供的 OPC 客户端 Control 控件: Siemens OPC DAAut…

Godot游戏制作 04平台设计

新建创景,添加AnimatableBody2D节点。 添加Sprite2D节点 拖动图片 剪裁图片,吸附模式:像素吸附 添加CollisionShape2D,设置实际形状为矩形 重命名AnimatableBody2D节点为Platform,保存场景,拖动platform场景…

VirtualBox 安装Centos 7 避坑指南 SSH连不上 镜像失效 静态网络配置等

背景 几乎每次安装Centos 7 时,都会遇到各种各样的问题,毕竟每次安装动辄就是半年几年,几乎都是在换工作时,有了新机器才会倒腾一次,时间久远,就会忘记一些细节,这次整理一下,避免以…

数字图像处理笔记(三) ---- 傅里叶变换的基本原理

系列文章目录 数字图像处理笔记(一)---- 图像数字化与显示 数字图像处理笔记(二)---- 像素加图像统计特征 数字图像处理笔记(三) ---- 傅里叶变换的基本原理 文章目录 系列文章目录前言一、傅里叶变换二、离散傅里叶变…

Vue3与Element-plus配合 直接修改表格中的一项数据——控制输入框的显示与隐藏

利用控制与隐藏输入框,直接修改表格中的每一项数据。 <!-- 表格模块 --> <div><el-table :data"tablelist" style"width: 100%"><el-table-column align"center" prop"deposit" label"接单押金">&l…

【动态规划】力扣.213. 打家劫舍 II

你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 &#xff0c;这意味着第一个房屋和最后一个房屋是紧挨着的。同时&#xff0c;相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一…

自动驾驶汽车普及之路

全球范围内自动驾驶汽车的普及正在加速。英国最近通过了 自动驾驶汽车法案 以便在未来几年内实现全自动驾驶和部分自动驾驶汽车安全融入社会。 更多自动驾驶汽车 目前&#xff0c;中国是世界上测试自动驾驶出租车最多的国家。而在美国&#xff0c;各大城市已将“自动驾驶出租车…

人工智能历史:从梦想到现实的变革之路

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

python机器学习8--自然语言处理(2)

1&#xff0e;移除用词 在很多情况下&#xff0c;有一些文章内的英文字符、标点符号分词的结果不符合自己的预期&#xff0c;会出现一些不想要的分词&#xff0c;此时就能通过以下的函数自己设定用词&#xff0c;并且删除。 jieba.analyse.set_stop_words("stop_words.tx…

哈默纳科HarmonicDrive谐波减速机的使用寿命计算

在机械传动系统中&#xff0c;减速机的应用无处不在&#xff0c;而HarmonicDrive哈默纳科谐波减速机以其独特的优势&#xff0c;如轻量、小型、传动效率高、减速范围广、精度高等特点&#xff0c;成为了众多领域的选择。然而&#xff0c;任何机械设备都有其使用寿命&#xff0c…

第五周:机器学习笔记

第五周学习周报 摘要Abstract机器学习——神经网络训练不起来怎么办&#xff1f;1. 局部最小点和鞍点1.1 Momentum(动量)1.2 Local minima和saddle point谁出现的情况更多&#xff1f; 2. 批次&#xff08;Batch&#xff09;2.1 Small Batch v.s. Large Batch Pytorch学习——T…

python项目通过docker部署到Linux系统并实现远程访问

背景需求&#xff1a;在Windows系统编写了简单的python代码&#xff0c;希望能通过docker打包到Linux Ubuntu系统中&#xff0c;并运行起来&#xff0c;并且希望在本地Windows系统中能通过postman访问。 目录 一、原本的python代码 二、创建一个简单的Flask应用程序 三、创…

SQL核心基础语法—快速入门MySQL

1.MySQL简介 MySQL其实是DBMS软件系统&#xff0c;也就是说MySQL并不是一个数据库&#xff0c;这是很多人的误区。我们经常听到的Oracle&#xff0c;MySQL&#xff0c;微软SQL Server&#xff0c;都是DBMS软件系统&#xff0c;我们只是通过这些系统来管理和维护数据库而已&…

系统架构师(每日一练7)

每日一练 1.关于网络延迟正确的是()。答案与解析 A.在对等网络中&#xff0c;网络的延迟大小与网络中的终端数量无关 B.使用路由器进行数据转发所带来的延迟小于交换机, C.使用internet服务器可最大程度地减小网络延迟 D.服务器延迟的主要影响因素是队列延迟和磁盘10延迟 2.以…

# redis 高级应用--使用 redis 消息队列完成秒杀过期订单处理(2)

redis 高级应用–使用 redis 消息队列完成秒杀过期订单处理&#xff08;2&#xff09; 一、springDataRedis 中定义消息的监听器 1、在 JAVA 程序中监听 redis 消息&#xff1a;配置监听 redis 的消息。 如果要在 java 代码中监听 redis 的主题消息&#xff0c;需要自定义处…

Android 列表或网格形式展示大量数据:RecyclerView

目录 RecyclerView是什么如何使用RecyclerView 涉及到的类LayoutManager为Item设置不同的布局样式制作拖动的RecyclerView 一、RecyclerView是什么 RecyclerView是Android支持库中的一个控件&#xff0c;用于在列表或网格形式展示大量数据。它是ListView的升级版&#xff0c…