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

【iOS】 Block再学习

iOS Block再学习

文章目录

  • iOS Block再学习
    • 前言
    • Block的三种类型
      • __ NSGlobalBlock__
      • __ NSMallocBlock__
      • __ NSStackBlock__
        • 小结
    • Block底层分析
      • Block的结构
      • 捕获自由变量
      • 捕获全局(静态)变量
      • 捕获静态变量
      • __block修饰符
        • forwarding指针
    • Block的copy时机
      • block作为函数返回值
      • 将block赋给__strong指针
      • block作为函数中方法名含有usingBlock的方法参数时
      • block作为GCD API的方法参数时候
      • Block的三层拷贝
        • 外界变量的类型
    • Block循环引用
      • 解决循环引用
        • weak-strong-dance
        • __block修饰符
        • 对象self作为参数
    • 小结

前言

笔者之前学习过block的相关内容,但是掌握不牢,今天再重新学习一遍

Block的三种类型

__ NSGlobalBlock__

void(^block)(void) = ^ {NSLog(@"testBlock");};NSLog(@"%@", block);

此时block没有参数也没有返回值,属于全局block

image-20250526185545015

如果访问全局变量:

image-20250526191915758

从上面可以看出无论是否创建block变量,只要访问全局变量的话,他就会创建一个全局区的block

__ NSMallocBlock__

int loaclA = 10;void(^block)(void) = ^ {NSLog(@"testBlock %ld", loaclA);};NSLog(@"%@", block);

此时block捕获了一个临时变量,就是底层拷贝a,所以是堆区block

image-20250526185737373

__ NSStackBlock__

NSLog(@"%@", [^{NSLog(@"%d", loaclA);} class]);

image-20250526190334083

这里在自由变量没有处理之前是栈区block处理之后是堆区block,目前的栈区block就越来越少了

这里是不创建Block变量,且访问自由变量的时候才会出现这里的一个栈区Block

小结
  • block如果不访问自由变量的话,都是存储在全局区的,如果访问全局变量的话,也是存储在全局区的Block
  • block如果访问自由变量的话
    • 如果没有创建block变量,才会创建一个栈区Block变量
    • 创建了一个Block变量,且访问自由变量,才会创建出一个堆区的Block,这里创建出堆区Block的原因是 栈区的Block执行了拷贝操作

Block底层分析

Block的结构

先看这段代码:

int main(int argc, const char * argv[]) {@autoreleasepool {extern NSString* nanString;int localA = 3;void(^block)(void) = ^{NSLog(@"123%ld", localA);};}return 0;
}

这里是一段司空见惯的Block捕获自由变量的代码.这里我们来分析一下这里的源码来认识一下底层内容:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int localA; // 捕获的自由变量__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _localA, int flags=0) : localA(_localA) {impl.isa = &_NSConcreteStackBlock; // 设置isa指针//&_NSConcreteStackBlock:标识 Block 初始类型为 栈 Block(未进行 copy 操作时)impl.Flags = flags;impl.FuncPtr = fp; // 代码块的函数赋值Desc = desc;} // block的一个构造函数,创建Block调用的内容
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int localA = __cself->localA; // bound by copy 这里是一个值拷贝NSLog((NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_3d8947_mi_0, localA);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; extern NSString* nanString;int localA = 3;void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, localA)); //相当于block等于__main_block_impl_0,是一个函数}return 0;
}

这里我们先看block的一个结构体,这里可以说明block是一个 __main_block_impl_0类型的对象.从构造函数可以看出他又一个isa指针

总结

  • block其实是一个对象,结构体,函数,又因为block没有名称,所以也被叫做,匿名函数

关系图示

如果从更加底层的角度来看:

block的底层结构

捕获自由变量

可以看到Block的实现结构体里面新增了localA变量,它在上面这是一个单纯的一个值拷贝

这里我看一下这段核心代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int localA = __cself->localA; // bound by copy 这里是一个值拷贝NSLog((NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_3d8947_mi_0, localA);}

