当前位置: 首页 > news >正文

iOS--runtime

什么是Runtime

  • runtime是由C和C++、汇编实现的一套API,为OC语言加入了面向对象、运行时的功能
  • 运行时(runtime)将数据类型的确定由编译时推迟到了运行时
  • 平时编写的OC代码,在程序运行过程中,最终会转换成runtime的C语言代码——runtime是Objective-C 的幕后⼯作者

如类结构中的ro和rw属性

  • ro(read-only)在编译时已经确定
  • rw(read-write)在运行时才确定,因此可以使用runtime进行修改

方法的本质是什么

方法的本质是发送消息objc_msgSend,即寻找IMP的过程
发送消息会有以下⼏个流程:

  • 快速查找流程——通过汇编objc_msgSend查找缓存cache_t是否有imp实现
  • 慢速查找流程——通过C++中lookUpImpOrForward递归查找当前类和父类的rwmethodlist的方法
  • 动态方法解析——通过调用resolveInstanceMethodresolveClassMethod来动态方法决议——实现消息动态处理
  • 快速转发流程——通过CoreFoundation来触发消息转发流程,forwardingTargetForSelector实现快速转发,由其他对象来实现处理方法
  • 慢速转发流程——先调用methodSignatureForSelector获取到方法的签名,生成对应的invocation;再通过forwardInvocation来进行处理
  • 以上流程均无法挽救就崩溃并报错

SEL和IMP的关系

遇到这种问题先要解释两者分别是什么?再解释两者的关系

SEL是方法编号,也是方法名,在dyld加载镜像到内存时,通过_read_image方法加载到内存的表中了
IMP是函数实现指针,找IMP就是找函数实现的过程
SELIMP的关系就可以解释为:

  • SEL就相当于书本的⽬录标题
  • IMP就是书本的⻚码
  • 函数就是具体页码对应的内容

比如我们想在《程序员的自我修养——链接、装载与库》一书中找到“动态链接”(SEL),肯定会翻到179页(IMP),179页会开始讲述具体内容(函数实现)

请添加图片描述

能否向运⾏时创建的类中添加实例变量

具体情况具体分析:

  • 编译好的类不能添加实例变量
  • 运行时创建的类可以添加实例变量,但若已注册到内存中就不行了

原因:

  • 编译好的实例变量存储的位置在ro,而ro是在编译时就已经确定了的
  • ⼀旦编译完成,内存结构就完全确定就⽆法修改
  • 只能修改rw中的方法或者可以通过关联对象的方式来添加属性

利用runtime-API创建对象

这题对runtime-API要求程度比较高

API介绍

动态创建类

/***创建类**superClass: 父类,传Nil会创建一个新的根类*name: 类名*extraBytes: 额外的内存空间,一般传0*return:返回新类,创建失败返回Nil,如果类名已经存在,则创建失败*/
Class FXPerson = objc_allocateClassPair([NSObject class], "LGPerson", 0);

添加成员变量

/**
*添加成员变量
*这个函数只能在objc_allocateClassPair和objc_registerClassPair之间调用。不支持向现有类添加一个实例变量
*这个类不能是元类,不支持在元类中添加一个实例变量
*实例变量的最小对齐为1 << align,实例变量的最小对齐依赖于ivar的类型和机器架构。对于任何指针类型的变量,请通过log2(sizeof(pointer_type))
*
*cls 往哪个类添加
*name 添加的名字
*size 大小
*alignment 对齐处理方式
*types 签名
*/
class_addIvar(FXPerson, "fxName", sizeof(NSString *), log2(sizeof(NSString *)), "@");

注册到内存

/***往内存注册类** cls 要注册的类*/objc_registerClassPair(FXPerson);

添加属性变量

/**
*往类里面添加属性
*
*cls 要添加属性的类
*name 属性名字
*attributes 属性的属性数组。
*attriCount 属性中属性的数量。
*/
class_addProperty(targetClass, propertyName, attrs, 4);

添加方法

