【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 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...