【iOS】Block底层分析
目录
- 前言
- Block底层结构
- Block捕获变量原理
- 捕获局部变量(auto、static)
- 全局变量
- 捕获实例`self`
- Block类型
- Block的copy
- Block作为返回值
- 将Block赋值给__strong指针
- Block作为Cocoa API中方法名含有usingBlock的方法参数
- Block作为GCD API的方法参数
- Block属性的写法
- Block访问对象类型的auto变量
- Block在栈上
- Block被拷贝到堆上
- Block从堆上移除
- 修饰符__block
- __block内存管理
- __forwarding指针
- __block修饰对象类型
- Block循环引用
- 解决办法
- 强弱共舞
- 总结
前言
Block是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,因为它内部有isa指针
Block的基本使用请看这两篇文章:
- k
- l
本篇文章着重探究Block这些特性的底层原理
Block底层结构
声明一个最简单的块并调用:
void (^block)(void) = ^{NSLog(@"Hello World!");
};
block();
使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C++代码:
// 原本的代码有各种强制转换,目前不重要,先删去从简// 声明并实现一个block
// void (*block)(void) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);// 调用执行block
// ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
block->FuncPtr(block);
// __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里
这些穿插了许多下划线的符号实际上是不同的结构体变量,Block本质就是struct __main_block_impl_0类型的结构体,下图清晰地说明了block的底层结构:

__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的(相当于直接把__block_impl里的值都放到__main_block_impl_0里)
所以block.impl->FuncPtr(block)就相当于block->FuncPtr(block)
Block捕获变量原理
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
捕获局部变量(auto、static)
auto:自动变量,离开作用域就自动销毁,只存在于局部变量
static:静态局部变量
// 不加关键字默认是auto变量
/*auto*/ int age = 10;
static int height = 175;void (^block)(void) = ^{// age、height的值捕获进来(capture))NSLog(@"age is %d, height is %d", age, height);
};// 修改局部变量的值
age = 20;
height = 180;block();
NSLog(@"%d %d", age, height);
打印结果:

可以看到age仍为修改前的值,而height确确实实被修改了
将以上代码转换成C++代码来看一下:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;int *height;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
Block结构体的变量多了两个,分别是age、height,这说明外部的变量被捕获到了Block的内部- 构造函数后面的
: age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来保存
声明实现Block调用析构函数:
int age = 10;
static int height = 175;block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));age = 20;
height = 180;
而后调用Block,实际调用__main_block_func_0:
block->FunPtr(block)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copyint *height = __cself->height; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}
此时的age是值传递,打印的只是Block初始化时传进去的值,后面age修改跟这个值无关;height是指针传递,打印的是height变量地址一直所指向那块内存的值
全局变量
int age_ = 10;
static int height_ = 175;int main(int argc, const char * argv[]) {@autoreleasepool {void (^block)(void) = ^{NSLog(@"age_ is %d, height_ is %d", age_, height_);};age_ = 20;height_ = 180;block();}return 0;
}
全局变量一直在内存中,打印的一直是最新的值,不用捕获

为什么会有这样的差异呢?
auto和static:因为作用域的问题,自动变量的内存随时可能被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到;静态变量就不一样了,它一直在内存中(作用域仅限于定义它们的函数、它们不能在函数外访问),随时可以通过指针访问到最新的值
全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),使用的时候再从Block中取出,而全局变量是直接访问
捕获实例self
- (void)testSelf {void (^block)(void) = ^{// NSLog(@"--------%p -- %p -- %p -- %p", self, _name, self->_name, self.name);NSLog(@"--------%p", self);/*NSLog(@"--------%p", self->_name);相当于NSLog(@"--------%p", _name);也会捕获进去*/};block();
}
看了它的C++实现后,发现self也会被捕获进去
实际上OC方法转换成C++函数后会发现前两个参数永远是方法调用者self、方法名_cmd:
void testSelf(Person* self, SEL _cmd, ) {// ...
}
即然self是参数,参数也是局部变量,它被捕获进Block也就能解释得通了
Block类型
上面提到Block是OC对象,因为它有isa指针,对象的isa指向它的类型,那么Block都有什么类型呢?
首先运行以下代码:
void (^block)(void) = ^{NSLog(@"Hello!");
};
NSLog(@"%@ %@", block, [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
/*__NSGlobalBlock__NSBlockNSObject*/
可以看到Block类型的根类是NSObject,也能说明Block是一个OC对象
不同操作对应的Block类型不同
// Global:没有访问auto变量,跟static变量无关
void (^block1)(void) = ^{NSLog(@"Hello");
};// 函数调用栈:要调用一个函数的时候,就会指定一块栈区空间给这个函数用
// 一旦函数调用完毕后,栈区的这块空间就会回收,变成垃圾数据,会被其他数据覆盖// Stack:访问了auto变量
int age = 21;
void (^block2)(void) = ^{NSLog(@"Hello - %d", age);
};
// ARC下打印Malloc?MRC下确实是StackNSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{NSLog(@"%d", age);
} class]); // 打印结果:__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__// 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
// 首先肯定以运行时的结果为准,Block确实有三种类型,可能会通过Runtime动态修改类型
-
没有访问自动变量的Block类型是
__NSGlobalBlock__,存储在数据段
其实Global不常用,既然不访问变量,那么将代码块封装成函数一行直接调用才显得更为简洁 -
访问了自动变量的Block类型是
__NSStackBlock__,存储在栈区
以上代码是在MRC下运行的 -
__NSStackBlock__的Block调用了copy后类型会变为__NSMallocBlock__,存储在堆区
若是在ARC下运行,即使不用copy修饰编译器也会自动对__NSStackBlock__进行copy操作,block2的类型将会是Malloc类型
手动对每种类型的Block调用copy后的结果如下图所示