/***往类里面添加方法**cls 要添加方法的类*sel 方法编号*imp 函数实现指针*types 签名*/
class_addMethod(FXPerson, @selector(setHobby), (IMP)fxSetter, "v@:@");

整体使用

// hobby的setter-IMP
void fxSetter(NSString *value) {printf("%s/n",__func__);
}// hobby的getter-IMP
NSString *fxHobby() {return @"iOS";
}// 添加属性变量的封装方法
void fx_class_addProperty(Class targetClass, const char *propertyName) {objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //typeobjc_property_attribute_t ownership0 = { "C", "" }; // C = copyobjc_property_attribute_t ownership = { "N", "" }; //N = nonatomicobjc_property_attribute_t backingivar  = { "V", [NSString stringWithFormat:@"_%@",[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]].UTF8String };  //variable nameobjc_property_attribute_t attrs[] = {type, ownership0, ownership, backingivar};class_addProperty(targetClass, propertyName, attrs, 4);
}// 打印属性变量的封装方法
void fx_printerProperty(Class targetClass){unsigned int outCount, i;objc_property_t *properties = class_copyPropertyList(targetClass, &outCount);for (i = 0; i < outCount; i++) {objc_property_t property = properties[i];fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));}
}int main(int argc, const char * argv[]) {@autoreleasepool {// 动态创建类Class FXPerson = objc_allocateClassPair([NSObject class], "FXPerson", 0);// 添加成员变量class_addIvar(FXPerson, "name", sizeof(NSString *), log2(sizeof(NSString *)), "@");// 注册到内存objc_registerClassPair(FXPerson);// 添加属性变量fx_class_addProperty(FXPerson, "hobby");fx_printerProperty(FXPerson);// 添加方法(为属性方法添加setter、getter方法)class_addMethod(FXPerson, @selector(setHobby:), (IMP)fxSetter, "v@:@");class_addMethod(FXPerson, @selector(hobby), (IMP)fxHobby, "@@:");// 开始使用id person = [FXPerson alloc];[person setValue:@"Felix" forKey:@"name"];NSLog(@"FXPerson的名字是:%@ 爱好是:%@", [person valueForKey:@"name"], [person valueForKey:@"hobby"]);}return 0;
}

注意事项

  • 记得导入<objc/runtime.h>
  • 添加成员变量class_addIvar必须在objc_registerClassPair前,因为注册到内存时ro已经确定了,不能再往ivars添加(同第四个面试题)
  • 添加属性变量class_addProperty可以在注册内存前后,因为是往rw中添加的
  • class_addProperty中“属性的属性”——nonatomic/copy是根据属性的类型变化而变化的
  • class_addProperty不会自动生成settergetter方法,因此直接调用KVC会崩溃

不只可以通过KVC打印来检验,也可以下断点查看ro、rw的结构来检验

关联对象分析

实则是为了解决分类创建属性的问题

分类直接添加属性的后果

  • 编译会出现警告:没有setter方法和getter方法
  • 运行会报错:-[FXPerson setName:]: unrecognized selector sent to instance 0x100f61180’
    请添加图片描述

为什么不能直接添加属性

Category在runtime中是用一个结构体表示的:

struct category_t {const char *name;classref_t cls;struct method_list_t *instanceMethods;struct method_list_t *classMethods;struct protocol_list_t *protocols;struct property_list_t *instanceProperties;// Fields below this point are not always present on disk.struct property_list_t *_classProperties;...
};

里面虽然可以添加属性变量,但是这些properties并不会自动生成Ivar,也就是不会有 @synthesize的作用,dyld加载期间,这些分类会被加载并patch到相应的类中。这是一个动态过程,Ivar不能动态添加

解决方案

手动实现setter、getter方法,关联对象

- (void)setName:(NSString *)name {/**参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self。参数二:void * == id key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。参数三:id value : 关联的值,也就是set方法传入的值给属性去保存。参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。*/objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (NSString *)name {/**参数一:id object : 获取哪个对象里面的关联的属性。参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。*/return objc_getAssociatedObject(self, @"name");
}

关联对象原理

setter方法——objc_setAssociatedObject分析

苹果设计接口时往往会加个中间层——即使底层实现逻辑发生变化也不会影响到对外接口

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {_object_set_associative_reference(object, (void *)key, value, policy);
}

跟进去看看_object_set_associative_reference实现

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {// This code used to work when nil was passed for object and key. Some code// probably relies on that to not crash. Check and handle it explicitly.// rdar://problem/44094390if (!object && !value) return;assert(object);if (object->getIsa()->forbidsAssociatedObjects())_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));// retain the new value (if any) outside the lock.// 在锁之外保留新值(如果有)。ObjcAssociation old_association(0, nil);// acquireValue会对retain和copy进行操作,id new_value = value ? acquireValue(value, policy) : nil;{// 关联对象的管理类AssociationsManager manager;// 获取关联的 HashMap -> 存储当前关联对象AssociationsHashMap &associations(manager.associations());// 对当前的对象的地址做按位去反操作 - 就是 HashMap 的key (哈希函数)disguised_ptr_t disguised_object = DISGUISE(object);if (new_value) {// break any existing association.// 获取 AssociationsHashMap 的迭代器 - (对象的) 进行遍历AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// secondary table existsObjectAssociationMap *refs = i->second;// 根据key去获取关联属性的迭代器ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;// 替换设置新值j->second = ObjcAssociation(policy, new_value);} else {// 到最后了 - 直接设置新值(*refs)[key] = ObjcAssociation(policy, new_value);}} else {// create the new association (first time).// 如果AssociationsHashMap从没有对象的关联信息表,// 那么就创建一个map并通过传入的key把value存进去ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}} else {// setting the association to nil breaks the association.// 如果传入的value是nil,并且之前使用相同的key存储过关联对象,// 那么就把这个关联的value移除(这也是为什么传入nil对象能够把对象的关联value移除)AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;refs->erase(j);}}}}// release the old value (outside of the lock).// 最后把之前使用传入的这个key存储的关联的value释放(OBJC_ASSOCIATION_SETTER_RETAIN策略存储的)if (old_association.hasValue()) ReleaseValue()(old_association);
}
  • ObjcAssociation old_association(0, nil)处理传进来的值得到new_value
  • 获取到管理所有关联对象的hashmap总表的管理者AssociationsManager,然后拿到hashmap总表AssociationsHashMap
  • DISGUISE(object)对关联对象的地址进行取反操作得到哈希表对应的下标index
  • 如果new_value为空(即对属性赋值为nil)就直接找到相应的表进行删除
  • 如果new_value不为空,就拿到总表的迭代器通过拿到的下标index进行遍历查找;如果找到管理对象的关联属性哈希map表,然后再通过key去遍历取值
    • 如果取到了,就先把新值设置到key上,再将旧值释放掉
    • 如果没取到,就直接将新值设置在key上

还是不明白就LLDB断点调试呗请添加图片描述

getter方法——objc_getAssociatedObject分析

id objc_getAssociatedObject(id object, const void *key) {return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {id value = nil;uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;{// 关联对象的管理类AssociationsManager manager;AssociationsHashMap &associations(manager.associations());// 生成伪装地址。处理参数 object 地址disguised_ptr_t disguised_object = DISGUISE(object);// 所有对象的额迭代器AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;// 内部对象的迭代器ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {// 找到 - 把值和策略读取出来ObjcAssociation &entry = j->second;value = entry.value();policy = entry.policy();// OBJC_ASSOCIATION_GETTER_RETAIN - 就会持有一下if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {objc_retain(value);}}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autorelease(value);}return value;
}

objc_getAssociatedObjectobjc_setAssociatedObject的逆过程
在这里插入图片描述

weak置空原理

当面试官问你weak置空原理是什么,你可能只知道weak怎么用却不知道怎么答吧

int main(int argc, const char * argv[]) {@autoreleasepool {FXPerson *person = [[FXPerson alloc] init];id __weak person = object;}return 0;
}

Xcode菜单栏Debug->Debug Workflow->Always show Disassembly打上勾查看汇编——汇编代码会来到libobjc库的objc_initWeak

weak创建过程

objc_initWeak

  • location:表示__weak指针的地址(我们研究的就是__weak指针指向的内容怎么置为nil)
  • newObj:所引用的对象,即例子中的person
id
objc_initWeak(id *location, id newObj)
{if (!newObj) {*location = nil;return nil;}return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object*)newObj);
}

storeWeak

  • HaveOld:weak指针之前是否已经指向了一个弱引用
  • HaveNew:weak指针是否需要指向一个新引用
  • CrashIfDeallocating:如果被弱引用的对象正在析构,此时再弱引用该对象,是否应该crash

storeWeak最主要的两个逻辑点(源码太长,这里不贴了)
请添加图片描述

由于是第一次调用,所以走haveNew分支——获取到的是新的散列表SideTable,主要执行了weak_register_no_lock方法来进行插入

weak_register_no_lock

  • 主要进行了 isTaggedPointerdeallocating条件判断
  • 将被弱引用对象所在的weak_table中的weak_entry_t哈希数组中取出对应的weak_entry_t
  • 如果weak_entry_t不存在,则会新建一个并插入
  • 如果存在就将指向被弱引用对象地址的指针referrer通过函数append_referrer插入到对应的weak_entry_t引用数组
    请添加图片描述
    append_referrer
    找到弱引用对象的对应的weak_entry哈希数组中插入请添加图片描述

weak创建流程

请添加图片描述

weak销毁过程

由于弱引用在析构dealloc时自动置空,所以查看dealloc的底层实现并LLVM调试

  • _objc_rootDealloc->rootDealloc
  • rootDealloc->object_dispose
  • object_dispose->objc_destructInstance
  • objc_destructInstance->clearDeallocating
  • clearDeallocating->sidetable_clearDeallocating
  • sidetable_clearDeallocating3->table.refcnts.erase(it)

weak销毁流程

在这里插入图片描述

Method Swizzing坑点

黑魔法应用

在日常开发中,再好的程序员都会犯错,比如数组越界

NSArray *array = @[@"F", @"e", @"l", @"i", @"x"];
NSLog(@"%@", array[5]);
NSLog(@"%@", [array objectAtIndex:5]);

因此为了避免数组越界这种问题,大神们开始玩起了黑魔法——method swizzing

  • 新建NSArray分类
  • 导入runtime头文件——<objc/runtime.h>
  • 写下新的方法
  • +load利用黑魔法交换方法
#import "NSArray+FX.h"
#import <objc/runtime.h>@implementation NSArray (FX)+ (void)load {// 交换objectAtIndex方法Method oriMethod1 = class_getInstanceMethod(self, @selector(objectAtIndex:));Method swiMethod1 = class_getInstanceMethod(self, @selector(fx_objectAtIndex:));method_exchangeImplementations(oriMethod1, swiMethod1);// 交换取下标方法Method oriMethod2 = class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:));Method swiMethod2 = class_getInstanceMethod(self, @selector(fx_objectAtIndexedSubscript:));method_exchangeImplementations(oriMethod2, swiMethod2);
}- (void)fx_objectAtIndex:(NSInteger)index {if (index > self.count - 1) {NSLog(@"objectAtIndex————————数组越界");return;}return [self fx_objectAtIndex:index];
}- (void)fx_objectAtIndexedSubscript:(NSInteger)index {if (index > self.count - 1) {NSLog(@"取下标————————数组越界");return;}return [self fx_objectAtIndexedSubscript:index];
}@end

然而程序还是无情的崩了…

在这里插入图片描述
其实在iOS中NSNumber、NSArray、NSDictionary等这些类都是类簇(Class Clusters),一个NSArray的实现可能由多个类组成。所以如果想对NSArray进行方法交换,必须获取到其真身进行方法交换,直接对NSArray进行操作是无效的
以下是NSArrayNSDictionary本类的类名
请添加图片描述
这样就好办了,可以使用runtime取出本类请添加图片描述

坑点一

黑魔法最好写成单例,避免多次交换

比如说添加了[NSArray load]代码,方法实现又交换回去了导致了崩溃请添加图片描述
+load方法改写成单例(虽然不常见,但也要避免)

+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 交换objectAtIndex方法Method oriMethod1 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));Method swiMethod1 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(fx_objectAtIndex:));method_exchangeImplementations(oriMethod1, swiMethod1);// 交换取下标方法Method oriMethod2 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndexedSubscript:));Method swiMethod2 = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(fx_objectAtIndexedSubscript:));method_exchangeImplementations(oriMethod2, swiMethod2);});
}

