[iOS]从拾遗到Runtime(上)

[iOS]从拾遗到Runtime(上)

文章目录

  • [iOS]从拾遗到Runtime(上)
    • 写在前面
    • 名词介绍
      • instance 实例对象
      • class 类对象
      • meta-class 元类对象
        • 为什么要有元类?
      • runtime
      • Method(objc_method)
        • SEL(objc_selector)
        • IMP
      • 类缓存(objc_cache)
      • Category(objc_category)
    • 消息传递
      • 消息传递的流程
    • 消息转发
      • 动态方法解析
      • 备用接收者
      • 完整消息转发
    • 参考博客

写在前面

最近看到学弟学习isMemberOfClass方法和isKindOfClass方法时遇到的一个问题

[[NSString class] isMemberOfClass:[NSObject class]];
[NSString isMemberOfClass:[NSObject class]];

这两句有啥区别
我知道第二句是类方法 第一句呢 和第二句啥区别 也是类方法吗

我刚想说 看我博客去
但是想了想我的博客真的惨不忍睹
当时写的时候语焉不详 再加上我对这一块也没啥印象了
于是问问gpt吧

笨蛋gpt信誓旦旦告诉我 类对象就是实例

好 问来问去不如自己动手 毕竟高中就教过实践是检验真理的唯一标准

NSLog(@"%@", [[NSString class] stringWithFormat:@"123"]);
NSLog(@"%d", [NSString isEqual:[NSString class]]);

打印结果
请添加图片描述
第一句是让[NSString class]的返回值再调用NSString的类方法 成功
第二句是用isEqual方法比较俩者是否意义相同 成功

那么目前来看 好像可以把二者作用画等号了

但这还不够
class方法返回值是类对象
到底啥是类对象 实例 元类?
我们说 类方法实例方法 类对象实例对象
那类对象就是实例的说法显然不太合理

