半路出家, 我的iOS自学之路-4-Block的声明,定义,闭包性,强引用循环

xiaoxiao2021-02-28  40

半路出家, 我的iOS自学之路-4-Block的声明,定义,闭包性,强引用循环

只学过Java, 半路出家, 自学iOS. 以下是我读完《Objective - C 编程》(第2版)的读书笔记博客中出现任何差错, 遗漏, 还请您在评论中告诉我群号:(空), 欢迎自学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参数中有详细描述.
转载请注明原文地址: https://www.6miu.com/read-2624679.html

最新回复(0)