坑点二

①子类交换父类实现的方法

  • 父类FXPerson类中有-doInstance方法,子类FXSon类没有重写
  • FXSon类新建分类做了方法交换,新方法中调用旧方法
  • FXPerson类FXSon类调用-doInstance请添加图片描述
    子类打印出结果,而父类调用却崩溃了,为什么会这样呢?
    因为FXSon类交换方法时取得doInstance先在本类搜索方法,再往父类里查找,在FXFather中找到了方法实现就把它跟新方法进行交换了。可是新方法是在FXSon分类中的,FXFather找不到imp就unrecognized selector sent to instance 0x600002334250
    所以这种情况下应该只交换子类的方法,不动父类的方法
+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Method oriMethod = class_getInstanceMethod(self, @selector(doInstance));Method swiMethod = class_getInstanceMethod(self, @selector(fx_doInstance));BOOL didAddMethod = class_addMethod(self, @selector(doInstance), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));if (didAddMethod) {class_replaceMethod(self, @selector(fx_doInstance), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));} else {method_exchangeImplementations(oriMethod, swiMethod);}});
}
  • 通过class_addMethod给FXSon类添加方法(class_addMethod不会取代本类中已存在的实现,只会覆盖本类中继承父类的方法实现)
    • 取得新方法swiMethod的实现和方法类型
    • 往方法名@selector(fx_doInstance)添加方法
    • class_addMethod 把新方法实现放到旧方法名中,此刻调用doInstance就是调用fx_doInstance,但是调用fx_doInstance会崩溃
  • 根据didAddMethod判断是否添加成功
    • 添加成功说明之前本类没有实现——class_replaceMethod替换方法
    • 添加失败说明之前本类已有实现——method_exchangeImplementations交换方法
    • class_replaceMethoddoInstance方法实现替换掉fx_doInstance中的方法实现
      请添加图片描述
      在这里插入图片描述
      FXPerson类只写了方法声明,没有方法实现,却做了方法交换——会造成死循环请添加图片描述
  • doInstance方法中添加了新的方法实现
  • fx_doInstance方法中想用旧的方法实现替代之前的方法实现,可是找不到doInstance实现,导致class_replaceMethod无效->在fx_doInstance中调用fx_doInstance就会死循环
    请添加图片描述