再加上消息转发那部分先前也是写的匆匆忙忙
不如在这一篇runtime一次性讲清楚(`ヮ´ )

名词介绍

instance 实例对象

instance对象就是通过alloc方法创建出来的对象,每次调用alloc方法都会生成新的instance对象

instance对象在内存中存放的信息包括

  1. isa指针
  2. 其他成员变量
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

class 类对象

class对象的作用是用来描述一个instance对象,它内部存放一个类的属性信息(@property)、对象方法信息(instance method)、协议信息(protocol)、成员变量信息(ivar),另外class对象里面还有两个指针,isa指针 和 superclass指针。

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针

typedef struct objc_class *Class;

查看objc/runtime.h中objc_class结构体的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),
该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象
类对象在编译期产生用于创建实例对象,是单例

meta-class 元类对象

meta-class对象的作用是用来描述一个class对象

跟class一样,元类对象在内存中也是只有一份的
它内部存储了 isa指针 + superclass指针 + 类方法信息(+方法)

类对象中的元数据存储的都是如何创建一个实例的相关信息
那么类对象和类方法应该从哪里创建呢? 就是从isa指针指向的结构体创建
类对象的isa指针指向的我们称之为元类(metaclass), 元类中保存了创建类对象以及类方法所需的所有信息

来看这张很经典的图
在这里插入图片描述
通过上图我们可以看出整个体系构成了一个自闭环,struct objc_object结构体实例它的isa指针指向类对象
类对象的isa指针指向了元类,super_class指针指向了父类的类对象
而元类的super_class指针指向了父类的元类,那元类的isa指针又指向了自己

为什么要有元类?

元类(Meta Class)是一个类对象的类。在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己

runtime

因为Objective-C是一门动态语言,所以它将一些决策工作从编译、连接过程推迟到运行时。所以只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objective-C运行框架的一块基石

通俗来说
OC 是一门动态语言,函数调用变成了消息发送,在编译期不能知道要调用哪个函数。所以 Runtime 无非就是去解决如何在运行时期找到调用方法这样的问题

Method(objc_method)

Method和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码

来看定义

runtime.h
/// An opaque type that represents a method in a class definition.代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;

在这个结构体中能看到SEL和IMP,说明SEL和IMP其实都是Method的属性

SEL 与 IMP 的关系非常类似于 HashTable 中 key 与 value 的关系
OC中不支持函数重载的原因就是因为一个类的方法列表中不能存在两个相同的 SEL
但是多个方法却可以在不同的类中有一个相同的SEL
不同类的实例对象执行相同的 SEL 时
会在各自的方法列表中去根据 SEL 去寻找自己对应的IMP
这使得OC可以支持函数重写

在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

SEL(objc_selector)

objc_msgSend函数第二个参数类型为SEL,它是selector在Objective-C中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:

选择器 好名字不是吗
眼尖记性好的观众可能记着之前消息转发讲过选择子
其实一个意思

顺带看看那里

Objc.h
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;

然后

@property SEL selector;

相信大家不难看出selector是SEL的一个实例

其实selector就是个映射到方法的C字符串,你可以用 Objective-C 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个 SEL 类型的方法选择器。
selector既然是一个string,我觉得应该是类似className+method的组合,命名规则有两条:

  • 同一个类,selector不能重复
  • 不同的类,selector可以重复

这也带来了一个弊端,我们在写C代码的时候,经常会用到函数重载,就是函数名相同,参数不同,但是这在Objective-C中是行不通的,因为selector只记了method的name,没有参数,所以没法区分不同的method。

- (void)caculate(NSInteger)num;
- (void)caculate(CGFloat)num;

这种就会报错
在这里插入图片描述在不同类中相同名字的方法所对应的方法选择器是相同的
即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器

IMP

IMP就是指向最终实现程序的内存地址的指针

/// A pointer to the function of a method implementation.  指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...); 
#endif

类缓存(objc_cache)

当Objective-C运行时通过跟踪它的isa指针检查对象时,它可以找到一个实现许多方法的对象
然而,你可能只调用它们的一小部分,并且每次查找时,搜索所有选择器的类分派表没有意义
所以类实现一个缓存,每当你搜索一个类分派表,并找到相应的选择器,它把它放入它的缓存
所以当objc_msgSend查找一个类的选择器,它首先搜索类缓存

这是基于这样的理论:如果你在类上调用一个消息,你可能以后再次调用该消息

为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在上述的objc_cache
所以在实际运行中,大部分常用的方法都是会被缓存起来的
Runtime系统实际上非常快,接近直接执行内存地址的程序速度

Category(objc_category)

Category是表示一个指向分类的结构体的指针

来看

struct category_t { 
//    name:是指 class_name 而不是 category_name
    const char *name; 
//    cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象
    classref_t cls; 
//    instanceMethods:category中所有给类添加的实例方法的列表
    struct method_list_t *instanceMethods;
//    classMethods:category中所有添加的类方法的列表 
    struct method_list_t *classMethods;
//    protocols:category实现的所有协议的列表
    struct protocol_list_t *protocols;
//    instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的
    struct property_list_t *instanceProperties;
};

从上面的category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量

消息传递

消息传递的流程

Objective-C中所有方法的调用/类的生成都在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现,理论上你可以在运行时通过类名/方法名调用到任意Objective-C 方法,替换任何类的实现以及新增任意类

比方说我们写一个调用方法[receiver foo]
首先这行代码会被改写成objc_msgSend(self, _cmd);这是一个runtime的函数
其原型如下

// 第一个参数类型是发送者, 第二个参数类型是SEL。SEL在OC中是selector方法选择器
id objc_msgSend ( id _Nullable self, SEL op, ... );

实际上,我们在调用的方法的过程,其实在Runtime中就是消息发送
objc_msgSend的实现是由汇编语言实现,根据CPU架构实现的过程各不相同

objc_msgSend会做以下几件事情:

  • 检测这个selector是不是要忽略 检查target是不是为nil

  • 如果这里有相应的nil的处理函数,就跳转到相应的函数中
    如果没有处理nil的函数,就自动清理并返回(这一点就是为何在Objective-C中给nil发送消息不会崩溃的原因)

  • 确定不是给nil发消息之后,在该对象的类(Class)的缓存中查找方法对应的IMP**(俗称快查)**

  • 如果找到,就跳转进去执行;
    如果没有找到,执行下一步;

  • 在方法列表中继续查找,一直找到NSObject为止;(俗称慢找)
    如果还没有找到,那就需要开始消息转发阶段了。至此,发送消息Messaging阶段完成。这一阶段主要完成的是通过select()快速查找IMP的过程

self与_cmd是两个编译器会自动添加的隐藏参数,self是一个指向接收对象的指针,_cmd为方法选择器。这个函数的实现为汇编版本,苹果开源的项目中共有6种对不同平台的汇编实现

这个东西有空再补

消息转发

前文介绍了进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索直到继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。那么消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会
在这里插入图片描述

动态方法解析

首先Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:让你有机会提供一个函数实现
如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程

#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSString *parameter = @"Some parameter"; // 假设的参数
    // 执行foo函数并传递参数
    [self performSelector:@selector(foo:) withObject:parameter];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(foo:)) { // 如果是执行foo函数,就动态解析,指定新的IMP
        class_addMethod([self class], sel, (IMP)fooMethod, "v@:@"); // 更新编码以匹配NSString参数
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void fooMethod(id obj, SEL _cmd, NSString *param) { // 添加参数声明
    NSLog(@"Doing foo with parameter: %@", param); // 使用传入的参数
}

@end

打印结果
请添加图片描述

可以看到虽然没有实现foo:这个函数,但是我们通过class_addMethod动态添加fooMethod函数,并执行fooMethod这个函数的IMP。从打印结果看,成功实现了

如果resolve方法返回 NO ,运行时就会移到下一步:forwardingTargetForSelector。

备用接收者

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会

举例如下

#import "ViewController.h"
#import "objc/runtime.h"

@interface MYPerson: NSObject

@end

@implementation MYPerson

- (void)foo {
    NSLog(@"Doing foo");//MYPerson的foo函数
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //执行foo函数
    [self performSelector:@selector(foo)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;//返回YES,进入下一步转发
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return [MYPerson new];//返回MYPerson对象,让MYPerson对象接收这个消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

@end

结果正确
在这里插入图片描述
如果在这一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了

完整消息转发

首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型

  • 如果-methodSignatureForSelector:返回nil
    Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了

  • 如果返回了一个函数签名
    Runtime就会创建一个NSInvocation 对象并发送 -forwardInvocation:消息给目标对象

#import "ViewController.h"
#import "objc/runtime.h"

@interface MYPerson: NSObject

@end

@implementation MYPerson

- (void)foo {
    NSLog(@"Doing foo");//MYPerson的foo函数
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //执行foo函数
    [self performSelector:@selector(foo)];
}

// resolveInstanceMethod方法,用于动态解析未实现的方法
// 返回YES表示尝试继续转发过程
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;//返回YES,进入下一步转发
}

// forwardingTargetForSelector方法,寻找可以响应此选择器的其他对象
// 返回nil表示无合适对象,继续向后转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;//返回nil,进入下一步转发
}

// methodSignatureForSelector方法,为尚未识别的选择器提供方法签名
// 当选择器为"foo"时,提供一个适合的签名,以便能够调用forwardInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }   
    return [super methodSignatureForSelector:aSelector];
}

// forwardInvocation方法,用于处理无法直接识别的消息转发
// 这里创建一个MYPerson实例,并尝试在此实例上调用原始选择器
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    MYPerson *p = [MYPerson new]; // 创建MYPerson的实例
    if([p respondsToSelector:sel]) { // 检查MYPerson实例是否能响应此选择器
        [anInvocation invokeWithTarget:p]; // 能响应则在MYPerson实例上调用
    }
    else {
        [self doesNotRecognizeSelector:sel]; // 否则,报告选择器未识别
    }
}

@end

结果不错
在这里插入图片描述
从打印结果来看,我们实现了完整的转发

通过签名,Runtime生成了一个对象anInvocation,发送给了forwardInvocation
我们在forwardInvocation方法里面让Person对象去执行了foo函数

签名参数v@:的解释在苹果文档Type Encodings有详细的解释
here

就这张图
在这里插入图片描述

参考博客

OC对象的本质(中)
iOS runtime详解
Objective-C Runtime

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

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

相关文章

【how2j JQuery部分】课后题答案及相关笔记

练习题 <script src"jquery.min.js"></script><script>$(function(){$(tr:odd).css({"background-color":"#f8f8f8"});}); </script> <style> table{border-collapse:collapse;width:90%;} tr{border-bottom-sty…

安捷伦E4991A美国原装二手KEYSIGHT、E4990A阻抗分析仪

商品品牌&#xff1a;安捷伦Agilent/是德KEYSIGHT 商品型号&#xff1a;E4990A 商品价格&#xff1a;面议或电议 商品详情&#xff1a; Agilent E4990A阻抗分析仪&#xff0c;20 Hz 至 10/20/30/50/120 MHz 主要特性与技术指标 5 种频率选件&#xff1b;20 Hz 至 10/20/30/50/1…

C++学习————第十天(string的基本使用)

1、string 对象类的常见构造 (constructor)函数名称 功能说明&#xff1a; string() &#xff08;重点&#xff09; 构造空的string类对象&#xff0c;即空字符串 string(const char* s) &#xff08;重点&#xff09;…

Java_从入门到JavaEE_11

一、抽象类及抽象方法 1.认识抽象类及抽象方法 应用场景&#xff1a;当一个方法必须在父类中出现&#xff0c;但是这个方法又不好实现&#xff0c;就把该方法变成抽象方法&#xff0c;交给非抽象的子类去实现 实例&#xff1a; //抽象类 public abstract class 类名{//抽象方…

Ansible----playbook模块之templates模块、tags模块、roles模块

目录 引言 一、templates模块 &#xff08;一&#xff09;关键信息 &#xff08;二&#xff09;实际操作 1.定义主机组 2.设置免密登录 3.分别建立访问目录 4.定义模板文件 5.创建playbook文件 6.执行剧本 7.验证结果 二、tags模块 &#xff08;一&#xff09;创建…

stm32_RTC_2_HAL——stm32CudeMX

介绍 RTC&#xff08;实时时钟&#xff09;不仅仅提供计数功能&#xff0c;它是一个完整的时钟和日历模块&#xff0c;用于提供日期和时间信息。RTC 能够提供年、月、日、星期、时、分、秒等时间信息&#xff0c;并且通常具有闹钟功能&#xff0c;可以用于定时唤醒或触发事件。…

Qt | QLineEdit 类(行编辑器)

01、上节回顾 Qt | QComboBox(组合框)02、QLineEdit 1、QLineEdit 类是 QWidget 类的直接子类,该类实现了一个单行的 输入部件,即行编辑器,见右图 2、验证器(QValidator 类)和输入掩码简介:主要作用是验证用户输入的字符是否符合验证器 的要求,即限制对用户的输入,比…

详细介绍ARM-ORACLE Database 19c数据库下载

目录 1. 前言 2. 获取方式 2.1 ORACLE专栏 2.2 ORACLE下载站点 1. 前言 现有网络上已有非常多关于ORACLE数据库机下载的介绍&#xff0c;但对于ARM平台的介绍不多&#xff0c;借此机会我将该版的下载步骤做如下说明&#xff0c;希望能够一些不明之人提供帮助和参考 2. 获…

【STM32G474】利用Cpp编写STM32代码后,Cubemx修改配置后代码报错147个error,如何处理?

问题描述 打开Cubemx&#xff0c;添加TIM7用于定时器精准延时&#xff0c;生成代码后&#xff0c;Keil提示有147个error。 之前是Cubemx是没有问题的&#xff0c;是利用Cpp编写stm32&#xff08;将Keil改为Version6&#xff09;后才导致Cubemx配置失败&#xff1a; debug成功…

[学习笔记]CyberDog小米机器狗 开发学习

1、机器狗本身是UbuntuROS2系统 2、控制机器人只需要了解lcm和Ros topic通讯 3、传感器数据&#xff08;包括一些imu(/imu)、激光雷达(/scan)&#xff09;会进行topic的一个广播。 仿真环境通信接口&#xff1a; -命令输入(见后续运控说明) 运控lcm数据接口 Motion man…

Gmail邮箱怎么注册?2024年完整指南(包含跳过手机号验证)

一、为什么要注册Gmail邮箱&#xff1f; 全球通用性&#xff1a;Gmail是一个全球性的邮件服务平台&#xff0c;被广泛认可和信赖。因为客户对于Gmail的接受度高&#xff0c;无需担心邮件被自动标记为垃圾邮件。 整合营销工具&#xff1a;通过Gmail账号&#xff0c;你可以轻松…

CleanMyMac X 4.15.3 版本发布

CleanMyMac X 4.15.3 版本发布&#xff0c;一款苹果 macOS 系统好用的伴侣软件&#xff0c;其包含 1.一键深度清理。2.系统垃圾专清。3.大/旧文件专清。4.系统提速。5.性能悬浮窗。6.恶意软件防护。7.隐私保护。8.软件卸载器。9.软件更新器等 9 大功能&#xff0c;为您的苹果电…

Flask-HTTP请求、响应、上下文、进阶实验

本节主要目录如下&#xff1a; 一、请求响应循环 二、HTTP请求 2.1、请求报文 2.2、Request对象 2.3、在Flask中处理请求 2.4、请求钩子 三、HTTP响应 3.1、响应报文 3.2、在Flask中生成响应 3.3、响应格式 3.4、Cookie 3.5、session&#xff1a;安全的Cookie 四、…

[公开课学习]台大李宏毅-自注意力机制 Transformer

自注意力机制 存在一些问题&#xff0c;将vector set/sequence作为input&#xff0c;例如&#xff1a; 文字处理&#xff1a;将文字用one-hot表示&#xff0c;或者向量空间的向量表示&#xff0c;然后进行翻译任务等语音处理&#xff1a;25ms音频作为一个向量&#xff0c;10m…

初识C++ · 模板初阶

目录 1 泛型编程 2 函数模板 3 类模板 1 泛型编程 模板是泛型编程的基础&#xff0c;泛型我们碰到过多次了&#xff0c;比如malloc函数返回的就是泛型指针&#xff0c;需要我们强转。 既然是泛型编程&#xff0c;也就是说我们可以通过一个样例来解决类似的问题&#xff0c…

pytorch基础: torch.unbind()

1. torch.unbind 作用 说明&#xff1a;移除指定维后&#xff0c;返回一个元组&#xff0c;包含了沿着指定维切片后的各个切片。 参数&#xff1a; tensor(Tensor) – 输入张量dim(int) – 删除的维度 2. 案例 案例1 x torch.rand(1,80,3,360,360)y x.unbind(dim2)print(&…

gitlab集群高可用架构拆分部署

目录 前言 负载均衡器准备 外部负载均衡器 内部负载均衡器 (可选)Consul服务 Postgresql拆分 1.准备postgresql集群 手动安装postgresql插件 2./etc/gitlab/gitlab.rb配置 3.生效配置文件 Redis拆分 1./etc/gitlab/gitlab.rb配置 2.生效配置文件 Gitaly拆分 1.…

BACnet转MQTT网关智联楼宇json格式自定义

智能建筑的BACnet协议作为楼宇自动化领域的通用语言&#xff0c;正逐步迈向更广阔的物联网世界。随着云计算和大数据技术的飞速发展&#xff0c;如何将BACnet设备无缝融入云端生态系统&#xff0c;成为众多楼宇管理者关注的焦点。本文将以一个实际案例&#xff0c;揭示BACnet网…

60、郑州大学附属肿瘤医院 :用于预测胃癌患者术后生存的深度学习模型的开发和验证[同学,我们的人生应当是旷野]

馒头老师要说的话&#xff1a; 我近期看了一下北京的脑机公司&#xff0c;大概是我之前对这一行业太过于乐观&#xff0c;北京的BCI公司和研究所&#xff0c;比上海、深圳、杭州甚至是重庆都要少&#xff0c;门槛也要高很多。也有我自己的原因&#xff0c;有时站的太高&#x…

92、动态规划-最小路径和

思路&#xff1a; 还是一样&#xff0c;先使用递归来接&#xff0c;无非是向右和向下&#xff0c;然后得到两种方式进行比较&#xff0c;代码如下&#xff1a; public int minPathSum(int[][] grid) {return calculate(grid, 0, 0);}private int calculate(int[][] grid, int …
最新文章