半路出家, 我的iOS自学之路-4-Block的声明,定义,闭包性,强引用循环
- 我是一只绝望的菜鸟, 只学过Java, 半路出家, 自学iOS.
- 以下是我读完《Objective - C 编程》(第2版)的读书笔记
- 博客中出现任何差错, 遗漏, 还请您在评论中告诉我
- 群号:653592167, 欢迎自学iOS的人加入, 一起交流, 共同成长
1. 以代码的形式讲解, Block在不同位置, 有不同定义方式.
.h文件
#import <Foundation/Foundation.h>
@interface A : NSObject
/*
定义一个Block类型的属性,
格式: 返回值类型 (^Block名称) (参数类型1, 参数类型2);
*/
@property (nonatomic, strong) NSString *(^thisBlock)(int, NSDate *);
/*
当Block作为返回类型时的定义方式,
格式: 返回类型 (^) (参数类型1, 参数类型2)
*/
- (NSString *(^)(int, NSDate *))getBlock;
/*
当Block作为参数时的定义方式,
格式: 返回类型 (^) (参数类型1, 参数类型2)
*/
- (void)setBloc:(NSString *(^)(int, NSDate *))aBlock;
@end
.m文件
#import "A.h"
@implementation A
- (void)test
{
/*
定义一个Block时,
格式: 返回类型 (^Block名) (参数类型1, 参数类型2)
*/
NSString *(^myBlock) (int, NSDate *); // 定义一个Block, Block名叫 myBlock
/*
编写Block的实现代码,
格式: ^ 返回值 (参数类型1 形参1, 参数类型2 形参2)
*/
myBlock = ^NSString *(int a, NSDate *date) {
// ... 逻辑代码 ..
return [[NSString alloc] init];
};
// 练习
__unused id(^aBlock)() = ^id() {
return nil;
};
}
@end
2. Block的闭包性
1. Block 对 外部变量 的”隐式”复制
即: Block 对象作为参数传入以后, 依然可以使用在 "原来环境" (声明Block的地方) 中的 变量/方法.
定义类A
// .h文件
#import <Foundation/Foundation.h>
@interface A : NSObject
- (void(^)(NSString *))returnBlock; // 返回一个Block
@end
// .m文件
#import "A.h"
@implementation A
- (void(^)(NSString *))returnBlock
{
int age = 11; // 局部变量age, Block 的 外部变量age
return ^void(NSString *name) { // 返回一个 匿名Block
NSLog(@"%@", [NSString stringWithFormat:@"我叫 \"%@\", 我今年 \"%d\" 岁", name, age]);
};
}
@end
定义类B
// .h文件
#import <Foundation/Foundation.h>
@interface B : NSObject
- (void)invokeBlock:(void(^)(NSString *))aBlock; // 执行Block
@end
// .m文件
#import "B.h"
@implementation B
- (void)invokeBlock:(void (^)(NSString *))aBlock
{
NSString *name = @"CN"; // 定义一个 B类 里的 局部变量name
aBlock(name);
}
@end
main.m文件
#import <Foundation/Foundation.h>
#import "A.h"
#import "B.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
A *a = [[A alloc] init];
B *b = [[B alloc] init];
[b invokeBlock:[a returnBlock]];
// 打印结果: 我叫 "CN", 我今年 "11" 岁
}
return 0;
}
把 A类 里的 Block 传入 B类的 实例方法 里, 再在 B类 的 实例方法 中执行这个 Block, Block 依然能调用到 A类 里的 局部变量age.
补充: 我在另一本书《Objective-C高级编程》里看到的解释是:
Block 的 闭包性 原理实现是因为它在”底层”做了以下行为 :
将 Block 引用到的变量, 在 Block 里面同样复制一份, 放入 Block 的代码块中, 一并传递.
这样一来, 虽然表面上你使用到了”原来环境”中的 变量, 实际上, 你只是引用的 Block 里的某一个由编译器自动给你生成的一个 局部变量. 这也是为什么在 Block 能够使用 外部变量, 但是不能修改 外部变量. 因为你修改的 变量, 实际上 Block 里的, “隐式”的声明出来的 局部变量, 这个 局部变量 是 外部变量 的 “替身”, 所以 外部变量 不会改变.
2. 允许 Block 修改 外部变量
使用关键字 __block
- (void)invokeBlock
{
// 添加 __block 修饰
__block int count = 10; // 外部变量count
NSLog(@"%d", count); // 打印: 10
void(^aBlock)() = ^void() { // 定义个 Block
count--;
};
aBlock(); // 执行 Block
NSLog(@"%d", count); // 打印: 9
}
通过 __block 的修饰, Block 不再”制作” 外部变量 的 “替身”, 而是直接调用 外部变量, 此时的 Block 就允许对 外部变量 的修改.
补充:
__block 关键字的作用, 实际上是改变了编译器的”底层”操作, 由之前制作 外部变量 的 “替身”, 变成了制作 外部变量的指针 的 “替身”, 这样一来, Block 是带着 外部变量的指针 一起传递, 通过 指针 对 变量的值 进行修改, 自然 外部变量 的值也就变了. 就跟C语言中的scanf()输入函数原理一样.
int a;
scanf("%d", &a);
3. 强引用循环
1.什么是Block? Block 就是 Block, 是一段可以执行的代码块.
就跟别人问你什么是函数, 什么是方法, 什么是对象一样, 就是一个新的概念, 独立存在的, 它就是它自己.
2. Block可以”强引用”其他对象, 其他对象也可以强引用Block.
"引用"这个功能不是只有对象才有的.
3. 强引用循环的本质就是: 两个对象互相”强引用”, 比如:
A "强引用" B, B 也"强引用" A, 这就强引用循环了.
前提: 当Block为成员变量的时候
1. 直接在Block中使用关键字 “self”, 会造成强引用
_aBlock = ^void() { // 此时的_aBlock为成员变量
NSLog(@"Block强引用self: %@", self); // 强引用循环了
};
解决办法: 使用关键字 __weak
__weak typeof(self) weakSelf = self; // 创建变量weakSelf"弱引用"self;
_aBlock = ^void() { // 此时的_aBlock为成员变量
NSLog(@"Block弱引用self: %@", weakSelf); // 弱引用循环了
};
2. 直接在Block中使用 成员变量, 会造成强引用
_aBlock = ^void() { // 此时的_aBlock为成员变量
NSLog(@"Block使用成员变量_num: %d", _num); // 成员变量 _num
/*
编译器在"底层"做了一个 "self.num" 替换 "_num" 的动作, 所以又变成了 block 强引用 "self",造成强引用循环
*/
};
解决办法: 使用关键字 __weak
__weak typeof(self) weakSelf = self; // 创建变量weakSelf"弱引用"self;
_aBlock = ^void() { // 此时的_aBlock为成员变量
NSLog(@"Block使用弱引用对象的属性num: %d", weakSelf.num);
/*
用 self 的 "弱引用对象" weakSelf 的属性 weakSelf.num 去替换 成员变量 _num
*/
};
前提: 当Block为 局部变量 的时, 在Block中调用self不会造成 强引用循环
- (void)test
{
void(^cnBlock)() = ^void() { // 此时的cnBlock为局部变量
NSLog(@"cnBlock中使用self :%@", self);
};
cnBlock();
/*
因为cnBlock是局部变量, 没有对象"强引用"cnBlock, cnBlock在超出它的生命周期之后,
就会被系统自动释放, 而被cnBlock"强引用"的 "self", 随着 cnBlock 被系统释放,
"self" 的 "强引用" 也就消失了, "self" 的 "引用计数" -1. 不会造成 "强引用循环"
*/
}
总结: 凡是被Block用到的资源, Block都会做一个动作:
- 计数类型的变量.
- 没使用关键字__block修饰, Block内部创建一个新变量, 新变量的值为外部变量的值.
- 使用关键字__block修饰, Block内部创建一个新的指针变量, 新指针变量的值为外部变量的地址. - 对象类型的变量. Block只会做一个动作, 强引用这个对象.
- 既然如此, 破解强引用的原理是什么呢?
- 一个对象本身可以是 “强引用对象”, 也可以是 “弱引用对象”, 比如上面例子中的 “self” 就是一个 “强引用对象”, 而 “weakSelf” 是一个 “弱引用对象”.
- 强引用一个 “弱引用对象”, 并不会影响 “被 ‘弱引用对象’ 引用的对象” 的释放, 这是 “弱引用” 的原理. 详情可见半路出家, 我的iOS自学之路-2-头文件, 属性, 引用计数, 协议, 类别, 类扩展, 然后快速定位: Ctrl+F -> 跟 OC 的 “引用计数” 有关的参数 -> 在weak参数中有详细描述.