+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Method oriMethod = class_getInstanceMethod(self, @selector(doInstance));Method swiMethod = class_getInstanceMethod(self, @selector(fx_doInstance));if (!oriMethod) {class_addMethod(self, @selector(doInstance), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd) {NSLog(@"方法未实现");}));}BOOL didAddMethod = class_addMethod(self, @selector(doInstance), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));if (didAddMethod) {class_replaceMethod(self, @selector(fx_doInstance), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));} else {method_exchangeImplementations(oriMethod, swiMethod);}});
}

在这里插入图片描述

注意事项

使用Method Swizzling有以下注意事项:

  • 尽可能在+load方法中交换方法
  • 最好使用单例保证只交换一次
  • 自定义方法名不能产生冲突
  • 对于系统方法要调用原始实现,避免对系统产生影响
  • 做好注释(因为方法交换比较绕)
  • 迫不得已情况下才去使用方法交换

这是一份做好封装的Method Swizzling交换方法

+ (void)FXMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL {if (!cls) NSLog(@"传入的交换类不能为空");Method oriMethod = class_getInstanceMethod(cls, oriSEL);Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);if (!oriMethod) {class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd) {NSLog(@"方法未实现");}));}BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));if (didAddMethod) {class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));} else {method_exchangeImplementations(oriMethod, swiMethod);}
}