Block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理
Block作为返回值
typedef void(^BBlock)(void);BBlock myBlock(void) {int age = 21;return ^{NSLog(@"----------%d", age);};
}BBlock bblock = myBlock();
bblock();
NSLog(@"%@", [bblock class]); // __NSMallocBlock__
//BBlock myBlock(void) {
// return [^{
// NSLog(@"----------");
// } copy];
//}
由于Block在栈区,所以函数调用完毕后Block内存就被销毁了,再去调用它就很危险,如果在MRC下运行上述代码,编译器会提示报错:

ARC下不必担心此问题,编译器会自动对返回的Block进行copy操作(如注释所写),返回拷贝到堆上的Block
将Block赋值给__strong指针
int age = 21;
/*__strong*/ BBlock bblock = ^{NSLog(@"--------%d", age);
};
NSLog(@"%@", [bblock class]); // ARC:__NSMallocBlock__// 没有被强指针指着
NSLog(@"%@", [^{NSLog(@"--------%d", age);
} class]); // __NSStackBlock__
Block作为Cocoa API中方法名含有usingBlock的方法参数
NSArray* array = @[@"one", @2, @{@"seven" : @7}];
// 遍历数组并调用Block
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {NSLog(@"%@ --- %lu", obj, (unsigned long)idx);
}];

Block作为GCD API的方法参数
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
Block属性的写法
因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
Block访问对象类型的auto变量
Block在栈上
只要Block存在栈上,无论访问外部变量是用强指针还是弱指针,都不会对外部auto变量产生强引用
Block被拷贝到堆上
如果Block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作
BBlock bblock;
{__strong Person* person = [[Person alloc] init];// __weak Person* person = [[Person alloc] init];person.age = 21;bblock = ^{// 在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放NSLog(@"-%d-", person.age);};// MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放//[bblock release];
}
NSLog(@"--------------");
将上面代码文件转换成C++文件:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__strong person;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
Block内部的__main_block_desc_0结构体会调用copy函数,copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
Block从堆上移除
如果Block从堆上移除,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动release引用的auto变量
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
注:
- 只有在引用对象类型的变量时,才会生成
copy和dispose函数 - 如果引用的是
static修饰的对象类型,那么捕获的变量在C++代码中将会是Person *__strong *person; - 代码里有
__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定支持ARC、指定运行时系统版本xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
使用GCD API验证Block对外部变量的强弱引用(Github Demo):
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {Person* person = [[Person alloc] init];__weak Person* weakPerson = person;// 强引用了,Block调用完毕释放了person才会释放
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"---%@", person);
// });// 弱引用,调用Block之前person已经释放
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"---%@", weakPerson);
// });// 编译器已经检查到会有强引用
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"---1%@", weakPerson);
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// NSLog(@"---2%@", person);
// });
// });// 不会等到弱引用就释放了dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"---1%@", person);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"---2%@", weakPerson);});});NSLog(@"Screen Touched");
}
修饰符__block
如果在Block内部修改捕获的auto变量值,编译器将会报错:
int age = 21;
BBlock block = ^{age = 20;NSLog(@"%d", age);
};
block();

