【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 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…...
微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...
