iOS-Block

Blocks的学习

Block的分类

Block根据其类型可以分为三类:

  • 全局Block(NSGlobalBlock
  • 栈Block(NSMallocBlock
  • 堆Block(NSStackBlock

而其区分的规则为:

如果没有引用局部变量,或者只引用了静态变量和全局变量,则为全局Block,如果内部有使用局部变量,如果有被强指针引用过,就是堆Block,如果没有则为栈Block。

- (void)func2 {/**— 全局block,没有使用局部变量,或者只使用了静态变量或者只使用了全局变量*/// 没有使用局部变量NSLog(@"block0 - %@",^{});// 使用了静态变量void(^block1)(void) = ^{blockInt = 3;};NSLog(@"block1 - %@",block1);/**- 堆Block 使用局部变量 并且用强指针引用过*/// 即使用局部变量又使用,静态变量NSInteger i = 1;void(^block2)(void) = ^{NSLog(@"block %ld", i);NSLog(@"block static %d", blockInt);};NSLog(@"block2 - %@",block2);// 只使用局部变量void(^block3)(void) = ^{NSLog(@"block %ld", i);};NSLog(@"block3 - %@",block3);// 使用强指针引用过,再使用若指针引用void(^ __weak block4)(void) = block3;NSLog(@"block4 - %@",block4);/**- 栈Block 没有被强引用过的*/// 没有使用强指针引用过void (^__weak block5)(void) = ^{NSLog(@"block %ld", i);};NSLog(@"block5 - %@",block5);
}

分析:
block0 : 没有使用任何变量,属于全局block。
block1 : 只使用了静态变量blockInt,属于全局block。
block2 : 使用了局部变量和静态变量,并且有被strong引用过,属于堆block
block3 : 使用了局部变量i,并且有被strong引用过,属于堆block
block4 : 虽然被weak指针引用的,但其已经被strong引用过,属于堆block
block5 : 没有被strong指针引用过。即使使用了局部变量,属于栈block

在这里插入图片描述

Block本质

可以将block代码通过clang 重写为C++代码,看其底层实现

int object_c_origin_block() {NSObject *obj = [[NSObject alloc] init];void(^ block)(void) = ^ {NSLog(@"%@",obj);};block();return 0;
}

重写命令

xcrun -sdk iphoneos clang -rewrite-objc <OriginFile> -o <CppFile>

得到Block结构的定义:

struct __object_c_origin_block_block_impl_0 {struct __block_impl impl;struct __object_c_origin_block_block_desc_0* Desc;NSObject *obj;__object_c_origin_block_block_impl_0(void *fp, struct __object_c_origin_block_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};

从重写的结果,我们可以得到Block的以下特点:

  • block出生就是在栈上(isa指针指向_NSConcreteStackBlock
  • block有捕获变量的能力(__object_c_origin_block_block_impl_0内部有obj变量)
  • block也是个对象(存在isa指针)

Block循环引用

在使用Block的时候,最容易出现的问题就是循环引用,尤其是在mvvm架构中,Controller引用ViewModelViewModel引用Block,有的Block的复制在Controller里面完成,有可能会捕获到Controller,从而造成循环引用。

- (void)bindViewModel {self.viewModel.refreshViewCallBack = ^(void) {[self.tableView reloadData];};
}

而解决循环引用有以下几种办法:

__weak __strong协作

- (void)bindViewModel01 {__weak typeof(self) weakSelf = self;self.viewModel.refreshViewCallBack = ^{__strong typeof(weakSelf) strongSelf = weakSelf;[strongSelf.tableView reloadData];};
}

block对象,并没有引用self,在执行block的时候strongSelf的生命周期只有在block内部,在block内部,self的引用计数+1,当执行完block,引用计数-1。既没有引起循环引用,又适当延长了self的生命周期,一举双得。

__block

- (void)bindViewModel02 {__block Controller *blockSelf = self;self.viewModel.refreshViewCallBack = ^{[blockSelf.tableView reloadData];blockSelf = nil;};
}

使用这种方式,同样也可以解决循环引用,但是要注意,block执行完一次,下一次执行之前记得要给blockSelf重新复制,不然会出问题,显然维护这个是非常麻烦的,所以不推荐。

参数传递

- (void)bindViewModel03 {self.viewModel.refreshViewBlcok = ^(OSMVVMViewController * _Nonnull vc) {[vc.tableView reloadData];};self.viewModel.refreshViewBlcok(self);
}

通过block的参数进行传递,同样可以解决循环引用,但是这样做的意义不大,因为block在执行的地方,一定是需要获取到self的,如果已经获取到self了,就可以直接对self操作了,再使用block有点多余。应用并不多,只做了解。

Block中对象的引用计数

来道经典的面试题:

- (void)func3 {NSObject *o = [[NSObject alloc] init];NSLog(@"CFGetRetainCount print start");NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));//1void(^strongBlock)(void) = ^ {NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));};strongBlock();//3  在栈区引用一次,在堆区又引用一次void(^ __weak weakBlock)(void) = ^{NSLog(@"%ld", CFGetRetainCount((__bridge CFTypeRef)o));};weakBlock();// 4栈区引用一次void(^copyBlock)(void) = [strongBlock copy];copyBlock();// 4 本来就在栈上不用+1。void(^copyBlock1)(void) = [weakBlock copy];copyBlock1();// 5NSLog(@"CFGetRetainCount print end");
}

Block Copy

在Block源码中,有关于BlockCopy的源码:

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// 拷贝 block,
// 如果原来就在堆上,就将引用计数加 1;
// 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
// 运行?stack -》malloc
void *_Block_copy(const void *arg) {struct Block_layout *aBlock;// 如果 arg 为 NULL,直接返回 NULLif (!arg) return NULL;// The following would be better done as a switch statement// 强转为 Block_layout 类型aBlock = (struct Block_layout *)arg;const char *signature = _Block_descriptor_3(aBlock)->signature;// 如果现在已经在堆上if (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on high// 就只将引用计数加 1latching_incr_int(&aBlock->flags);return aBlock;}// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock;}else {// Its a stack block.  Make a copy.// block 现在在栈上,现在需要将其拷贝到堆上// 在堆上重新开辟一块和 aBlock 相同大小的内存struct Block_layout *result =(struct Block_layout *)malloc(aBlock->descriptor->size);// 开辟失败,返回 NULLif (!result) return NULL;// 将 aBlock 内存上的数据全部复制新开辟的 result 上memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)// Resign the invoke pointer as it uses address authentication.result->invoke = aBlock->invoke;
#endif// reset refcount// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1// copy 方法中会调用做拷贝成员变量的工作_Block_call_copy_helper(result, aBlock);// Set isa last so memory analysis tools see a fully-initialized object.// isa 指向 _NSConcreteMallocBlockresult->isa = _NSConcreteMallocBlock;return result;}
}
  • copy的调用时机,有强指针第一次指向的时候,会调用一次copy。而这次copy,会把栈区的Block拷贝到堆区。在平时使用[block copy]的时候,也会调用。
  • copy的时候根据Block的类型采取了不同的操作,如果是堆block,只进行引用计数+1,相当于浅拷贝,如果是全局block,直接返回,相当于不拷贝,如果是栈block,是重新开辟新的内存并创建,并且isa指向_NSConcreteMallocBlock,设置类型为堆block,然后返回。

_Block_copy源码中,从栈区copy到堆区的过程中,_Block_call_copy_helper(result, aBlock)的调用时为了复制栈区的Block里面的成员变量,给堆区的Block

// 调用 block 的 copy helper 方法,即 Block_descriptor_2 中的 copy 方法
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{// 取得 block 中的 Block_descriptor_2struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);// 如果没有 Block_descriptor_2,就直接返回if (!desc) return;// 调用 desc 中的 copy 方法,copy 方法中会调用 _Block_object_assign 函数(*desc->copy)(result, aBlock); // do fixup
}

其中最最要的代码时找到了一个copy方法然后调用。而这个copy方法是在desc中。需要理解这些代码,需要借助clang重写的c++代码。

在重写的代码中可以看到__object_c_origin_block_hello_block_impl_0的构造函数的调用:

__object_c_origin_block_hello_block_desc_0_DATA = {0,sizeof(struct __object_c_origin_block_hello_block_impl_0),__object_c_origin_block_hello_block_copy_0,__object_c_origin_block_hello_block_dispose_0
};

其实这里根据变量名称,就能猜个大概,__object_c_origin_block_hello_block_desc_0_DATA就是源码中的desc,而__object_c_origin_block_hello_block_copy_0即为(*desc->copy)方法。所以我们要看其中拷贝成员变量的过程,需要关注__object_c_origin_block_hello_block_copy_0的实现。

static void __object_c_origin_block_hello_block_copy_0(struct __object_c_origin_block_hello_block_impl_0*dst,struct __object_c_origin_block_hello_block_impl_0*src)
{_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}

这里是因为重写前的objcet-c代码里面的block捕获了1个局部变量,只用copy一个成员变量所以只会有一句,如果捕获多个局部变量,就会有多句。例如:

static void __object_c_origin_block_hello_block_copy_0(struct __object_c_origin_block_hello_block_impl_0*dst, struct __object_c_origin_block_hello_block_impl_0*src)
{_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj1, (void*)src->obj1, 3/*BLOCK_FIELD_IS_OBJECT*/);}

其内部其实是调用了_Block_object_assign方法,在源码中也可以找到_Block_object_assign的实现:

/*******************************************************block 可以引用 4 种不同的类型的对象,当 block 被拷贝到堆上时,需要 help,即帮助拷贝一些东西。1)基于 C++ 栈的对象2)Objective-C 对象3)其他 Block4)被 __block 修饰的变量block 的 helper 函数是编译器合成的(比如编译器写的 __main_block_copy_1() 函数),它们被用在 _Block_copy() 函数和 _Block_release() 函数中。copy helper 对基于 C++ 栈的对象调用调用 C++ 常拷贝构造函数,对其他三种对象调用 _Block_object_assign 函数。 dispose helper 对基于 C++ 栈的对象调用析构函数,对其他的三种调用 _Block_object_dispose 函数。_Block_object_assign 和 _Block_object_dispose 函数的第三个参数 flags 有可能是:1)BLOCK_FIELD_IS_OBJECT(3) 表示是一个对象2)BLOCK_FIELD_IS_BLOCK(7) 表示是一个 block3)BLOCK_FIELD_IS_BYREF(8) 表示是一个 byref,一个被 __block 修饰的变量;如果 __block 变量还被 __weak 修饰,则还会加上 BLOCK_FIELD_IS_WEAK(16)所以 block 的 copy/dispose helper 只会传入四种值:3,7,8,24上述的4种类型的对象都会由编译器合成 copy/dispose helper 函数,和 block 的 helper 函数类似,byref 的 copy helper 将会调用 C++ 的拷贝构造函数(不是常拷贝构造),dispose helper 则会调用析构函数。还一样的是,helpers 将会一样调用进两个支持函数中,对于对象和 block,参数值是一样的,都另外附带上 BLOCK_BYREF_CALLER (128) bit 的信息。#疑问:调用的这两个函数是啥?BLOCK_BYREF_CALLER 里究竟存的是什么??所以 __block copy/dispose helper 函数生成 flag 的值为:对象是 3,block 是 7,带 __weak 的是 16,并且一直有 128,有下面这么几种组合:__block id                   128+3       (0x83)__block (^Block)             128+7       (0x87)__weak __block id            128+3+16    (0x93)__weak __block (^Block)      128+7+16    (0x97)********************************************************///
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment,
// 参数 destAddr 其实是一个二级指针,指向真正的目标指针
void _Block_object_assign(void *destArg, const void *object, const int flags) {const void **dest = (const void **)destArg;switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {case BLOCK_FIELD_IS_OBJECT:/*******id object = ...;[^{ object; } copy];********/// 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数,// 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的_Block_retain_object(object);// 使 dest 指向的目标指针指向 object*dest = object;break;case BLOCK_FIELD_IS_BLOCK:/*******void (^object)(void) = ...;[^{ object; } copy];********/// 使 dest 指向的拷贝到堆上object*dest = _Block_copy(object);break;case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:case BLOCK_FIELD_IS_BYREF:/*******// copy the onstack __block container to the heap// Note this __weak is old GC-weak/MRC-unretained.// ARC-style __weak is handled by the copy helper directly.__block ... x;__weak __block ... x;[^{ x; } copy];********/// 使 dest 指向的拷贝到堆上的byref*dest = _Block_byref_copy(object);break;case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:/*******// copy the actual field held in the __block container// Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly.__block id object;__block void (^object)(void);[^{ object; } copy];********/// 使 dest 指向的目标指针指向 object*dest = object;break;case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:/*******// copy the actual field held in the __block container// Note this __weak is old GC-weak/MRC-unretained.// ARC-style __weak is handled by the copy helper directly.__weak __block id object;__weak __block void (^object)(void);[^{ object; } copy];********/// 使 dest 指向的目标指针指向 object*dest = object;break;default:break;}
}

这里是对成员变量的类型进行了分类,如果是对象类型的,直接将对象的增加对象的引用计数,如果是Block类型,会对该Block也进行一次_Block_copy操作,如果是__block修饰的,会调用_Block_byref_copy_Block_byref_copy的解析在下面👇。

__block

对于__block修饰的对象,底层会将其多封装一层。

struct __Block_byref_obj_0 {void *__isa;
__Block_byref_obj_0 *__forwarding;int __flags;int __size;void (*__Block_byref_id_object_copy)(void*, void*);void (*__Block_byref_id_object_dispose)(void*);NSObject *obj;
};

在对block内部操作,其根本是操作他的__forwarding->obj操作:

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_qz_4pv2xnmd3g137rwtb0fpj2rr0000gn_T_OSBlockOriginFile_ce67cf_mi_1,(obj->__forwarding->obj));(obj->__forwarding->obj) = __null;

Block Copy的时候,__block修饰的对象或类型在拷贝的过程中会调用_Block_byref_copy进行拷贝。

// 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3,
//    被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable,
//    原来 byref 的 forwarding 也会指向堆上的 byref;
// 2. 如果 byref 已经在堆上,就只增加一个引用计数。
// 参数 dest是一个二级指针,指向了目标指针,最终,目标指针会指向堆上的 byref
static struct Block_byref *_Block_byref_copy(const void *arg) {// arg 强转为 Block_byref * 类型struct Block_byref *src = (struct Block_byref *)arg;// 引用计数等于 0if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {// src points to stack// 为新的 byref 在堆中分配内存struct Block_byref *copy = (struct Block_byref *)malloc(src->size);copy->isa = NULL;// byref value 4 is logical refcount of 2: one for caller, one for stack// 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。// 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack// one for caller 很好理解,那 one for stack 是为什么呢?// 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 copycopy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;// 堆上 byref 的 forwarding 指向自己copy->forwarding = copy; // patch heap copy to point to itself// 原来栈上的 byref 的 forwarding 现在也指向堆上的 byrefsrc->forwarding = copy;  // patch stack to point to heap copy// 拷贝 sizecopy->size = src->size;// 如果 src 有 copy/dispose helperif (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// Trust copy helper to copy everything of interest// If more than one field shows up in a byref block this is wrong XXX// 取得 src 和 copy 的 Block_byref_2struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);// copy 的 copy/dispose helper 也与 src 保持一致// 因为是函数指针,估计也不是在栈上,所以不用担心被销毁copy2->byref_keep = src2->byref_keep;copy2->byref_destroy = src2->byref_destroy;// 如果 src 有扩展布局,也拷贝扩展布局if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);// 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上copy3->layout = src3->layout;}// 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数(*src2->byref_keep)(copy, src);}else {// Bitwise copy.// This copy includes Block_byref_3, if any.// 如果 src 没有 copy/dispose helper// 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3memmove(copy+1, src+1, src->size - sizeof(*src));}}// already copied to heap// src 已经在堆上,就只将引用计数加 1else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {latching_incr_int(&src->forwarding->flags);}return src->forwarding;
}

__blockBlock类似,如果在栈区,会重新malloc一份,进行深拷贝操作,但这两个forwarding都会指向堆区的,如果已经在堆区,只会将其引用计数+1。

参考博客:

iOS八股文(十六)关于Block,你在第几层?

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

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

相关文章

arping命令 ip地址冲突检测 根据ip查mac地址

arping命令介绍 arping 命令主要用来获取ip对应的mac地址&#xff0c;更新本地arp缓存表。平时主要用来探测ip地址是否冲突即同一个网络里&#xff0c;同一个ip不同mac地址的情况。ip地址冲突将导致网络故障。 arping常用命令参数 arping [参数] ip -U 强制更新邻近主机的a…

关于电脑显示器屏幕看不出灰色,灰色和白色几乎一样无法区分,色彩调整方法

问题&#xff1a; 电脑显示器屏幕看不出灰色&#xff0c;灰色和白色几乎一样无法区分。白色和灰色有色差。 解决方法&#xff1a; 打开“控制面板” ->“色彩管理” ->“高级” ->“校正显示器” 在下一步调节中调成中间这一个实例的样子就可以了 进行微调&#x…

【hadoop】部署hadoop全分布模式

hadoop全分布模式 全分布模式特点部署全分布模式准备工作正式配置hadoop-env.shhdfs-site.xmlcore-site.xmlmapred-site.xmlyarn-site.xmlslaves对NameNode进行格式化复制到另外两台虚拟机启动 对部署是否成功进行测试 全分布模式特点 真正的分布式环境&#xff0c;用于生产具…

【Vue】day02-Vue基础入门

目录 day02 一、今日学习目标 1.指令补充 2.computed计算属性 3.watch侦听器 4.综合案例 &#xff08;演示&#xff09; 二、指令修饰符 1.什么是指令修饰符&#xff1f; 2.按键修饰符 3.v-model修饰符 4.事件修饰符 三、v-bind对样式控制的增强-操作class 1.语法…

边缘检测之loG算子

note // 边缘检测之loG算子&#xff1a;对高斯函数求二阶导数 // G(x,y) exp(-1 * (x*x y*y) / 2 / sigma / sigma) // loG(x,y) ((x*x y*y - 2 * sigma * sigma) / (sigma^4)) * exp(-1 * (x*x y*y) / 2 / sigma /sigma) /* [ 0,0,-1,0,0; 0,-1,-2,-1,0; -1,-2,16,-2…

uni-app实现emoj表情包发送(nvue版)

uni-app实现表情包发送&#xff0c; vue实现思路直接使用grideview网格布局加载emoj表情包即可实现&#xff0c;很简单&#xff0c;但是nvue稍微复杂&#xff0c;这里采用的方案是nvue提供的组件list 看效果 代码 <template><view style"margin-right: 10rpx;m…

如何使用自有数据微调ChatGLM-6B

构建自己的数据集 数据格式&#xff1a;问答对 官网例子 ADGEN 数据集任务为根据输入&#xff08;content&#xff09;生成一段广告词&#xff08;summary&#xff09;。 { "content": "类型#上衣*版型#宽松*版型#显瘦*图案#线条*衣样式#衬衫*衣袖型#泡泡袖…

3.8 Bootstrap 面包屑导航(Breadcrumbs)

文章目录 Bootstrap 面包屑导航&#xff08;Breadcrumbs&#xff09; Bootstrap 面包屑导航&#xff08;Breadcrumbs&#xff09; 面包屑导航&#xff08;Breadcrumbs&#xff09;是一种基于网站层次信息的显示方式。以博客为例&#xff0c;面包屑导航可以显示发布日期、类别或…

Stable Diffusion + EbSynth + ControlNet 解决生成视频闪烁

一、安装 1.1、安装ffmpeg 下载地址&#xff1a; 解压&#xff0c;配置环境变量 E:\AI\ffmpeg\bin 检查是否安装成功 1.2、安装SD的 EbSynth 插件 插件地址 https://github.com/s9roll7/ebsynth_utility 报错&#xff1a;ModuleNotFoundError: No module named extension…

【广州华锐互动】AR远程巡检系统在设备维修保养中的作用

随着科技的不断发展&#xff0c;AR(增强现实)远程巡检系统在设备检修中发挥着越来越重要的作用。这种系统可以将AR技术与远程通信技术相结合&#xff0c;实现对设备检修过程的实时监控和远程指导&#xff0c;提高设备检修的效率和质量。 首先&#xff0c;AR远程巡检系统可以帮助…

单片机尽力少用位域操作

1、在51单片机中少用uint32_t类型&#xff0c;查看汇编真的好多条指令&#xff0c;尽力避免少用。 2、在32位单片机中&#xff0c;u8、u16、u32类型操作起来基本没有什么影响&#xff0c;下图是我做的测试&#xff0c;可能测试不全面&#xff0c;按照当前测试&#xff0c;在32…

Kubernetes_1.27.3_Harbor结合Nacos实战

Nacos 实战 作者:行癫(盗版必究) 一:Nacos简介 1.简介 ​ Nacos是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台;Nacos 致力于帮助您发现、配置和管理微服务;Nacos 提供了一组简单易用的特…

实战 ➾【Red Hat 搭建部署VSFTPd服务】

实战 ➾【Red Hat 搭建部署VSFTPd服务】 &#x1f53b; 前言&#x1f53b; 一、vsFTPd服务部署&#x1f6a5; 1.1 vsFTPd服务安装&#x1f6a5; 1.2 vsFTPd服务的启动与关闭 &#x1f53b; 二、vsFTPd相关配置&#x1f6a5; 2.1 vsFTPd的相关配置文件&#x1f6a5; 2.2 配置匿名…

SpringSecurity(6.1.x版本) 认证,授权,自定义登录,内部机制探讨

SpringSecurity 文章目录 SpringSecurityCSRF跨站请求伪造攻击SFA会话固定攻击XSS跨站脚本攻击开发环境搭建认证直接认证使用数据库认证自定义验证 其他配置自定义登录界面记住我功能 授权基于角色授权基于权限授权使用注解权限判断 内部机制探究授权校验流程安全上下文安全上下…

【JAVA】方法的使用:方法语法、方法调用、方法重载、递归练习

&#x1f349;内容专栏&#xff1a;【JAVA从0到入门】 &#x1f349;本文脉络&#xff1a;JAVA方法的使用&#xff0c;递归练习 &#x1f349;本文作者&#xff1a;Melon_西西 &#x1f349;发布时间 &#xff1a;2023.7.19 目录 1. 什么是方法(method) 2 方法定义 2.1 方法…

C语言——文件操作(超全超详细)

C语言——文件操作 1. 什么是文件 磁盘上的文件是文件 但是在程序设计中&#xff0c;我们一般谈的文件有两种&#xff1a;程序文件、数据文件&#xff08;从文件功能的角度来分类的&#xff09; 1.1 程序文件 包括源程序文件&#xff08;后缀为.c&#xff09;&#xff0c;目…

教程 | 如何10秒内一键生成高质量PPT

Hi! 大家好&#xff0c;我是赤辰&#xff01; 近期新进的学员不少职场小白&#xff0c;对AI工具提效办公很感兴趣&#xff0c;今天火速给大家安排&#xff0c;ChatGPTMindShow强强联合&#xff0c;30秒内快速生成PPT&#xff0c;对于策划小白来说简直是福音呀&#xff01; 市…

用百度地图api获取当前定位,获取经纬度——前端笔记

问题&#xff1a; 做一个按钮&#xff0c;点击后可以获取到当前位置的经纬度&#xff0c;并渲染地图。 效果如下: 代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head><title>获取当前定位测试<…

精选了6款好用的AI绘画工具,值得一试

近几年来&#xff0c;伴随着AI技术的发展&#xff0c;设计领域发生了巨大的变化。AI绘图工具的出现很大程度上减轻了设计师的工作负担&#xff0c;本文精选了6款优秀的AI绘图工具为大家推荐&#xff0c;一起来看看吧&#xff01; 1、即时灵感 即时灵感作为国产的AI绘图工具&a…

PuTTY下载(免安装exe)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…