【iOS】——Runtime学习

文章目录

  • 一、Runtime介绍
  • 二、Runtime消息传递
  • 三、实例对象、类对象、元类对象
  • 四、isa_t结构体的具体实现
  • 五、cache_t的具体实现
  • 六、class_data_bits_t的具体实现
  • 七、Runtime消息转发
    • 动态方法解析
    • 备用接收者
    • 完整消息转发


一、Runtime介绍

iOS的Runtime,通常称为Objective-C Runtime,是一个C语言库,包含了很多底层的纯C语言API。,它是Objective-C语言动态特性的基石。这个系统在程序运行时提供了一系列强大的功能,允许我们在应用运行过程中动态地操作类和对象,执行诸如检查和改变对象、交换方法实现、动态添加方法或属性等操作。

高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的,这里采用了消息传递的机制,在程序运行之前,消息都没有与任何方法绑定起来。只有在真正运行的时候,才会根据函数的名字来,确定该调用的函数。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。

二、Runtime消息传递

当你通过对象调用方法时,例如像这样[obj someMethod]编译器会将其转换为一个消息发送的底层调用,通常是 objc_msgSend(obj, @selector(someMethod))。这个函数接受两个主要参数:方法的调用者方法选择器(也就是方法名)。

objc_msgSend,其 “ 原型” ( prototype )如下:

void objc_msgsend(id self, SEL cmd, ...)

第一 个参数代表接收者也就是方法调用者,第二个参数代表方法选择器(SEL 是选择子的类型)也就是方法的名字,后续参数就是消息中的 那些参数,其顺序不变。

在进行具体的方法实现查找时:

  1. 首先,Runtime系统会通过obj的 isa 指针找到其所属的class
  2. 接着在这个类的方法列表(method list)中查找与选择器(someMethod)匹配的方法实现(IMP)。
  3. 如果在当前类中没有找到,Runtime会沿着类的继承链往它的 superclass 中查找,直到到达根类(通常为 NSObject)。
  4. 一旦找到someMethod这个函数,就去执行它的实现IMP 。

但一个class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class 中另一个重要成员objc_cache 做的事情 - 再找到someMethod之后,把someMethodmethod_name 作为keymethod_imp作为value 给存起来。当再次收到someMethod消息的时候,可以直接在cache 里找到,避免去遍历objc_method_list

因此Runtime的消息传递流程应该是

  1. 首先,Runtime系统会通过obj的 isa 指针找到其所属的class
  2. 接着在这个类的缓存中查找与选择器匹配的方法实现
  3. 如果缓存中没找到接着在这个类的方法列表(method list)中查找与选择器(someMethod)匹配的方法实现(IMP)。
  4. 如果在当前类中没有找到,Runtime会沿着类的继承链往它的 superclass 中查找,也是先查缓存再查方法列表,直到到达根类(通常为 NSObject)。
  5. 一旦找到someMethod这个函数,就去执行它的实现IMP 。

从下面的源代码可以看到cache是存在objc_class 结构体中的。

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
}

三、实例对象、类对象、元类对象

下面是OC2.0中关于类和对象的定义

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;
}

其关系图如下图所示:
在这里插入图片描述

  • 分析上面的源代码不难看出在OC2.0中每个对象都有一个isa_t类型的结构体(也就是平常所说的isa指针,其实它的本质是个结构体)。
  • objc_class继承于objc_object。所以在objc_class中也会包含isa_t类型的结构体isa。所以OC中类其实也是一个对象。在objc_class中,除了isa之外,还有3个成员变量,一个是父类的指针,一个是方法缓存,最后一个数据域(存储了类中的详细信息包括方法列表)。
  • objc_object被源码typedef成了id类型,这也就是我们平时遇到的id类型。这个结构体中就只包含了一个isa_t类型的结构体。

当一个对象的实例方法被调用的时候,会通过isa指针找到相应的类,接着进行一系列操作,如果是对象的类方法被调用该怎么办呢,这里就引入了元类(meta-class)的概念。meta-class它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

当一个对象的实例方法被调用的时候,会通过isa指针找到相应的元类,在元类的缓存中查找与选择器匹配的方法实现,如果没有找到再到元类的数据域的方法列表中查找,如果还没找到则沿着继承链找元类的父类,直到根类(NSObject)。如果找到就去执行它的方法实现。

对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。
类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。