从底层可看出在这里修改变量的值,实际上是通过改变__main_block_fun_0函数里的局部变量达到改变main函数里的变量,这是两个独立的函数,显然不可能
1. 使用static修饰变量
用static来修饰age属性,底层用指针访问,block内部引用的是age的地址值,函数间会传递变量的地址,可以根据地址去修改age的值,修改的就是同一块内存
但不好的是age属性会一直存放在内存中不销毁,造成多余的内存占用,而且会改变age属性的性质,不再是一个auto变量了
2. 使用__block修饰变量
用__block来修饰属性,底层会生成__Block_byref_age_0类型的结构体对象,里面存储着age的真实值

转换成C++文件来查看内部结构,经__block修饰后,会根据__main_block_impl_0里生成的age对象来修改内部的成员变量age而且在外面打印的age属性的地址值也是__Block_byref_age_0结构体里的成员变量age的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值
struct __Block_byref_age_0 {void *__isa;
__Block_byref_age_0 *__forwarding; // 指向结构体本身int __flags;int __size;int age;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_age_0 *age; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 传进去的是age的地址__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}return 0;
}
总结
__block可以用于解决block内部无法修改auto变量值的问题- 编译器会将
__block变量包装成一个对象 - 其实修改的变量是
__block生成的对象里面存储的变量的值,而不是外面的auto变量,但是内部生成的相同的变量的地址和外面的auto变量地址值是一样的,所以修改了内部的变量也会修改了外面的auto变量 __block不能修饰全局变量、静态变量(static)
__block内存管理
程序编译时,block和__block都是在栈中的,这时并不会对__block变量产生强引用
因为__block也会包装成 OC对象,所以block底层也会生成copy函数和dispose函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
Block复制到堆上
当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)
实际上,这时__block修饰的变量因为被包装成了OC对象,所以也会被拷贝到堆上,如果再有block强引用__block,由于__block变量已经拷贝到堆上了,就不会再拷贝了:

Block从堆上移除
当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)
如果有多个block同时持有着__block变量,那么只有所有的block都从堆中移除了,__block变量才会被释放

__block和OC对象在block中的区别
__block生成的对象就是强引用,而NSObject对象会根据修饰符__strong或者__weak来区分是否要进行retain操作
注意:__weak不能修饰基本数据类型,编译器会报__weak' only applies to Objective-C object or block pointer types; type here is 'int'警告
__forwarding指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_age_0 *age = __cself->age; // bound by ref(age->__forwarding->age) = 20;
}
- 在栈中,
__block中的__forwarding指针指向自己的内存地址 - 复制到堆中之后,
__forwarding指针指向堆中的__block,堆中的__forwarding指向堆中的__block - 这样的目的都是为了不论访问的
__block是在栈上还是在堆上,都可以通过__forwarding指针找到存储在堆中的auto变量

保证20被存储在堆中Block所引用的变量
__block修饰对象类型
情况类似于Block捕获对象类型的auto变量,__block包装的对象结构体里的对象变量会有__strong或__weak修饰
当__block对象在栈上时,不会对指向的对象产生强引用
当__block对象被copy到堆上时,也会生成一个新的结构体对象,并且只会被block进行强引用,会根据不同的修饰符__strong和__weak来对应着该对象类型成员变量是被强引用(retain)或弱引用
struct __Block_byref_weakPerson_0 {void __isa;__Block_byref_weakPerson_0 __forwarding;int __flags;int __size;void (__Block_byref_id_object_copy)(void, void);void (__Block_byref_id_object_dispose)(void*);Person *__weak weakPerson;
};static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose((void * ) ((char)src + 40), 131);// __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
__attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
注:在MRC环境下即使用__block修饰,对于结构体对象的成员变量,__block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,__block修饰的变量就已经释放了,这点和在ARC环境下不同
Block循环引用
两个对象相互强引用,导致谁的引用计数都不会归零,谁都不会释放
int main(int argc, const char * argv[]) {@autoreleasepool {Person* person = [[Person alloc] init];person.age = 21;person.block = ^{NSLog(@"%d", person.age);};}NSLog(@"111111111111");return 0;
}
结果就是person对象不会释放,因为没有调用dealloc方法
person对象里面的block属性强引用着block对象,而block对象内部也会有一个person的成员变量指向这个Person对象,这样就会造成循环引用,谁也无法释放
@implementation Person- (void)test {self.block = ^{NSLog(@"%d", self.age);};
}- (void)dealloc
{NSLog(@"%s", __func__);
}@endint main(int argc, const char * argv[]) {@autoreleasepool {Person* person = [[Person alloc] init];person.age = 21;[person test];}return 0;NSLog(@"111111111111");
}
block引用(捕获,之前提到self就是函数的第一个参数,参数也是局部变量)self,self又持有block,同样会造成循环引用