这里我们可以看到这里如果对于他的值其实是只读的,它并不会修改原来block中持有的变量的值,所以是有问题的

block捕获外界变量时,在内部会自动生成同一个属性来保存

捕获全局(静态)变量

这里我们看一下如果是一个全局变量会是什么形式?

int localA = 10;struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {NSLog((NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_4d4ace_mi_0, localA++);}

这里我们可以看到它对于全局变量就是直接引用了全局变量的内容

捕获静态变量

这里我们看一下如果是一个静态局部变量:

int main(int argc, const char * argv[]) {@autoreleasepool {extern NSString* nanString;static int loaclB = 10;void(^block)(void) = ^{loaclB++;NSLog(@"123%ld", localA++);};}return 0;
}

如果是一个静态局部变量的话他是通过指针来获取的,同时静态局部变量与Block建立关联的是指针(int *),也就是说Block捕获的静态局部变量捕获的是变量的指针,因此当我们对静态局部变量进行修改时,Block内部的静态局部变量的值也会随之改变.

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *loaclB;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_loaclB, int flags=0) : loaclB(_loaclB) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

__block修饰符

如果给localA添加一个修饰符__block,然后在block中对localA进行一个加加操作

int main(int argc, const char * argv[]) {@autoreleasepool {extern NSString* nanString;__block int localA = 3;void(^block)(void) = ^{NSLog(@"123%ld", localA++);};}return 0;
}

这时候我们在把它重写成我们的结构体:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_localA_0 *localA; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_localA_0 *_localA, int flags=0) : localA(_localA->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->localA, (void*)src->localA, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->localA, 8/*BLOCK_FIELD_IS_BYREF*/);}//重新看一下函数部分
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_localA_0 *localA = __cself->localA; // bound by refNSLog((NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_fd8242_mi_0, (localA->__forwarding->localA)++);}
int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; extern NSString* nanString;//创建一个__Block_byref_a_0 是一个结构体,相当于把外界对象村对象__attribute__((__blocks__(byref))) __Block_byref_localA_0 localA = {(void*)0,(__Block_byref_localA_0 *)&localA, 0, sizeof(__Block_byref_localA_0), 3};void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_localA_0 *)&localA, 570425344));}return 0;
}

这里我们发现原先的localA变成了__Block_byref_localA_0的部分

struct __Block_byref_localA_0 { //__block修饰的外界变量的结构体void *__isa;
__Block_byref_localA_0 *__forwarding;int __flags;int __size;int localA;
};

总结:

  • 外界变量如果被__block修饰会变成这个结构体
  • 结构体用来保存变量的指针和值
  • 将变量生成的结构体对象中的指针地址 传递给block, 然后在block内部就可以对外界变量进行操作了

两种拷贝对比如下

  • 值拷贝 - 深拷贝,只是拷贝数值,且拷贝的值不可更改,指向不同的内存空间,案例中普通变量loacaA就是值拷贝
  • 指针拷贝 - 浅拷贝,生成的对象指向同一片内存空间,案例中经过__block修饰的变量localA就是指针拷贝
forwarding指针

这里我们首先看下面这段代码:

__block int val = 0;void (^block)(void) = ^{val++;};++val;block();NSLog(@"%ld", val);

这里我们看一下这里的一个val在堆区上面,一个在栈区上面,如果我们不做处理的话对于他的数据计算会有问题:

所以这就是forwarding这个指针的使命所在–确保可以正确的访问__block变量

我们把它转译成c++源码来看一下:


int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; extern NSString* nanString;__attribute__((__blocks__(byref))) __Block_byref_loaclB_0 loaclB = {(void*)0,(__Block_byref_loaclB_0 *)&loaclB, 0, sizeof(__Block_byref_loaclB_0), 10};void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_loaclB_0 *)&loaclB, 570425344));(loaclB.__forwarding->loaclB)++;}return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_loaclB_0 *loaclB = __cself->loaclB; // bound by ref(loaclB->__forwarding->loaclB)++;NSLog((NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_589979_mi_0, localA++);}

