Block

文章目录

  • 前言
  • Block本质
  • Block循环引用
    • 解决循环引用
      • 1.__weak __strong协作
      • 2.__block
      • 3.参数传递
  • Block中对象的引用计数
  • Block Copy
  • __block
  • Block的分类


前言

之前学过Block了,那就在学学

之前学习Block的博客

参考


提示:以下是本篇文章正文内容,下面案例可供参考

Block本质

这是Block的源码,源码部分可以看这个博客的源码部分之前学习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;
};
  1. Block从出生就是在栈上,(isa指针指向_NSConcreateStatckBlock)
  2. Block有捕获变量的能力(__object_c_origin_block_impl_0内部有obj变量)
  3. Block也是一个对象(存在isa指针)

Block循环引用

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

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

解决循环引用

1.__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的生命周期,一举双得。

2.__block

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

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

3.参数传递

- (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));
    //1
    void(^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();
    // 5
    NSLog(@"CFGetRetainCount print end");
}

Block Copy

// 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,直接返回 NULL

    if (!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
        // 就只将引用计数加 1
        latching_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);
        // 开辟失败,返回 NULL
        if (!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 部分的位全部清为 0
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
        result->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 指向 _NSConcreteMallocBlock
        result->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_2
    struct 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的构造函数的调用:

void(* block)(void) = ((void (*)())&__object_c_origin_block_hello_block_impl_0
                           (
                            (void*)__object_c_origin_block_hello_block_func_0,
                            &__object_c_origin_block_hello_block_desc_0_DATA,
                            (__Block_byref_obj_0 *)&obj,
                            570425344)
                           );

可以看第三个参数的初始化

    __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)其他 Block
 4)被 __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) 表示是一个 block
 3)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;

    // 引用计数等于 0
    if ((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,相当于引用了 copy
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        // 堆上 byref 的 forwarding 指向自己
        copy->forwarding = copy; // patch heap copy to point to itself
        // 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref
        src->forwarding = copy;  // patch stack to point to heap copy
        // 拷贝 size
        copy->size = src->size;

        // 如果 src 有 copy/dispose helper
        if (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_2
            struct 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_3
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    // src 已经在堆上,就只将引用计数加 1
    else 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。

Block的分类

  1. 全局Block(NSGlobalBlock)
  2. 栈Block(NSMallocBlock)
  3. 堆Block(NSStackBlock)

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

- (void)func2 {
    //静态变量
    static int blockInt = 2;
    /**
     — 全局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

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

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

相关文章

AtcoderABC249场

A - JoggingA - Jogging 题目大意 高桥和青木一起慢跑,高桥每隔 ACAC 秒钟走 BB 米,然后休息 CC 秒钟,青木每隔 DFDF 秒钟走 EE 米,然后休息 FF 秒钟。现在已经过去了 XX 秒钟,问谁跑得更远。 思路分析 模拟来解决这…

【广州华锐互动】智慧交通3D可视化交互平台

智慧交通3D可视化交互平台由广州华锐互动开发,是一种基于现代科技的智能交通管理系统,它能够实现对车站内部人员和车辆的实时监控和管理。该平台采用了先进的三维可视化技术,将车站内部的结构和设备以立体、直观的方式呈现在用户面前&#xf…

【云原生】Docker网络Overlay搭建Consul实现跨主机通信

目录 1.overlay网络是什么? 实现overlay环境 1.overlay网络是什么? 在Docker中,Overlay网络是一种容器网络驱动程序,它允许在多个Docker主机上创建一个虚拟网络,使得容器可以通过这个网络相互通信。 Overlay网络使用…

echarts 横向柱状图 刻度标签

echarts 横向柱状图 刻度标签 怎么调试都不左对齐 将width去掉固定宽度 echarts会自适应

tql!一款Go编写的RAT主机管理工具

工具介绍 这是一款使用go编写的RAT主机群管理工具,已具备命令控制台、文件管理、屏幕截屏、开机启动服务、NPS代理等功能。 流量:支持TCP,UDP/KCP协议,通讯默认使用tls证明书进行加密 关注【Hack分享吧】公众号,回复…

数据结构初阶--排序2

目录 前言快速排序思路hoare版本代码实现挖坑法代码实现前后指针法代码实现 快排优化三项取中法代码实现三指针代码实现 快排非递归代码实现 归并排序思路代码实现归并非递归代码实现 计数排序思路代码实现 前言 本篇文章将继续介绍快排,归并等排序算法以及其变式。…

Docker本地镜像发布到阿里云

我们构建了自己的镜像后,可以发布到远程镜像提供给其他人使用,比如发布到阿里云 使用build/commit生成新的镜像,并生成自己镜像的版本标签tag,此新的镜像在自己的本地库中,使用push可以将镜像提交到阿里云公有库/私有库…

FPGA——pwm呼吸灯

文章目录 一、实验环境二、实验任务三、实验过程3.1 verilog代码3.2 引脚配置 四、仿真4.1 仿真代码4.2 仿真结果 五、实验结果六、总结 一、实验环境 quartus 18.1 modelsim vscode Cyclone IV开发板 二、实验任务 呼吸灯是指灯光在微电脑的控制之下完成由亮到暗的逐渐变化…

数据结构顺序表,实现增删改查

一、顺序表结构体定义 #define MAXSIZE 8 //定义常量MAXSIZE,表示数据元素的最大个数为8 typedef int datatype; //重定义int类型,分别后期修改顺序表中存储的数据类型 typedef struct {int len; //顺序表长度datatype data[MAXSIZE…

考研线性代数考点总结

一.行列式 1.数字型行列式 数字行列式的计算含零子式的分块计算 2.行列式的性质 |A||A^T|交换行列,行列式的值变号含公因子的提出或乘进去把某行的K倍加到另一行,行列式的值不变。行列式可以根据某一行或某一列分拆 3.抽象行列式 n阶或高阶行列式 常…

自动驾驶MCU 软件架构说明

目录 1 文档... 2 1.1.1 变更历史... 2 1.1.2 Term.. 2 1.1.3 引用文档... 2 2 MCU软件框架图... 3 3 模块介绍... 3 文档 变更历史 版本Version 状态 Status 内容 Contents 日期 Date 撰写 Editor 批准 Approver V0.1 …

Spring Boot单元测试

前言🍭 ❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️ Spring Spring MVC MyBatis_冷兮雪的博客-CSDN博客 Spring Boot 中进行单元测试是一个常见的做法,可以帮助你验证…

opencv -13 掩模

什么是掩膜? 在OpenCV中,掩模(mask)是一个与图像具有相同大小的二进制图像,用于指定哪些像素需要进行操作或被考虑。掩模通常用于选择特定区域或进行像素级别的过滤操作。 OpenCV 中的很多函数都会指定一个掩模&…

Python 算法基础篇之 Python 语言回顾:变量、条件语句、循环语句、函数等

Python 算法基础篇之 Python 语言回顾:变量、条件语句、循环语句、函数等 引言 1. 变量2. 条件语句3. 循环语句 a ) for 循环 b ) while 循环 4. 函数总结 引言 Python 是一种流行的编程语言,具有简洁而易读的语法。在学习算法时,了解 Python…

人工智能商业变现途径,并介绍详细公司案列

目录 1. 推荐系统:2. 智能广告和营销:3. 聊天机器人和虚拟助手:4. 自动化和机器人化:5. 数据分析和预测:6. 机器视觉和图像识别:7. 金融科技(FinTech):8. 医疗诊断和健康…

STM32(HAL库)驱动GY30光照传感器通过串口进行打印

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 软件IIC引脚配置 2.3 串口外设配置 2.4 项目生成 3、KEIL端程序整合 3.1 串口重映射 3.2 GY30驱动添加 3.3 主函数代 3.4 效果展示 1、简介 本文通过STM32F103C8T6单片机通过HAL库方…

学习记录——语义分割、实时分割和全景分割的区别、几个Norm的区别

语义分割、实时分割和全景分割区别? semantic segmentation(语义分割) 通常意义上的目标分割指的就是语义分割,图像语义分割,简而言之就是对一张图片上的所有像素点进行分类。   语义分割(下图左&#…

HCIA配置命令集

目录 扩展 交换机 路由器 路由器网关配置 DHCP服务器 Telnet :远程登录协议 静态路由配置 动态路由 OSPF RIP NAT—网络地址转换 ACL—访问控制列表 ACL的分类: 配置 配置基础ACL : 例一: 例二: 配…

fastadmin+python+mysql +wxbot实现万能模糊查询(和chatgpt一起完成的)

废话不多说直接上代码: 功能,fastadmin后台管理这些机房服务器的信息,wxbot 通过/指令任意字段的信息查询 让wxbot去数据库里查询相关的信息,在通过wx发送给你。 1.创建数据库 CREATE TABLE fa_databank (ID INT AUTO_INCREMEN…

017 - STM32学习笔记 - SPI读写FLASH(二)

016 - STM32学习笔记 - SPI访问Flash(二) 上节内容学习了通过SPI读取FLASH的JEDEC_ID,在flash资料的指令表中,还看到有很多指令可以使用,这节继续学习使用其他指令,程序模板采用上节的模板。 为了方便起…