解决办法
-
使用
__weak、__unsafe_unretained让Block指向对象的引用变为弱引用
// __unsafe_unretained typeof(self)weakSelf = self; __weak typeof(self)weakSelf = self;self.block = ^{NSLog(@"%d", weakSelf.age); }; -
用
__block解决,用__block修饰对象会造成三者相互引用造成循环引用,需要手动调用block
__block Person* person = [[Person alloc] init]; person.age = 21; person.block = ^{NSLog(@"%d", person.age);person = nil; }; person.block();block内部也需要手动将person置空,这个person是__block内部生成的指向Person对象的变量 -
block传参,将self作为参数传入block中,进行指针拷贝,并没有对self进行持有// Person.m self.block = ^(Person * _Nonnull person) {NSLog(@"%d", person.age); }; self.block(self); -
MRC下不支持
__weak,只能使用__unsafe_unretained
MRC下直接使用__block即可解决循环引用,上面提到了MRC环境下__block修饰的变量只会被弱引用,已达成效果:__block Person *person = [[Person alloc] init]; person.age = 10;person.block = [^{NSLog(@"age is %d", person.age); } copy];[person release];
强弱共舞
这种情况虽没有引起循环引用,但block延迟执行2秒,等person释放后,就无法获取其age,很不合理
__weak typeof(person) weakPerson = person;
person.block = ^{dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%d", weakPerson.age);});
};
person.block();
改进一下:
__weak typeof(person) weakPerson = person;
person.block = ^{__strong __typeof(weakPerson)strongPerson = weakPerson;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%d", strongPerson.age);});
};
person.block();
通过运行结果发现,完全解决了以上self中途被释放的问题,这是为什么呢?分析如下:
- 在完成
block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放
总结
Block在iOS开发中极为重要,非常适合处理异步操作、回调、集合操作等场景,重点学习Block的内存管理、变量捕获和循环引用解决方案
相关文章:
【iOS】Block底层分析
目录 前言Block底层结构Block捕获变量原理捕获局部变量(auto、static)全局变量捕获实例self Block类型Block的copyBlock作为返回值将Block赋值给__strong指针Block作为Cocoa API中方法名含有usingBlock的方法参数Block作为GCD API的方法参数Block属性的写…...
复现dom破坏案例和靶场
文章目录 靶场网址第一个实验步骤和原理(代码为示例要根据自己的实验修改) 第二个实验步骤和原理(代码为示例要根据自己的实验修改) 靶场网址 注册后点击 第一个实验 此实验室包含一个 DOM 破坏漏洞。注释功能允许“安全”HTML。为了解决这个实验,请构造一个 HT…...
【高校科研前沿】南方科技大学冯炼教授等人在遥感顶刊RSE发文:全球人类改造的基塘系统制图
1.文章简介 论文名称:Global mapping of human-transformed dike-pond systems(全球人类改造的基塘系统制图) 第一作者及单位:Yang Xu(南方科技大学环境学院) 第一通讯作者及单位:冯炼&#x…...
How to run angular CICD on gitlab-runner of wsl?
前提文件 .gitlab-ci.yml, .dockerignore, ci-funcs.sh, Dockerfile, karma.conf.js, nginx.conf, nginx-custom.conf, sonar-project.properties 1.test.ts const context require.context(./app/pages, true, /\.spec\.ts$/); 2.sonar-project.properties sonar.sourcessrc/…...
搭建Java集成开发环境IntelliJ IDEA
搭建Java集成开发环境(Integrated Development Environment,简称IDE)IntelliJ IDEA是一个涉及多个步骤的过程,旨在帮助Java开发者高效、舒适地进行编程工作。IntelliJ IDEA由JetBrains公司开发,以其强大的代码自动补全…...
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
🌐JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决 🖥️ 浏览器事件学习和编写 浏览器事件是用户与网页交互的主要方式,了解并掌握这些事件的处理方…...
驾校预约学习系统--论文pf
TOC springboot373驾校预约学习系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上不可跨域…...
交叉编译ARM平台的OpenCV1.0
首先,从http://www.opencv.org.cn下载1.0的源码包,然后解压出来,进入解压后的目录,再进行下面的修改: 将configure 文件下列内容注释掉(有两处),只保留GTK_CFLAGS"" 、GTK_LIBS"" 、have_gtkno 三项内容(如下黑体所示)&…...
牛客周赛 Round 56 AK
背景 语言艺术 A题:面包店故事 题意 一块面包要x元,加培根要y元,有n元,问能否买到加培根的面包 思路 大水题,gpt秒了 代码 #include <bits/stdc.h> using namespace std; int main() {int x, y, n; cin …...
LeetCode 热题 HOT 100 (038/100)【宇宙最简单版】
【动态规划】No. 0337 打家劫舍III【中等】👉力扣对应题目指路 希望对你有帮助呀!!💜💜 如有更好理解的思路,欢迎大家留言补充 ~ 一起加油叭 💦 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&a…...
SQLALchemy ORM 的关联关系之 ORM 中的一对一
SQLALchemy ORM 的关联关系之 ORM 中的一对一 场景示例实现一对一关系使用 `relationship()` 和外键(FK)插入和查询数据总结在 SQLAlchemy ORM 中,一对一(One-to-One)关联关系是一种比较少见的模型关系,但它确实有其应用场景,特别是在你需要将一个对象与另一个对象紧密绑…...
模型部署 - docker
docker简介 Docker 是一种开源的容器化平台,允许开发者将应用程序及其依赖项打包到一个标准化的单元中,称为“容器”。这些容器可以在任何支持 Docker 的系统上运行,无需担心环境差异。 为什么需要 Docker? 在传统的开发中&…...
学懂C++(三十四):深入详解 C++ 高级多线程编程技术中的并发设计模式
引言 在现代软件开发中,多线程编程已成为提升性能和响应能力的重要手段。设计模式为解决并发问题提供了有效的解决方案。本文将探讨常见的并发设计模式,包括生产者-消费者模式、读者-写者模式、单例模式、帧-工作者模式以及Future-Task模式,并…...
大数据产业链图谱_产业链全景图_大数据行业市场分析
数据作为新型生产要素,是数字化、网络化、智能化的基础,已快速融入生产、分配、流通、消费和社会服务管理等各环节,影响着千行百业,推动着我国数字经济的蓬勃发展。 大数据又称巨量数据、海量数据,是由数量巨大、结构…...
photonserver 部署相关教程
Photon Server 是 Exit Games 开发的高性能、可扩展的多人游戏服务器框架。部署 Photon Server 需要一些基础的服务器管理知识和配置技巧。以下是一个基本的部署教程,帮助你将 Photon Server 部署在 Windows 服务器上。 目录 1. 下载并安装 Photon Server 2. 配置…...
GEE训练:sentinel-1数据的投影、显示和导出
函数 projection() Returns the default projection of an Image. Throws an error if the bands of the image dont all have the same projection. 返回图像的默认投影。如果图像带的投影不一致,则会抛出错误。 Arguments: this:image (Image): The image from which …...
后端学习笔记(七)--MyBatis参数传递
5.MyBatis参数传递 *MyBatis接口方法中可以接收各种各样的参数,MyBatis底层对于这些参数进行不同的封装处理方式 *单个参数: 1.POJO类型:直接使用,属性名和参数占位符名称一致 2.Map集合:直接使用,…...
uniapp 网络请求自动处理loading
文章目录 背景整理思路V1版本V2版本V3版本 背景 最近在写uniapp,发现执行网络请求的时候经常要处理Loading效果。 比如,在发送网络请求之前,触发Loadng;无论请求成功还是失败都要关闭Loading;请求失败的时候我们还要…...
【Solidity】函数的使用
构造函数 构造函数仅在部署合约时调用一次,它的作用主要是初始化一些状态变量。 contract Demo {address public owner;uint public num;constructor(uint _num) {owner msg.sender;num _num;} }函数装饰器 函数装饰器可以在函数执行之前或之后插入代码逻辑&am…...
详解golang内存管理
介绍 要搞明白 Go 语言的内存管理,就必须先理解操作系统以及机器硬件是如何管理内存的。因为 Go 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙
WebGL:在浏览器中解锁3D世界的魔法钥匙 引言:网页的边界正在消失 在数字化浪潮的推动下,网页早已不再是静态信息的展示窗口。如今,我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室,甚至沉浸式的V…...
js 设置3秒后执行
如何在JavaScript中延迟3秒执行操作 在JavaScript中,要设置一个操作在指定延迟后(例如3秒)执行,可以使用 setTimeout 函数。setTimeout 是JavaScript的核心计时器方法,它接受两个参数: 要执行的函数&…...