某个方法进行多次交换,最终的调用顺序是怎样的?请添加图片描述

补充面试题二

问:如果发现方法交换之后不生效,应该怎么排查?
答:解决的方案有多种,主要从两个方向进行思考

  • 方法交换的class有没有问题
  • 方法交换的method有没有问题

相关文章:

iOS--runtime

什么是Runtime runtime是由C和C、汇编实现的一套API&#xff0c;为OC语言加入了面向对象、运行时的功能运行时&#xff08;runtime&#xff09;将数据类型的确定由编译时推迟到了运行时平时编写的OC代码&#xff0c;在程序运行过程中&#xff0c;最终会转换成runtime的C语言代…...

06. 管理Docker容器数据

目录 1、前言 2、Docker实现数据管理的方式 2.1、数据卷&#xff08;Data Volumes&#xff09; 2.2、数据卷容器&#xff08;Data Volume Containers&#xff09; 3、简单示例 3.1、数据卷示例 3.2、数据卷容器示例 1、前言 在生产环境中使用 Docker&#xff0c;一方面…...

计算机视觉常用数据集介绍

1 MINIST MINIST 数据集应该算是CV里面最早流行的数据了&#xff0c;相当于CV领域的Hello World。该数据包含70000张手写数字图像&#xff0c;其中60000张用于train&#xff0c; 10000张用于test&#xff0c; 并且都有相应的label。图像的尺寸比较小&#xff0c; 为28x28。 数…...