对象,类,元类对应关系的图如下图:
在这里插入图片描述
图中实线是父类指针,虚线是isa指针

  • Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root
    class(class)的superclass指向nil。
  • 每个Class都有一个isa指针指向唯一的Meta class
  • Root class(meta)的superclass指向Root class(class),也就是NSObject
  • 每个Meta class的isa指针都指向Root class (meta)

类对象和元类在编译期产生是单例(只能有一个),实例对象是运行期产生的,可以有无数个

四、isa_t结构体的具体实现

前面提到isa指针的本质是个结构体,其源代码如下:

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

通过源码不难发现isa是一个union联合体。

  • 有一个无参数的构造函数用来进行默认的初始化
  • 有一个接受一个uintptr_t类型的值来初始化bits字段的构造函数,允许直接以整数形式初始化isa_t
  • 有一个指向所属类的指针
  • 有一个无符号整数用来进行底层的位操作,利用整数的每一位来编码额外信息

下面是objc_object的源码,里面包含了关于isa指针的一些操作:

struct objc_object {
private:
    isa_t isa;
public:
    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    void initIsa(Class cls /*indexed=false*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
private:
    void initIsa(Class newCls, bool indexed, bool hasCxxDtor);

首先来看initIsa函数

inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor)
{
    if (!indexed) {
        isa.cls = cls;
    } else {
        isa.bits = ISA_MAGIC_VALUE;
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

在initInstanceIsa函数中调用更通用的initIsa方法,传入true表示使用索引,并传递hasCxxDtor标志。

initIsa函数有三个参数

  • 第一个参数是所属的类
  • 第二个参数是是否启动索引机制
  • 第三个参数是是否有析构函数

在initIsa函数中如果没有启用索引机制,则直接设置isa的cls指针为给定的类。

启用索引的情况下

  1. 首先设置isa的magic值,这是一个特殊标记,用于识别或校验isa的格式。
  2. 接着设置has_cxx_dtor标志,表明该对象具有C++析构函数,需要在对象销毁时调用。
  3. 最后对类指针(cls)进行右移3位操作,然后赋值给shiftcls字段,这通常是为了在isa中存储类索引。
    右移操作意味着类地址的一部分被用来作为索引,这是一种空间换时间的优化策略。

下面是isa的内部细节:


#if __arm64__

// 定义掩码,用于提取或设置isa中的特定位段。
#define ISA_MASK        0x0000000ffffffff8ULL   // 最低3位和最高位之外的位全为1
#define ISA_MAGIC_MASK  0x000003f000000001ULL   // 用于识别isa的magic的位段
#define ISA_MAGIC_VALUE 0x000001a000000001ULL   // isa的magic值,用于标记非指针isa

// isa_t结构体内部的位域定义
struct {
    uintptr_t indexed           : 1;    // 标记是否使用了类的索引
    uintptr_t has_assoc         : 1;    // 对象是否有关联引用
    uintptr_t has_cxx_dtor      : 1;    // 对象是否有C++析构函数
    uintptr_t shiftcls          : 33;   // 类指针偏移或索引位,用于存储类地址的部分信息
    uintptr_t magic             : 6;    // magic位,用于快速区分isa的类型或状态
    uintptr_t weakly_referenced : 1;    // 对象是否被弱引用
    uintptr_t deallocating      : 1;    // 对象是否正在释放过程中
    uintptr_t has_sidetable_rc  : 1;    // 引用计数是否需要从侧表中获取
    uintptr_t extra_rc          : 19;   // 额外的引用计数位,直接存储小的引用计数值
    // 定义RC_ONE和RC_HALF作为引用计数位操作的常量
#   define RC_ONE   (1ULL<<45)          // 用于增加引用计数1的掩码
#   define RC_HALF  (1ULL<<18)          // 用于减半引用计数的掩码
};

#elif __x86_64__

// 重新定义掩码以适应X86_64架构的地址空间
#define ISA_MASK        0x00007ffffffffff8ULL
#define ISA_MAGIC_MASK  0x001f800000000001ULL
#define ISA_MAGIC_VALUE 0x001d800000000001ULL

// 位域结构体的定义调整以适应X86_64架构
struct {
    uintptr_t indexed           : 1;
    uintptr_t has_assoc         : 1;
    uintptr_t has_cxx_dtor      : 1;
    uintptr_t shiftcls          : 44;  // 类偏移或索引位,调整以适应更大的地址空间
    uintptr_t magic             : 6;
    uintptr_t weakly_referenced : 1;
    uintptr_t deallocating      : 1;
    uintptr_t has_sidetable_rc  : 1;
    uintptr_t extra_rc          : 8;   // 较少的额外引用计数位,因为地址空间分配不同
    // 调整RC_ONE和RC_HALF的定义以匹配新的位布局
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
};

第一位index,代表是否开启isa指针优化。index = 1,代表开启isa指针优化。在WWDC2013苹果介绍了 Tagged Pointer用来进行指针优化。 Tagged Pointer的存在主要是为了节省内存。

我们知道,对象的指针大小一般是与机器字长有关,在32位系统中,一个指针的大小是32位(4字节),而在64位系统中,一个指针的大小将是64位(8字节)。假设我们要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。如果没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。如下图所示:

在这里插入图片描述
苹果提出了Tagged Pointer对象。由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位),对于绝大多数情况都是可以处理的。所以,引入了Tagged Pointer对象之后,64位CPU下NSNumber的内存图变成了以下这样:
在这里插入图片描述

五、cache_t的具体实现

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的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。
cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。

六、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;

    // 类的完全限定名,经过C++名称修饰后的字符串表示。
    char *demangledName;
}

// 该结构体代表了类的只读数据部分,包含了在编译时期确定且不可更改的类信息。
struct class_ro_t {
    // 类的标志位集合,描述类的基本属性,如是否是元类等。
    uint32_t flags;

    // 实例变量起始偏移量,用于计算实例变量在对象内存布局中的位置。
    uint32_t instanceStart;

    // 实例的总大小,包括实例变量和可能的内嵌对象等。
    uint32_t instanceSize;

    // LP64环境下保留的字段,未使用。
#ifdef __LP64__
    uint32_t reserved;
#endif

    // 实例变量布局描述,指示了实例变量的内存排列和对齐规则。
    const uint8_t * ivarLayout;

    // 类的名称,C字符串形式。
    const char * name;

    // 基础方法列表,包含类定义时声明的所有实例方法。
    method_list_t * baseMethodList;

    // 类遵循的基础协议列表。
    protocol_list_t * baseProtocols;

    // 实例变量列表,描述类声明的所有实例变量。
    const ivar_list_t * ivars;

    // 弱实例变量的布局信息,用于ARC下管理弱引用。
    const uint8_t * weakIvarLayout;

    // 基础属性列表,类定义时声明的属性。
    property_list_t *baseProperties;

    // 提供一个便捷方法返回基础方法列表,增加了代码的可读性。
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
}
  • class_data_bits_t 存储了快速访问的类标志位,用于控制类的某些行为或状态。
  • class_rw_t 代表了类的动态部分,可以随着程序运行而改变,包括方法、属性、遵循的协议等,是类扩展和修改的场所。
  • class_ro_t 则是类的静态定义,包含了编译时期确定的类信息,如类名、实例变量布局、基础方法列表等,这部分在运行时是不可修改的。

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

class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags

它为我们提供了便捷方法用于返回其中的 class_rw_t *指针:

class_rw_t *data() {
    return bits.data();
}

在编译期类的结构中的 class_data_bits_t *data指向的是一个 class_ro_t *指针:
在这里插入图片描述
在运行时调用 realizeClass方法,会做以下3件事情:

从 class_data_bits_t调用 data方法,将结果从 class_rw_t强制转换为 class_ro_t指针
初始化一个 class_rw_t结构体
设置结构体 ro的值以及 flag
最后调用methodizeClass方法,把类里面的属性,协议,方法都加载进来。

七、Runtime消息转发

在OC中进行消息传递时如果直到根类还没有找到方法的具体实现就会进行消息转发流程。
消息转发分为三个部分:

  • 动态方法解析
  • 备用接收者
  • 完整消息转发
    在这里插入图片描述

动态方法解析

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

需要注意的是此处跟函数的返回值没有关系,只跟是否添加函数有关

下面是一段示例代码:

#import "ViewController.h"
#import "objc/runtime.h"
@interface ViewController ()

@end

@implementation ViewController

//OBJC_EXPORT BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //执行foo函数
        [self performSelector:@selector(koo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(koo)) {//如果是执行koo函数,就动态解析,指定新的IMP
        class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
    
}

void fooMethod(id obj, SEL _cmd) {
    NSLog(@"Doing foo");//新的foo函数
}



@end

在这里插入图片描述
可以看到虽然没有实现koo:这个函数,但是我们通过class_addMethod动态添加fooMethod函数,并执行fooMethod这个函数的IMP。从打印结果看,成功实现了。
如果没有添加新方法 ,运行时就会移到下一步:forwardingTargetForSelector

备用接收者

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

示例代码如下:

#import <Foundation/Foundation.h>
#import "objc/runtime.h"
NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@end

NS_ASSUME_NONNULL_END
#import "Person.h"

@implementation Person
- (void)foo {
    NSLog(@"Doing foo");
}
@end
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;//返回NO,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return [Person new];//返回Person对象,让Person对象接收这个消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

@end

在这里插入图片描述
可以看到我们通过forwardingTargetForSelector把当前ViewController的方法转发给了Person去执行了。

完整消息转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil ,Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation 对象并发送 -forwardInvocation:消息给目标对象。

示例代码如下:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@end

NS_ASSUME_NONNULL_END
#import "Person.h"

@implementation Person
- (void)foo {
    NSLog(@"doing foo");
}
@end

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

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;//返回YES,进入下一步转发
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;//返回nil,进入下一步转发
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    Person *p = [Person new];
    if([p respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:p];
    }
    else {
        [self doesNotRecognizeSelector:sel];
    }

}


@end

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

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

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

相关文章

人形机器人:工业领域的得力助手

人行机器人近2年显示出强劲的增长势头&#xff0c;根据最新数据预测&#xff0c;到2026年&#xff0c;中国人形机器人产业规模将突破200亿元&#xff0c;这一数字预示着人形机器人在未来社会中将扮演更加重要的角色。 在工业领域&#xff0c;由于工业环境复杂多变&#xff0c;对…

音视频直播(一)

协议基础篇 直播协议基础推流与拉流推流拉流 直播传输协议RTMP传输协议 && HTTP-FLV协议为什么RTMP做推流&#xff0c;反而很少做拉流&#xff1f;HTTP-FLV协议 RTSP协议HLS协议SRT协议 WebRTC协议应用于直播 直播协议基础 从网络上搜寻到的有关推流与拉流的示意图 从…

STM32F103VE和STM32F407VE的引脚布局

STM32F103VE vs STM32F407VE 引脚对比表 引脚 STM32F103VE STM32F407VE 备注 1 VSS VSS 地 2 VDD VDD 电源 3 VSSA VSSA 模拟地 4 VDDA VDDA 模拟电源 5 OSC_IN OSC_IN 外部时钟输入 6 OSC_OUT OSC_OUT 外部时钟输出 7 NRST NRST 复位 8 PC13 (GPIO) PC13 (GPIO) GPIO 9 PC14 (…

【MySQL】sql语句之表操作(上)

序言 在上一篇的数据库操作的内容中&#xff0c;学习了两种属性和常用的七种操作&#xff0c;学习是循序渐进的&#xff0c;库的操作学完了&#xff0c;就要开始学习表的操作了&#xff0c;而表可与数据强相关&#xff0c;比如DDL&#xff0c;即数据定义语言&#xff0c;DML&am…

JVMの静、动态绑定异常捕获JIT即时编译

在说明静态绑定和动态绑定之前&#xff0c;我们首先要了解在字节码指令的层面&#xff0c;JVM是如何调用方法的&#xff1a; 例如我有以下的代码&#xff0c;很简单就是在main方法中调用了另一个静态方法&#xff1a; public class MethodTest {public static void main(Strin…

小程序 UI 风格美不胜收

小程序 UI 风格美不胜收 小程序 UI 风格美不胜收

论文阅读《SELECTIVE DOMAIN-INVARIANT FEATURE FOR GENERALIZABLE DEEPFAKEDETECTION》

作者&#xff1a;Yingxin Lai、 Guoqing Yang1、Yifan He2、Zhiming Luo、Shaozi Li 期刊&#xff1a;ICASSP-2024 目的&#xff1a;解决泛化性的问题&#xff0c;提出了3个模块 论文整体的架构图&#xff1a;&#xff08;挑选域特征不变&#xff0c;减少对图像内容或者风格…

完全平方数

完全平方数 完全平方数动态规划 完全平方数 给你一个整数 n &#xff0c;返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数&#xff0c;其值等于另一个整数的平方&#xff1b;换句话说&#xff0c;其值等于一个整数自乘的积。例如&#xff0c;1、4、9 和 16 都是…

289M→259M得物包体积治理实践

一、前言 iOS应用的包体积大小是衡量得物性能的重要指标&#xff0c;过大包体积会降低用户对应用的下载意愿&#xff0c;还会增加用户的下载等待时间以及用户手机的存储空间&#xff0c;本文重点介绍在包体积治理中的新思路以及原理与实践。 二、原理介绍 Macho产物测试 我…

想要修改word文档怎么移除编辑权限?学会这两个方法,轻松搞定

日常办公和学习中&#xff0c;Word文档是我们不可或缺的工具。然而&#xff0c;有时我们可能会遇到一些设置了编辑权限的文档&#xff0c;这可能是由于文档的创建者希望控制文档的修改和传播&#xff0c;或者是因为文档在某些共享或协作环境中被设置为只读模式。在这种情况下&a…

网工内推 | 网络运维工程师,H3CIE认证优先,13薪,享股票期权

01 畅读 &#x1f537;招聘岗位&#xff1a;高级网络运维工程师 &#x1f537;职责描述&#xff1a; 1.负责线上业务网络技术运维工作&#xff0c;保障并优化线上网络质量&#xff1b; 2.规划并构建公司线上业务网络架构&#xff1b; 3.规划线上业务网络质量评估与监控体系&…

mysql中 redo日志(上)

大家好。我们知道InnoDB 存储引擎是以页为单位来管理存储空间的&#xff0c;我们进行的增删改查操作其实本质上都是在访问页面。而在真正访问页面之前&#xff0c;需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。那么我们思考一个问题&#xff1a;如果我们只在内…

vue2中使用tinymce

vue2中使用tinymce的记录 本篇文章主要实现的功能&#xff1a; &#xff08;1&#xff09;【查看】时禁用编辑 &#xff08;2&#xff09;【编辑】时某些内容是不可编辑的 &#xff08;3&#xff09;【内容】前端拼接编辑器模板 &#xff08;4&#xff09;【内容】编辑器模板中…

【漏洞复现】锐捷校园网自助服务系统 login_judge.jsf 任意文件读取漏洞(XVE-2024-2116)

0x01 产品简介 锐捷校园网自助服务系统是锐捷网络推出的一款面向学校和校园网络管理的解决方案。该系统旨在提供便捷的网络自助服务&#xff0c;使学生、教职员工和网络管理员能够更好地管理和利用校园网络资源。 0x02 漏洞概述 校园网自助服务系统/selfservice/selfservice…

Java核心: 为图片生成水印

今天干了一件特别不务正业的事&#xff0c;做了一个小程序用来给图片添加水印。事情的起因是需要将自己的身份证照片分享给别人&#xff0c;手边并没有一个趁手的工具来生成图片水印。很多APP提供了水印的功能&#xff0c;但会把我的图片上传到他们的服务器&#xff0c;身份证太…

台式机安装Windows 11和Ubuntu 22双系统引导问题

一、基本情况 1.1、硬件情况 电脑有2个NVMe固态硬盘&#xff0c;1个SATA固态硬盘&#xff0c;1个机械硬盘。其中一个NVMe固态硬盘是Windows系统盘&#xff0c;另一个NVMe固态为Windows软件和文件盘&#xff0c;SATA固态硬盘为Ubuntu专用&#xff0c;机械硬盘为数据备份盘。 …

Find My电动螺丝刀|苹果Find My技术与螺丝刀结合,智能防丢,全球定位

电动螺丝刀&#xff0c;别名电批、电动起子&#xff0c;是用于拧紧和旋松螺钉用的电动工具。它不仅提高了工作效率&#xff0c;还大大减轻了工作者的体力负担。在装配线等生产环境中&#xff0c;电动螺丝刀已经成为了不可或缺的工具。电动螺丝刀的批头还具备接地防静电功能&…

Leetcode:四数之和

题目链接&#xff1a;18. 四数之和 - 力扣&#xff08;LeetCode&#xff09; 普通版本&#xff08;排序 双指针&#xff09; 主旨&#xff1a;类似于三数之和的解法&#xff0c;但需要多加一些限制&#xff0c;同时为了防止多个数组元素的相加之和出现整型溢出问题还要将整型…

IDEA 2022

介绍 【尚硅谷IDEA安装idea实战教程&#xff08;百万播放&#xff0c;新版来袭&#xff09;】 jetbrains 中文官网 IDEA 官网 IDEA 从 IDEA 2022.1 版本开始支持 JDK 17&#xff0c;也就是说如果想要使用 JDK 17&#xff0c;那么就要下载 IDEA 2022.1 或之后的版本。 公司…

《TCP/IP网络编程》(第十三章)多种I/O函数(2)

使用readv和writev函数可以提高数据通信的效率&#xff0c;它们的功能可以概括为**“对数据进行整合传输及发送”**。 即使用writev函数可以将分散在多个缓冲中的数据一并发送&#xff0c;使用readv函数可以由多个缓冲分别接受&#xff0c;所以适当使用他们可以减少I/O函数的调…