从上面我们可以看出对于这种自由变量的加减操作,都是通过我们的(loaclB->__forwarding->loaclB)++这一句指令来执行的,无论是堆区的block还是栈区的block都是这样操作的,这时候我们看一下Block copy的相关源码:

这部分源码内容会在下面的Block的拷贝部分讲一下,这里先不提起,先让笔者了解一下有关于__Block变量拷贝的图

当block变量从stack copy到heap上面的时候,stack上的forwarding被修改程指向heap上的block变量,通过这个机制,保证我们无论是在stack和heap上面都可以访问到同一个block变量

img

Block的copy时机

在 Objective-C 中,Block 最初是在栈上创建的。栈上的 Block(NSStackBlock)生命周期与其定义的作用域相关联,一旦该作用域结束,栈上的 Block 将不再有效。这意味着如果你需要在 Block 的定义作用域外使用它,比如将它作为回调传递或保存为后续使用,你需要将它复制到堆上(成为 NSMallocBlock)。

就好比我们在子线程回调到主线程的时候,我们的Block如果在栈上的话,那么超出作用域就会被销毁,无法回到主线程被调用,因此需要拷贝到堆上

在ARC中进行了很多优化的内容,Block的copy操作会在下面这些情况下执行:

block作为函数返回值

- (nxBlock)test {int a = 0;return ^{NSLog(@"1233123 %d", a);};
}
nxBlock testBlock = [self test];
NSLog(@"%@", [testBlock class]);

image-20250527163628890

将block赋给__strong指针

int loaclA = 10;void(^block)(void) = ^ {NSLog(@"testBlock %ld", loaclA);};NSLog(@"%@", block);

block作为函数中方法名含有usingBlock的方法参数时


NSArray *array = @[@1,@4,@5];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {}];

block作为GCD API的方法参数时候

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{}); 

Block的三层拷贝

这里有一个Block的三层copy,我们先分析学习有关于

static void *_Block_copy_internal(const void *arg, const int flags) 
{struct Block_layout *aBlock;const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;if (!arg) return NULL;aBlock = (struct Block_layout *)arg; // 强制转化成Block_layout对象,防止对外界造成影响if (aBlock->flags & BLOCK_NEEDS_FREE)         // NSConcreteMallocBlock//是否需要释放{latching_incr_int(&aBlock->flags);return aBlock;}else if (aBlock->flags & BLOCK_IS_GLOBAL)     // NSConcreteGlobalBlock//如果是全局block,直接返回{return aBlock;}//	为栈block或者是堆区block,由于堆区需要申请内存,所以是栈区的block的操作// Its a stack block.  Make a copy.struct Block_layout *result = malloc(aBlock->descriptor->size);if (!result) return (void *)0;//通过memmove内存拷贝,将aBlock拷贝到resultmemmove(result, aBlock, aBlock->descriptor->size); // bitcopy first// reset refcountresult->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not neededresult->flags |= BLOCK_NEEDS_FREE | 1;result->isa = _NSConcreteMallocBlock;if (result->flags & BLOCK_HAS_COPY_DISPOSE) {(*aBlock->descriptor->copy)(result, aBlock); // do fixup}return result;
}

下面简单讲述一下这个步骤:

  • 如果需要释放,如果需要就直接释放
  • 如果是globalBlock不需要copy,直接返回
  • 否则就只有两种情况,栈区block和堆区block,但是由于需要拷贝,所以堆区Block需要申请内存,所以最后是有关于栈区Block的拷贝代码
    • 通过malloc申请内存来接受block
    • 通过memmove将block拷贝到新申请的内存中
    • 设置block对象的类型为堆区block,就是result->isa = _NSConcreteMallocBlock;