Arcgis画等高线

目录 数据准备绘制等高线3D等高线今天我们将学习如何在ArcGIS中绘制等高线地图。等高线地图是地理信息系统中常见的数据表现形式,它通过等高线将地形起伏展现得一目了然,不仅美观,还能提供重要的地形信息。 数据准备 在开始之前,确保已经准备好了高程数据,它通常以栅格数…...

abp vnext4.3版本托管到iis同时支持http和https协议

在项目上本来一直使用的是http协议,后来因为安全和一些其他原因需要上https协议&#xff0c;如果发布项目之后想同时兼容http和https协议需要更改一下配置信息&#xff0c;下面一起看一下&#xff1a; 1.安装服务器证书 首先你需要先申请一张服务器证书&#xff0c;申请后将证…...

2023年全网电视盒子无线ADB修改桌面(无需ROOT)

前言 1.主要是为了解决电视盒子等安卓设备无法卸载或者停用原始桌面导致无法选用第三方桌面。 解决方案 1.首先自行下载我提供的网盘APK 2.点击打开中国移动云盘 3.不管你是通过U盘还是局域网共享能够让你的电视安装第三方应用&#xff0c;毕竟每个品牌的安装方法不尽相同…...

什么是Java中的Maven?

Java中的Maven&#xff0c;可以简单理解为“一个神奇的工具”&#xff0c;它可以自动帮你管理Java项目的依赖关系&#xff0c;让你不再为手动下载、配置各种库而烦恼。想象一下&#xff0c;你正在写一个Java项目&#xff0c;突然发现需要引入一个名为"第三方库"的模块…...

【C++】总结7

文章目录 函数指针C中类成员的访问权限和继承权限问题定义和声明的区别C中类的静态成员与普通成员的区别是什么&#xff1f;虚函数为什么不能重载为内联函数&#xff1f;对ifdef endif的理解如何在不使用额外空间的情况下&#xff0c;交换两个数&#xff1f; 函数指针 什么是函…...

【前端知识】React 基础巩固(四十二)——React Hooks的介绍

React 基础巩固(四十二)——React Hooks的介绍 一、为什么需要Hook? Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下使用state以及其他的React特性&#xff08;比如生命周期&#xff09;。 class组件 VS 函数式组件&#xff1a; class的优势…...

adb命令丨adb push命令大全_adb操控手机和指令

【ADB命令】adb push命令总结 adb push命令大全操控手机和指令 运行在 Android 设备上的adb后台进程 执行 adb shell ps | grep adbd &#xff0c;可以找到该后台进程&#xff0c;windows 请使用 findstr 替代 grep [xuxu:~]$ adb shell ps | grep adbd root 23227 1 6672 8…...

