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

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

  • 只学过Java, 半路出家, 自学iOS.
  • 以下是我读完《Objective - C 编程》(第2版)的读书笔记
  • 博客中出现任何差错, 遗漏, 还请您在评论中告诉我
  • 群号:(空), 欢迎自学iOS的人加入, 一起交流, 共同成长

1. 以代码的形式讲解, Block在不同位置, 有不同定义方式.

.h文件

#import 
@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 
@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 
@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 
#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都会做一个动作:

  1. 计数类型的变量.
    - 没使用关键字__block修饰, Block内部创建一个新变量, 新变量的值为外部变量的值.
    - 使用关键字__block修饰, Block内部创建一个新的指针变量, 新指针变量的值为外部变量的地址.
  2. 对象类型的变量. Block只会做一个动作, 强引用这个对象.
    - 既然如此, 破解强引用的原理是什么呢?
    1. 一个对象本身可以是 “强引用对象”, 也可以是 “弱引用对象”, 比如上面例子中的 “self” 就是一个 “强引用对象”, 而 “weakSelf” 是一个 “弱引用对象”.
    2. 强引用一个 “弱引用对象”, 并不会影响 “被 ‘弱引用对象’ 引用的对象” 的释放, 这是 “弱引用” 的原理. 详情可见半路出家, 我的iOS自学之路-2-头文件, 属性, 引用计数, 协议, 类别, 类扩展, 然后快速定位: Ctrl+F -> 跟 OC 的 “引用计数” 有关的参数 -> 在weak参数中有详细描述.

你可能感兴趣的:(ios,ios,读书笔记)