外界变量的类型
// Runtime support functions used by compiler when generating copy/dispose helpers// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {// see function implementation for a more complete description of these fields and combinations//普通对象,即没有其他的引用类型,也就是我们任何的一个Object都是这个逻辑BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...//block类型作为变量BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable//经过__block修饰的变量和对象BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable//weak 弱引用变量BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers//返回的调用对象 - 处理block_byref内部对象内存会加的一个额外标记,配合flags一起使用BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

这里我们在看一下这里的_Block_object_assign的源码,以及和下面这段Block拷贝的代码进行对比:

int main(int argc, const char * argv[]) {@autoreleasepool {extern NSString* nanString;int loaclB = 10;__block NSObject* obj = [[NSObject alloc] init];NSMutableArray* ary = [NSMutableArray array];void(^block)(void) = ^{[ary addObject:@"123"];NSLog(@"123%ld %@", loaclB, obj);};block();[ary addObject:@"23333"];NSLog(@"%@", ary);loaclB++;}return 0;
}//反编译之后变成下面这样:
//block的拷贝函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->ary, (void*)src->ary, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);} //这里调用了这个_Block_object_assign方法//block里面的匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_obj_0 *obj = __cself->obj; // bound by refNSMutableArray *ary = __cself->ary; // bound by copyint loaclB = __cself->loaclB; // bound by copy((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)ary, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_d58752_mi_0);NSLog((NSString *)&__NSConstantStringImpl__var_folders_rb_0ts7dvhs3zg9fc6kk2nvtlr00000gn_T_main_d58752_mi_1, loaclB, (obj->__forwarding->obj));}
//block的结构
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;NSMutableArray *ary;int loaclB;__Block_byref_obj_0 *obj; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_ary, int _loaclB, __Block_byref_obj_0 *_obj, int flags=0) : ary(_ary), loaclB(_loaclB), obj(_obj->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};

这里我们看到了这里有两种类型:

BLOCK_FIELD_IS_OBJECTBLOCK_FIELD_IS_BYREF这里在这三层拷贝的最后一层进行一个讲解

这里如果有__block修饰的变量就会先调用一次这个方法:

static struct Block_byref *_Block_byref_copy(const void *arg) {//强转为Block_byref结构体类型,保存一份struct Block_byref *src = (struct Block_byref *)arg;if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {// src points to stack 申请内存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 stackcopy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;//block内部持有的Block_byref 和 外界的Block_byref 所持有的对象是同一个,这也是为什么__block修饰的变量具有修改能力//copy 和 scr 的地址指针达到了完美的同一份拷贝,目前只有持有能力copy->forwarding = copy; // patch heap copy to point to itself // 把堆区的指针指向自己src->forwarding = copy;  // patch stack to point to heap copy // 把栈区的指针指向堆区,保证数值一致copy->size = src->size;//如果有copy能力if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) { // 有自己的一个copy的逻辑// Trust copy helper to copy everything of interest// If more than one field shows up in a byref block this is wrong XXX//Block_byref_2是结构体,__block修饰的可能是对象,对象通过byref_keep保存,在合适的时机进行调用struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);copy2->byref_keep = src2->byref_keep; // 调用自定义复制逻辑copy2->byref_destroy = src2->byref_destroy;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);copy3->layout = src3->layout;}//等价于 __Block_byref_id_object_copy(*src2->byref_keep)(copy, src);}else {// Bitwise copy.// This copy includes Block_byref_3, if any.memmove(copy+1, src+1, src->size - sizeof(*src)); //如果为简单类型就直接进行一个内存拷贝}}// already copied to heapelse if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {latching_incr_int(&src->forwarding->flags);}return src->forwarding;
}

这个函数完成了copy之后会调用我们的最后一个函数_Block_object_assign这个函数由编译器来处理

第三层拷贝是block对传入对象的变量进行_Block_object_assign,将block内部将要使用的对象的变量拷贝到block内部。