【腾讯云 Cloud Studio 实战训练营】沉浸式体验编写一个博客系统

文章目录 前言项目中技术栈新建工作空间登录(注册)Cloud Studio 账号&#xff1a;进入 Cloud Studio 控制台&#xff1a;配置工作空间参数&#xff1a;确认并创建工作空间&#xff1a;项目搭建 配置nuxt 脚手架运行项目报错信息解决错误脚手架运行预览问题 开启博客代码配置lay…...

手机视频聊天分享

在人际互动的手机APP中&#xff0c;增加语音视频聊天功能是一个常见的需求。而现在&#xff0c;更进一步&#xff0c;在某些场景下&#xff0c;我们需要能将自己的手机屏幕分享给他人&#xff0c;或者是观看他人的手机屏幕。那么&#xff0c;这些常见的功能是如何实现的了&…...

神经网络小记-优化器

优化器是深度学习中用于优化神经网络模型的一类算法&#xff0c;其主要作用是根据模型的损失函数来调整模型的参数&#xff0c;使得模型能够更好地拟合训练数据&#xff0c;提高模型的性能和泛化能力。优化器在训练过程中通过不断更新模型的参数&#xff0c;使模型逐步接近最优…...

200+行代码写一个简易的Qt界面贪吃蛇

照例先演示一下&#xff1a; 一个简单的Qt贪吃蛇&#xff0c;所有的图片都是我自己画的&#xff08;得意&#xff09;。 大致的运行逻辑和之前那个200行写一个C小黑窗贪吃蛇差不多&#xff0c;因此在写这个项目的时候&#xff0c;大多情况是在想怎么通过Qt给展现出来。 背景图…...

redis中使用bloomfilter的白名单功能解决缓存穿透问题

一 缓存预热 1.1 缓存预热 将需要的数据提前缓存到缓存redis中&#xff0c;可以在服务启动时候&#xff0c;或者在使用前一天完成数据的同步等操作。保证后续能够正常使用。 1.2 缓存穿透 在redis中&#xff0c;查询redis缓存数据没有内容&#xff0c;接着查询mysql数据库&…...

Spring Boot 2.7.8以后mysql-connector-java与mysql-connector-j

错误信息 如果升级到Spring Boot 2.7.8&#xff0c;可以看到因为找不到mysql-connector-java依赖而出现错误。 配置&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&l…...

03|「如何写好一个 Prompt」

前言 Prompt 文章目录 前言一、通用模板和范式1. 组成2. 要求1&#xff09;文字描述2&#xff09;注意标点符号 一、通用模板和范式 1. 组成 指令&#xff08;角色&#xff09; 生成主体 额外要求 指令&#xff1a;模型具体完成的任务描述。例如&#xff0c;翻译一段文字&…...

关于提示词 Prompt

Prompt原则 原则1 提供清晰明确的指示 注意在提示词中添加正确的分割符号 prompt """ 请给出下面文本的摘要&#xff1a; <你的文本> """可以指定输出格式&#xff0c;如&#xff1a;Json、HTML提示词中可以提供少量实例&#xff0c;…...

【Linux多线程】线程的互斥与同步(附抢票案例代码+讲解)

线程的互斥与同步 &#x1f4ab; 概念引入⭐️临界资源&#xff08;Critical Resource&#xff09;&#xff1a;&#x1f31f;临界区&#xff08;Critical Section&#xff09;&#xff1a;✨互斥&#xff08;Mutex&#xff09;&#xff1a; ⚡️结合代码看互斥☄️ 代码逻辑&a…...

ajax概述

目录 1.什么是ajax 2.ja原生ajax 3.jQuery框架的ajax 4.综合案例 1.什么是ajax Ajax 即"Asynchronous Javascript And XML"&#xff08;异步 JavaScript 和 XML&#xff09;&#xff0c;是指一种创建交互式网页应用的网页开发技术。Ajax 异步 JavaScript 和 XML&…...

AWSLambda之设置时区