上面我们自己给出的那段代码中有BLOCK_FIELD_IS_OBJECTBLOCK_FIELD_IS_BYREF

这两种都会进入我们的最后的_Block_object_assign然后进行不同的处理

  • 如果是普通对象,则交给系统ARC处理,并拷贝对象指针,就是引用计数甲一,所以外界变量不能释放
  • 如果是block类型的变量,则通过_Block_copy操作,将block拷贝到堆区
  • 如果是__block修饰的变量,就通过_Block_byref_copy函数进行内存拷贝以及常规处理

三层拷贝总结:

第一层通过_Block_copy实现对象的自身拷贝,从栈区拷贝到堆区

第二层通过调用_Block_byref_copy这个来实现对于对象拷贝成Block_byref类型

第三次调用_Block_object_assign对于__block修饰的当前变量内部对象的内存管理

当且仅当用__block变量的时候才会有三次拷贝.

Block循环引用

先认识一下什么是循环引用:

  • 正常使用: 是指A持有B的引用,当A调用dealloc方法,给B发送release信号,B收到release信号,如果此时B的retainCount为0的时候,则调用B的dealloc方法
  • 循环引用:A, B互相持有,所以导致A无法调用dealloc方法给Breleasse信号,所以B也无法接受到release信号,所以A,B此时都无法释放

image

解决循环引用

self.name = @"123";
self.testBlock = ^(void){NSLog(@"%@", self.name);
};
UIView animateWithDuration:1 animations:^{NSLog(@"%@",self.name);
};

代码第一种发生了一个循环引用的问题,因为在block中持有了外部变量name,导致block也持有了self,而self本来是持有block的,所以导致了self和block的互相持有代码二中没有循环引用,因为self中没有持有animation的block,不构成互相持有

weak-strong-dance

如果block内部并未嵌套block,直接使用__weak修饰self即可

self.name = @"123";__weak typeof (self) weakSelf = self;self.testBlock = ^(void){NSLog(@"%@", weakSelf.name);};

此时的weakSelfself指向同一片内存空间,且使用__weak不会导致self的引用计数发生变化

__weak typeof (self) weakSelf = self;self.testBlock = ^(void){NSLog(@"%@", weakSelf.name);};

如果block里面嵌套了block,那么就要同时使用__weak__strong

self.testBlock = ^(void){__strong typeof(weakSelf) strongSelf = weakSelf;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", weakSelf.name);});};

如果不采用强弱共舞的话,可能会出现一个过了一会self被释放导致我们获取不到对应的数据,所以要采用__strong修饰一下,

其中strongSelf是一个临时变量,在cjlBlock的作用域内,即内部block执行完就释放strongSelf,所以并不会出现block持有self的一个情况导致这些问题

__block修饰符

我们可以采用__block修饰符,在主动调用完后手动释放self

__block PersonViewController* vc = self;self.testBlock = ^(void){NSLog(@"%@", vc.name);vc = nil;};
self.testBlock();

需要注意的是这里的block必须调用如果不调用block的话,vc就不会主动置为nil,那么仍旧是循环引用,self和block都不会被释放

对象self作为参数

主要是把对象self作为参数,提供给block内部使用,不会有引用计数问题

self.testBlock = ^(PersonViewController* vc){NSLog(@"%@", vc.name);}; 

小结

笔者这里简单总结了有关于Block的一个知识,复习了有关于Block捕获变量,以及使用__block修饰的时候forwarding指针的有一个变化,还有一个它发生拷贝的一个时机,以及复习了几种有关于解决Block循环引用的内容.

相关文章:

【iOS】 Block再学习

iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...

Mysql故障排插与环境优化

前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...

文件上传漏洞防御全攻略

要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...

STM32标准库-ADC数模转换器

文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...

webpack面试题

面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...

node.js的初步学习

那什么是node.js呢? 和JavaScript又是什么关系呢? node.js 提供了 JavaScript的运行环境。当JavaScript作为后端开发语言来说, 需要在node.js的环境上进行当JavaScript作为前端开发语言来说,需要在浏览器的环境上进行 Node.js 可…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...

【若依】框架项目部署笔记

参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作: 压缩包下载:http://download.redis.io/releases 1. 上传压缩包,并进入压缩包所在目录,解压到目标…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...

2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版

1.题目描述 2.思路 当前的元素可以重复使用。 (1)确定回溯算法函数的参数和返回值(一般是void类型) (2)因为是用递归实现的,所以我们要确定终止条件 (3)单层搜索逻辑 二…...

数据库正常,但后端收不到数据原因及解决

从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...

Java数组Arrays操作全攻略

Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式(偏导…...

Python学习(8) ----- Python的类与对象

Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...

Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合

无论是python,或者java 的大型项目中,都会涉及到 自身平台微服务之间的相互调用,以及和第三发平台的 接口对接,那在python 中是怎么实现的呢? 在 Python Web 开发中,FastAPI 和 Django 是两个重要但定位不…...

rm视觉学习1-自瞄部分

首先先感谢中南大学的开源,提供了很全面的思路,减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接:https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架: 代码框架结构:readme有…...

高分辨率图像合成归一化流扩展

大家读完觉得有帮助记得关注和点赞!!! 1 摘要 我们提出了STARFlow,一种基于归一化流的可扩展生成模型,它在高分辨率图像合成方面取得了强大的性能。STARFlow的主要构建块是Transformer自回归流(TARFlow&am…...

负载均衡器》》LVS、Nginx、HAproxy 区别

虚拟主机 先4,后7...

命令行关闭Windows防火墙

命令行关闭Windows防火墙 引言一、防火墙:被低估的"智能安检员"二、优先尝试!90%问题无需关闭防火墙方案1:程序白名单(解决软件误拦截)方案2:开放特定端口(解决网游/开发端口不通)三、命令行极速关闭方案方法一:PowerShell(推荐Win10/11)​方法二:CMD命令…...

算法—栈系列

一&#xff1a;删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...

leetcode73-矩阵置零

leetcode 73 思路 记录 0 元素的位置&#xff1a;遍历整个矩阵&#xff0c;找出所有值为 0 的元素&#xff0c;并将它们的坐标记录在数组zeroPosition中置零操作&#xff1a;遍历记录的所有 0 元素位置&#xff0c;将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...

ArcPy扩展模块的使用(3)

管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如&#xff0c;可以更新、修复或替换图层数据源&#xff0c;修改图层的符号系统&#xff0c;甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...

向量几何的二元性:叉乘模长与内积投影的深层联系

在数学与物理的空间世界中&#xff0c;向量运算构成了理解几何结构的基石。叉乘&#xff08;外积&#xff09;与点积&#xff08;内积&#xff09;作为向量代数的两大支柱&#xff0c;表面上呈现出截然不同的几何意义与代数形式&#xff0c;却在深层次上揭示了向量间相互作用的…...

归并排序:分治思想的高效排序

目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法&#xff0c;由约翰冯诺伊曼在1945年提出。其核心思想包括&#xff1a; 分割(Divide)&#xff1a;将待排序数组递归地分成两个子…...

Xcode 16 集成 cocoapods 报错

基于 Xcode 16 新建工程项目&#xff0c;集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…...

Mac flutter环境搭建

一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...

Windows 下端口占用排查与释放全攻略

Windows 下端口占用排查与释放全攻略​ 在开发和运维过程中&#xff0c;经常会遇到端口被占用的问题&#xff08;如 8080、3306 等常用端口&#xff09;。本文将详细介绍如何通过命令行和图形化界面快速定位并释放被占用的端口&#xff0c;帮助你高效解决此类问题。​ 一、准…...

[拓扑优化] 1.概述

常见的拓扑优化方法有&#xff1a;均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有&#xff1a;有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...