目标 希望Lambda运行的时区是东八区。 解决 只需要设置lambda的环境变量TZ为东八区时区即可&#xff0c;即Asia/Shanghai。 参考 使用 Lambda 环境变量...

leetcode 386. 字典序排数 中等

给你一个整数 n &#xff0c;按字典序返回范围 [1, n] 内所有整数。 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法。 示例 1&#xff1a; 输入&#xff1a;n 13 输出&#xff1a;[1,10,11,12,13,2,3,4,5,6,7,8,9]示例 2&#xff1a; 输入&#xff1a;n 2…...

2025年ESWA SCI1区TOP,自适应学习粒子群算法AEPSO+动态周期调节灰色模型,深度解析+性能实测

目录 1.摘要2.粒子群算法PSO原理3.改进策略4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流 1.摘要 能源数据的科学预测对于能源行业决策和国家经济发展具有重要意义&#xff0c;尤其是短期能源预测&#xff0c;其精度直接影响经济运行效率。为了更好地提高预测模型…...

PySide6 GUI 学习笔记——常用类及控件使用方法(多行文本控件QTextEdit)

文章目录 PySide6.QtWidgets.QTextEdit 应用举例概述核心特性常用方法文本内容操作光标和选择操作格式和样式查找功能视图控制状态设置常用信号 代码示例示例说明1. 基本设置2. 文本格式化功能3. 功能按钮4. 信号处理 PySide6.QtWidgets.QTextEdit 应用举例 概述 QTextEdit 是…...

Vue ④-组件通信 || 进阶语法

组件三大部分 template&#xff1a;只有能一个根元素 style&#xff1a;全局样式(默认)&#xff1a;影响所有组件。局部样式&#xff1a;scoped 下样式&#xff0c;只作用于当前组件 script&#xff1a;el 根实例独有&#xff0c;data 是一个函数&#xff0c;其他配置项一致…...

【汇编逆向系列】三、函数调用包含单个参数之float类型-xmm0寄存器,sub,rep,stos,movss,mulss,addss指令

一、汇编代码 single_float_param:0000000000000060: F3 0F 11 44 24 08 movss dword ptr [rsp8],xmm00000000000000066: 57 push rdi0000000000000067: 48 83 EC 10 sub rsp,10h000000000000006B: 48 8B FC mov …...

174页PPT家居制造业集团战略规划和运营管控规划方案

甲方集团需要制定一个清晰的集团价值定位&#xff0c;从“指引多元”、“塑造 能力”以及“强化协同”等方面引领甲方做大做强 集团需要通过管控模式、组织架构及职能、授权界面、关键流程、战略 实施和组织演进路径&#xff0c;平衡风险控制和迅速发展&#xff0c;保证战略落地…...

Java中List的forEach用法详解

在 Java 中&#xff0c;List.forEach() 是 Java 8 引入的一种简洁的遍历集合元素的方法。它基于函数式编程思想&#xff0c;接受一个 Consumer 函数式接口作为参数&#xff0c;用于对集合中的每个元素执行操作。 基本语法 java 复制 下载 list.forEach(consumer); 使用示…...

【Linux】Ubuntu 创建应用图标的方式汇总,deb/appimage/通用方法

Ubuntu 创建应用图标的方式汇总&#xff0c;deb/appimage/通用方法 对于标准的 Ubuntu&#xff08;使用 GNOME 桌面&#xff09;&#xff0c;desktop 后缀的桌面图标文件主要保存在以下三个路径&#xff1a; 当前用户的桌面目录&#xff08;这是最常见的位置&#xff09;。所…...

[蓝桥杯 2024 国 B] 蚂蚁开会

问题描述 二维平面上有 n 只蚂蚁&#xff0c;每只蚂蚁有一条线段作为活动范围&#xff0c;第 i 只蚂蚁的活动范围的两个端点为 (uix,uiy),(vix,viy)。现在蚂蚁们考虑在这些线段的交点处设置会议中心。为了尽可能节省经费&#xff0c;它们决定只在所有交点为整点的地方设置会议…...