美文网首页程序员
由简入深,洞悉Block,源码亲测

由简入深,洞悉Block,源码亲测

作者: 小曼blog | 来源:发表于2020-06-26 11:00 被阅读0次
timg.jpg

关于Blcok,我们经常使用,在Swift中,闭包更是无处不在。那么,关于Block,我们到底了解多少呢?这篇文章旨在对Block做一次全面的总结,从简单应用,到案例讲解再到易错点,以及原理和内存机制,争取一文写清楚Block。
拒绝搬砖,手写代码,全部测试。

知识点:

小小扩展:关于值的类型

1.Block的几种用法
(1)作为局部变量;
(2)作为全局变量;
(3)Block作为函数参数;
(4)Block作为函数返回值;
(5)Block中使用局部变量和全局变量;

2.Block在项目中常用案例
(1)页面传值;
(2)代替代理使用;
(3)作为参数传值;

3.Block的常见问题
(1)循环引用问题产生以及解决方法;
(2)崩溃问题以及解决方法;

4.Block内存机制
(1)iOS五大内存区
(2)Block的存放
(2.1)全局区Block
(2.2)栈区Block
(2.3)堆区Block
(3)为什么加了_ _block,就可以访问外部的变量了?

5.Block原理

小小扩展:关于值的类型

我们知道,无论是变量、常量还是参数,都会有它的类型,我们一开始接触一门语言的时候,都会详细的介绍这门语言的各种类型。比如C的int, float, double, char, const char, char *, *, 等等类型,比如OC的NSString, NSArray, NSSet, NSValue等等类型。我们也知道,这些类型用来标志一个值在内存中的存储方式。把某个类型放在值的前面,用于声明这个值的类型。比如:
int a;
char b;
NSString * name;
那么,Block作为匿名函数,它也是有类型的,不过它的类型比较复杂一些,是由简单类型组合起来的类型。比如:
void (^block) (void)
类型是: void (^) (void)
NSString * (^block1) (void)
类型是:NSString * (^) (void)
NSInteger (^block3) (NSInteger, NSInteger)
类型是: NSInteger (^) (NSInteger, NSInteger)
明白了Block的类型,我们就可以把Block跟其他简单类型一样作为参数,作为变量进行使用了。

1.Block的几种用法
(1)作为局部变量;
//block作为局部变量
-(void)test1 {
   /**
    返回值类型 (^Block名称)(参数列表) = ^ 返回值类型 (参数列表){
       实现代码
    }
     */
    void (^block) (void) = ^ {
        NSLog(@"返回值为空,参数为空的block");
    };
    //使用block
    block();
    
    NSString * (^block1) (void) = ^{
        NSString * temp = @"返回值为字符串,参数为空的block";
        return temp;
    };
    //使用
    NSLog(@"%@", block1());
    
    NSInteger (^block2) (NSInteger) = ^ NSInteger (NSInteger a){
        NSInteger t = a * 10;
        return t;
    };
    NSInteger test2 = block2(3);
    NSLog(@"test2: %ld", (long)test2);
    
    //求两个数的和
    NSInteger (^block3) (NSInteger, NSInteger) = ^ NSInteger (NSInteger a, NSInteger b) {
        return a + b;
    };
    NSInteger test3 = block3(2, 7);
    NSLog(@"test3: %ld", test3);
    
    //求两个数的乘积
    double (^block4) (double, double) = ^double (double a, double b) {
        return a * b;
    };
    NSLog(@"test4: %.2f", block4(2.3, 4.7));
    
    //求两个数的加减乘除运算
    double (^block5) (double, double, NSString*) = ^ double (double a, double b, NSString* mark){
        
        if ([mark isEqualToString:@"+"]) {
            return a + b;
        } else if ([mark isEqualToString:@"-"]) {
            return a - b;
        } else if ([mark isEqualToString:@"*"]) {
            return a * b;
        } else if ([mark isEqualToString:@"/"]) {
            if (b == 0) {
                return -1;
            }
            return a / b;
        } else {
            return -1;
        }
    };
    
    NSLog(@"test5: %f", block5(4.0, 6.5, @"+"));
    NSLog(@"test6: %f", block5(7.8, 6.5, @"-"));
    NSLog(@"test7: %f", block5(2.4, 6.5, @"*"));
    NSLog(@"test8: %f", block5(4.8, 2.4, @"/"));
    
    //block赋值
    double (^block6) (double, double) = block4;
    //调用
    NSLog(@"test9: %.2f", block6(2.1, 3.8));
}

打印结果:


image.png
(2)作为全局变量;
//给block起别名
typedef NSInteger (^SumBlock) (NSInteger, NSInteger);
@interface BlockTestViewController ()

@property(nonatomic, copy)SumBlock block7;

@end
//block作为全局变量
-(void)test2 {
    self.block7 = ^ NSInteger (NSInteger a, NSInteger b) {
        NSInteger sum = 0;
        for (NSInteger i = a; i < b + 1; i++) {
            sum = sum + I;
        }
        NSLog(@"从 %ld 到 %ld 的和为 %ld", a, b, sum);
        return sum;
    };
    
    if (self.block7) {
        //调用
        self.block7(0, 100);
        self.block7(0, 50);
    }
}

打印结果:


image.png
(3)Block作为函数参数;
//请求成功
typedef void (^SuccessBlock)(NSDictionary* json, int code);
//请求失败
typedef void (^FailBlock)(NSString* msg);

//调用test3系列:block作为参数
-(void)test4 {
    //有参数,有返回值的block作为参数
    [self test3:^NSString *(NSString *name) {
        NSLog(@"name = %@", name);
        return @"哇哈哈哈哈";
    }];
    //有参数,无返回值的block作为参数
    [self test3_1:^(NSString *name) {
        NSLog(@"我是block的参数,name = %@", name);
    }];
    //无参数,有返回值的block作为参数
    [self test3_2:^int{
        return 10;
    }];
    //函数也有返回值,block也有返回值
    NSArray * arr = [self test3_3:^NSArray *(NSArray *array) {
        //过滤重元素
        NSMutableArray * tempArray = [NSMutableArray array];
        BOOL flag = NO;
        for (NSNumber * num in array) {
            for (NSNumber * temp in tempArray) {
                if (num == temp) {
                    flag = YES;
                }
            }
            if (flag == NO) {
                [tempArray addObject:num];
            }
            flag = NO;
        }
        return tempArray;
    }];
    NSLog(@"测试的array: %@", arr);
    
    //应用实例
    [self loadDataSuccess:^(NSDictionary *json, int code) {
        NSLog(@"请求成功,%@", json);
    } fail:^(NSString *msg) {
        NSLog(@"%@", msg);
    }];
}

//block作为函数参数-1
//有参数,有返回值的block作为参数
-(void)test3:(NSString* (^)(NSString * name))block {
    NSString * newName = NSStringFromSelector(_cmd);
    NSLog(@"%@", block(newName));
}
//block作为函数参数-2
//有参数,无返回值的block作为参数
-(void)test3_1: (void (^)(NSString * name))block {
    NSString * myName = @"丹顶鹤";
    block(myName);
}

/**
 block作为函数参数-3
 无参数,有返回值的block作为参数
 */
-(void)test3_2: (int (^)(void))blcok {
    NSLog(@"我是test3_2");
    int a = blcok();
    NSLog(@"我是block的返回值哟, a = %d", a);
}

/**
block作为函数参数-5
函数也有返回值
*/
-(NSArray *)test3_3: (NSArray* (^)(NSArray* array))block {
    NSArray * myArray = @[@1,@2,@5,@8,@5,@7,@9,@7,@2,@1];
    return block(myArray);
}

/**
 block作为函数参数-4
 实际应用:模拟网络请求
 */
-(void)loadDataSuccess: (SuccessBlock)success fail:(FailBlock)fail {
    NSDictionary * json = @{@"num":@1,
                            @"name":@"小明",
                            @"age":@18,
                            @"phone":@"12345678922",
                            @"email":@"6666666@163.com"};
    int code = 200;
    if (code == 200) {
        success(json, code);
    } else {
        fail(@"请求失败了");
    }
}

打印结果:


image.png
(4)Block作为函数返回值;
//调用test5: block作为函数返回值
-(void)test6 {
    //有返回值,无参数的block作为函数返回值
    NSString* (^block)(void) = [self test5_1];
    NSLog(@"%@", block());
    
    //有返回值,有参数的block作为函数返回值
    NSString * (^block2)(NSString * name) = [self test5_2:@"小花花"];
    NSString * bReturn = block2(@"小点点");
    NSLog(@"最终的值: %@", bReturn);
}

//有返回值,无参数的block作为函数返回值
-(NSString* (^)(void))test5_1 {
    NSString * (^block)(void) = ^ NSString* {
        return @"我们都是好孩子";
    };
    return block;
}

//有返回值,有参数的block作为函数返回值
-(NSString* (^)(NSString* name))test5_2: (NSString *)testName {
    NSString * (^block)(NSString * name) = ^ NSString* (NSString* name) {
        return [NSString stringWithFormat:@"block的参数:%@",name];
    };
    NSLog(@"函数的参数: %@",testName);
    return block;
}

打印结果:


image.png
(5)Block中使用局部变量和全局变量;
@interface BlockTestViewController ()

@property(nonatomic, copy)SumBlock block7;
@property(nonatomic, assign)int num;

@end
-(void)test7 {
    //1.使用局部变量
    //要想在block内部使用局部变量,需要在局部变量前面加上__block
    __block int a = 100;
    int b = 20;
    void (^block)(void) = ^{
        NSLog(@"a = %d, b = %d", a,b);
        a = 200;
    };
    block();
    NSLog(@"after a = %d, b = %d", a,b);
    
    //2.使用全局变量
    self.num = 10;
    double (^block1) (void) = ^{
        self.num = 20;
        double t = self.num * 3.14;
        return t;
    };
    //使用block1
    double r = block1();
    NSLog(@"self.num = %d, r = %.2f", self.num, r);
    
}
- (void)dealloc
{
    NSLog(@"%@销毁了",NSStringFromClass(self.class));
}

打印结果:


image.png
2.Block在项目中常用案例
(1)页面传值;

新建两个VC,一个是BlockAController,一个是BlockBController,如下图:


image.png

BlockAController代码如下:

//
//  BlockAController.h
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "BaseViewController.h"

NS_ASSUME_NONNULL_BEGIN

@interface BlockAController : BaseViewController

@end

NS_ASSUME_NONNULL_END

//
//  BlockAController.m
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import "BlockAController.h"
#import "BlockBController.h"

@interface BlockAController ()<BlockBControllerDelegate>
@property (weak, nonatomic) IBOutlet UITextField *delegateValue;
@property (weak, nonatomic) IBOutlet UITextField *blockValue;

@end

@implementation BlockAController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (IBAction)gotoB:(id)sender {
    
    BlockBController * bVC = [BlockBController instance];
    bVC.delegate = self;
    __weak BlockAController * weakSelf = self;
    bVC.block1 = ^(NSString * name) {
        self.blockValue.text = name;
    };
    [self.navigationController pushViewController:bVC animated:YES];
    
}

#pragma mark - BlockBControllerDelegate
- (void)delegateB:(NSString *)name {
    self.delegateValue.text = name;
}

#pragma mark -
- (void)dealloc {
    
    NSLog(@"%@销毁了",NSStringFromClass([self class]));
}

@end

BlockBController.h

//
//  BlockBController.h
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "BaseViewController.h"

NS_ASSUME_NONNULL_BEGIN

@protocol BlockBControllerDelegate <NSObject>
//代理传值
-(void)delegateB: (NSString *)name;
@end

typedef void (^BlockType)(NSString * name);

@interface BlockBController : BaseViewController

@property(nonatomic, weak)id<BlockBControllerDelegate>delegate;

@property(nonatomic, copy)BlockType block1;

@end

NS_ASSUME_NONNULL_END

BlockBController.m

//
//  BlockBController.m
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import "BlockBController.h"

@interface BlockBController ()
@property (weak, nonatomic) IBOutlet UITextField *input;

@end

@implementation BlockBController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (IBAction)OKBtnClick:(id)sender {
    
    if (_input.text.length > 0) {
        
        if ([self.delegate respondsToSelector:@selector(delegateB:)]) {
            [self.delegate delegateB:_input.text];
            
        }
        
        if (self.block1) {
            self.block1(_input.text);
        }
        [self.navigationController popViewControllerAnimated:YES];
    } else {
        NSLog(@"没有值");
    }
    
}

#pragma mark -
- (void)dealloc {
    NSLog(@"%@销毁了",NSStringFromClass([self class]));
}


@end

为了少些重复代码,新建一个baseVC,如下:

//
//  BaseViewController.h
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BaseViewController : UIViewController

+(instancetype)instance;

@end

NS_ASSUME_NONNULL_END

//
//  BaseViewController.m
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import "BaseViewController.h"

@interface BaseViewController ()

@end

@implementation BaseViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

+(instancetype)instance {
    UIStoryboard * storyB = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    BaseViewController * vc = [storyB instantiateViewControllerWithIdentifier:NSStringFromClass([self class])];
    return vc;
}

@end

A,B在Storyboard中的页面设计如下图


image.png

运行结果:


页面传值.gif
(2)代替代理使用;

我们在使用TableView的时候,往往会在cell中进行一些点击操作,然后把值传回给VC,进行一系列操作。这种需求,使用代理和Block都是可以实现的。我们使用Block实现以下看看。
我们新建一个VC: BlockCController

//
//  BlockCController.h
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "BaseViewController.h"

NS_ASSUME_NONNULL_BEGIN

@interface BlockCController : BaseViewController

@end

NS_ASSUME_NONNULL_END

//
//  BlockCController.m
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import "BlockCController.h"
#import "BlockCCell.h"

@interface BlockCController ()<UITableViewDataSource, UITableViewDelegate>

@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, strong)NSArray * datas;

@end

@implementation BlockCController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setup];
    [self setupData];
}

-(void)setup {
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
}

-(void)setupData {
    NSArray * contents = @[@"床前明月光", @"疑是地上霜", @"举头望明月", @"低头思故乡"];
    NSMutableArray * temp = [NSMutableArray array];
    for (int i = 0; i < 20; i++) {
        NSMutableDictionary * dict = [NSMutableDictionary dictionary];
        dict[@"num"] = [NSString stringWithFormat:@"%d", I];
        NSString * str = contents[i%4];
        dict[@"content"] = str;
        [temp addObject:dict];
    }
    self.datas = temp;
}

#pragma mark - UITableViewDataSource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.datas.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    BlockCCell * cell = [BlockCCell cellWithTableView:tableView];
    NSDictionary * dict = self.datas[indexPath.row];
    [cell set:dict];
    cell.clickBlock = ^ (NSDictionary * dict) {
        NSLog(@"VC中,block实现,第%@行, 内容:%@", dict[@"num"], dict[@"content"]);
    };
    return cell;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 100;
}

#pragma mark - UITableViewDelegate


#pragma mark - lazy
-(NSArray *)datas {
    if (!_datas) {
        _datas = [NSArray array];
    }
    return _datas;
}


@end

cell:

//
//  BlockCCell.h
//  Test
//
//  Created by wenhuanhuan on 2020/6/29.
//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

typedef void (^BlockAction)(NSDictionary *);

@interface BlockCCell : UITableViewCell

@property(nonatomic, copy)BlockAction clickBlock;

+(instancetype)cellWithTableView:(UITableView *)tableView;
-(void)set: (NSDictionary *)dict;

@end

NS_ASSUME_NONNULL_END

//
//  BlockCCell.m
//  Test
//
//  Created by wenhuanhuan on 2020/6/29.
//  Copyright © 2020 weiman. All rights reserved.
//

#import "BlockCCell.h"

@interface BlockCCell()

@property (weak, nonatomic) IBOutlet UILabel *numLabel;
@property (weak, nonatomic) IBOutlet UILabel *contentLabel;
@property (weak, nonatomic) IBOutlet UIButton *clickBtn;

@property (nonatomic, strong)NSDictionary * dict;

@end

@implementation BlockCCell

+(instancetype)cellWithTableView:(UITableView *)tableView {
    NSString * identifier = @"BlockCCell";
    BlockCCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    return cell;
}

-(void)awakeFromNib {
    [super awakeFromNib];
    self.clickBtn.layer.masksToBounds = YES;
    self.clickBtn.layer.cornerRadius = 5;
}

-(void)set: (NSDictionary *)dict {
    self.numLabel.text = dict[@"num"];
    self.contentLabel.text = dict[@"content"];
    self.dict = dict;
}

- (IBAction)clickAction:(id)sender {
    NSLog(@"cell, 点击了 %@ 行", self.numLabel.text);
    if (self.clickBlock) {
        self.clickBlock(self.dict);
    }
}


@end

运行结果:


image.png image.png
(3)作为参数传值;

要说Block作为参数进行传值,应用最多的应该是在网络请求的时候啦。下面简单模拟一下:

//请求成功
typedef void (^SuccessBlock)(NSDictionary* json, int code);
//请求失败
typedef void (^FailBlock)(NSString* msg);


-(void)loadDataSuccess: (SuccessBlock)success fail:(FailBlock)fail {
    NSDictionary * json = @{@"num":@1,
                            @"name":@"小明",
                            @"age":@18,
                            @"phone":@"12345678922",
                            @"email":@"6666666@163.com"};
    int code = 200;
    if (code == 200) {
        success(json, code);
    } else {
        fail(@"请求失败了");
    }
}

使用:

[self loadDataSuccess:^(NSDictionary *json, int code) {
        NSLog(@"请求成功,%@", json);
    } fail:^(NSString *msg) {
        NSLog(@"%@", msg);
    }];
3.Block的常见问题
(1)循环引用问题产生以及解决方法;

我们再新建一个VC:LastPageViewController,代码如下:

//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "BaseViewController.h"

NS_ASSUME_NONNULL_BEGIN

@interface LastPageViewController : BaseViewController


@end

NS_ASSUME_NONNULL_END

//
//  LastPageViewController.m
//  Test
//
//  Created by wenhuanhuan on 2020/6/28.
//  Copyright © 2020 weiman. All rights reserved.
//

#import "LastPageViewController.h"
#import "LastCell.h"

#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height

@interface LastPageViewController ()<UITableViewDataSource, UITableViewDelegate>
@property(nonatomic,strong)UITableView *TAB;
@end

@implementation LastPageViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.TAB];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return 10;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    __weak LastPageViewController * weakSelf = self;
    LastCell *cell = [LastCell loadLastWithTableView:tableView];
    [cell set:[NSString stringWithFormat:@"%ld", indexPath.row]];
    cell.loadCell = ^(NSString * _Nonnull name) {
        NSLog(@"点击row = %@, 退出了", name);
        [weakSelf.navigationController popViewControllerAnimated:YES];
    };
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 60;
}

-(UITableView *)TAB{

    if (!_TAB) {
        _TAB = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight-10) style:(UITableViewStylePlain)];
        _TAB.delegate = self;
        _TAB.dataSource = self;
    }
    return _TAB;
}

-(void)dealloc {
    NSLog(@"%@销毁了",NSStringFromClass([self class]));
}
@end

cell:

LastCell.h

//  Copyright © 2020 weiman. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface LastCell : UITableViewCell

@property(nonatomic,copy)void(^loadCell)(NSString *name);

+ (instancetype)loadLastWithTableView:(UITableView *)tableView;
-(void)set:(NSString *)name;

@end

NS_ASSUME_NONNULL_END

LastCell.m

//
//  LastCell.m
//  Copyright © 2020 weiman. All rights reserved.
//

#import "LastCell.h"

@interface LastCell()

@property(nonatomic,strong)UIButton *btnClick;

@end

@implementation LastCell

+(instancetype)loadLastWithTableView:(UITableView *)tableView{
    
    static NSString *ID = @"LastCell";
    LastCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    if (!cell) {
        cell = [[LastCell alloc]initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:ID];
    }
    return cell;
}
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        
        [self.contentView addSubview:self.btnClick];
        self.btnClick.frame = CGRectMake(20, 10, 100, 40);
    }
    return self;
}

-(void)set:(NSString *)name {
    [self.btnClick setTitle:name forState:UIControlStateNormal];
}

- (void)btnClickAction{

    if (self.loadCell) {
        self.loadCell(self.btnClick.titleLabel.text);
    }

}
-(UIButton *)btnClick{

    if (!_btnClick) {

        _btnClick = [UIButton buttonWithType:UIButtonTypeCustom];
        _btnClick.backgroundColor = [UIColor redColor];
         
        [_btnClick addTarget:self action:@selector(btnClickAction) forControlEvents:(UIControlEventTouchUpInside)];
    }
    return _btnClick;
}
@end

运行:


image.png image.png

我们先把block修饰符改成Strong,看看会不会有问题


image.png

运行结果:


image.png

我们发现没有问题,VC依然是可以销毁的。

我们再改成weak试试:


image.png

运行结果:


image.png

崩溃了。

为什么会这样呢?我们来分析一下。
首先看看使用Strong修饰Block,我们的引用情况如下图:


image.png

从图中可以看出,并不会引起循环引用的情况,没有闭环。

再来看看使用weak为什么崩溃?


image.png

一下就明白了,我们在使用Block的时候,可能已经释放掉了,当然会引起崩溃了。

再看看copy,是把Block从栈上copy到堆上,由开发者自己管理释放,所以也不会有问题,这个我们后面详细说。


image.png
什么时候会引起循环引用呢?

在此例当中,当我们在VC中,没有使用weakSelf的时候,就会引起循环引用,我们先看图。


image.png

代码中试试:


image.png

运行:


image.png

我们发现,LastPageViewController没有被释放,引起了内存泄露。我们在使用Block的时候,一定要注意。

(2)崩溃问题;

block和代理一样,如果没有实现方法,就会出现崩溃。
如下图,我们注释掉block的实现:


image.png

再次运行,出现了崩溃


image.png
所以,使用block的时候和代理一样,都要判断是否实现了,如下图:
image.png

这一点一定要注意。

4.Block内存机制
(1)iOS五大内存区

说到内存,就要先了解下内存的五大区:


image.png

分别简单介绍下。

  • 堆区:程序员管理,优点:灵活方便,缺点:效率略低。
  • 栈区:系统管理,优点:快速高效,缺点:有限制,数据不灵活(先进后出)。通常存放:形参、局部变量。
  • 方法区:存放方法(函数)的二进制代码。
  • 静态区(全局区):系统管理,通常存放:全局变量和静态变量。
    注:全局区又可分为未初始化全局区: .bss段和初始化全局区:data段。 举例:int a;未初始化的。int a = 10;已初始化的。
  • 常量区:系统管理,通常存放:常量字符串。
    借用网上一小段C代码来看看程序是如何存放的。


    image.png

了解了iOS的内存分布,那么Block是如何存放的呢?

(2)Block的存放

Block根据存放的区域,分为三种:栈区Block,堆区Block,全局区Block。

注意:在ARC和MRC下,同样的block代码,可能会在不同的区域,因为ARC下,系统会为我们把栈上的block拷贝到堆上一份。下面我们分别试一试。

MRC下:

我们首先把当前的VC的ARC关闭,开启MRC模式,如下图:


image.png
  • 全局区Block

什么都不访问的Block,在全局区。

- (IBAction)test1:(id)sender {
    
    void (^block) (void) = ^{
        NSLog(@"这是一个block,什么都没调用");
    };
    NSLog(@"%@", block);
    //调用
    block();
    
    printf("\n\n");
}

打印结果:


image.png

访问静态全局变量的Block在全局区

static NSString * name = @"Xcode";
//访问静态全局变量的Block:全局区
- (IBAction)test4:(id)sender {
    
    void (^block) (void) = ^{
        NSLog(@"block 静态全局变量 name = %@", name);
    };
    NSLog(@"%@", block);
    block();
    
    printf("\n\n");
}

打印:


image.png

本身是全局变量的Block, 被赋值的block如果是全局block,全局变量block也在全局区。

image.png
/*本身是全局变量的Block, 被赋值的block如果是全局block,
全局变量block也在全局区
*/
- (IBAction)test5:(id)sender {
    
//全局区block
    void (^block)(void) = ^{
        NSLog(@"哈哈h哈哈");
    };
    
    NSLog(@"myBlock赋值之前:myBlock = %@, block = %@", self.myBlock, block);
    self.myBlock = block;
    NSLog(@"myBlock赋值之后:myBlock = %@, block = %@", self.myBlock, block);
    
    block();
    self.myBlock();
    NSLog(@"调用之后:myBlock = %@, block = %@", self.myBlock, block);
}

打印:


image.png
  • 栈区Block
    如果block内部访问了外部局部变量,那么在MRC下,这个block存储在栈区。
//访问外部局部变量的Block:MRC栈区,ARC堆区
- (IBAction)test2:(id)sender {
    
    int a = 10;
    void (^block)(void) = ^{
        NSLog(@"block 外部局部变量,a = %d", a);
    };
    NSLog(@"%@", block);
    block();
    
    __block int b = a;
       void (^block2)(void) = ^{
           b = 20;
           NSLog(@"block2 改变外部局部变量,b = %d", b);
       };
       NSLog(@"%@", block2);
       block2();
    
    printf("\n\n");
}

打印结果:


image.png

如果block内部访问了外部的全局变量,在MRC下,这个block存储于栈区。

//访问外部全局变量的Block:MRC栈区,ARC堆区
- (IBAction)test3:(id)sender {
   
    void (^block) (void) = ^{
        self.num = 300;
        NSLog(@"block 全局变量 num = %d", self.num);
    };
    NSLog(@"%@", block);
    block();
    
    printf("\n\n");
}

打印结果:


image.png
  • 堆区Block

如果被赋值的block是在栈区,全局变量block使用copy修饰,会在堆区拷贝一份,成为堆区block

//全局block使用copy修饰,对于在栈区的block,赋值给全局copy修饰的block之后,全局block在堆区拷贝了一份。
- (IBAction)test6:(id)sender {
    
    int a = 100000;
    void (^block)(void) = ^{
        NSLog(@"block 外部局部变量,a = %d", a);
    };
    NSLog(@"赋值之前:blockTest = %@, block = %@", self.blockTest, block);
    self.blockTest = block;
    NSLog(@"赋值之后:blockTest = %@, block = %@", self.blockTest, block);
    
    block();
}

打印结果:


image.png

ARC下

看完了MRC下的block存储,我们再看看ARC下的Block存储吧。
先把MRC开启去掉,如下图:


image.png

我们再来看看上面的例子的打印结果。
代码不再粘贴,就看看打印结果。

1.什么都不访问的block:全局区,与MRC同。


image.png

2.访问静态全局变量:全局区,与MRC同。


image.png

3.访问局部变量, MRC:栈区, ARC:堆区


image.png

4.访问全局变量,MRC:栈区,ARC:堆区


image.png

5.对于全局变量的Block,与被赋值的block在什么区域有关。例如:
self.myBlock = blockTest;
如果blockTest是全局区block,那么赋值以后,self.myBlock也是全局区block。
如果blockTest是堆区block,那么copy修饰的self.myBlock在堆区拷贝一份,成为堆区block。
如果blockTest是栈区block,那么copy修饰的self.myBlock在堆区拷贝一份,成为堆区block。

image.png image.png

不知道大家有没有注意,在MRC下的栈区Block,到了ARC就变成了堆区Block,为什么呢?
因为在ARC上,当系统捕获到自动变量的时候,就自动的进行一次copy操作,把栈上的Block拷贝到了堆上,就变成了堆区Block。

ARC下没有栈区Block吗?不,有的,当Block作为参数的时候,访问外部局部变量,这个Block就是栈区Block,如

//block作为参数
-(void)test8:(void (^)(void))block {
    NSLog(@"---------%@--------", NSStringFromSelector(_cmd));
    NSLog(@"函数中,block作为参数");
    NSLog(@"%@",block);
    block();
    printf("\n\n");
}

NSLog(@"--访问局部变量的弱引用block");
    __block int static_k = 3;
    __weak void (^block2)(void) = ^{
        static_k++;
    };
    block2();
    NSLog(@"block2 = %@",block2);
    printf("\n\n");
- (void)viewDidLoad {
    [super viewDidLoad];
    
    int age = 18;
    [self test8:^{
        NSLog(@"调用时,block作为参数,访问局部变量,age = %d", age);
    }];
    
}

打印结果:


image.png image.png

前面内容比较多,也很零散,关于Block的内存机制,这里也只是选取了几种常见的例子,我们简单做个总结吧。

image.png
Block为什么要用copy修饰?
在ARC中,Block不一定要用copy修饰,使用strong也是没问题的。
原因:

通过上面的分析我们知道了,在MRC下,Block中访问局部或者全局变量,这个Block存放于栈区,栈区是系统管理的,超过作用于就会被销毁了,所以我们要使用copy修饰,把栈区的Block拷贝到堆区一份,由程序员自己管理,全局共享。所以,在MRC时代,我们使用copy修饰Block。
在ARC中,访问局部变量或者全局变量的Block存储于堆区,系统已经给我们做了一次copy,我们就不需要自己copy一次了。所以,ARC中,使用strong修饰Block也是没问题的。
之所以大家还是使用copy,是因为MRC遗留下来的习惯。

在ARC下,我们把栈区Block赋值给全局变量Block,看看会不会是栈区Block呢?

@property(nonatomic, strong)void (^test2)(void);
//block作为参数
-(void)test8:(void (^)(void))block {
    NSLog(@"---------%@--------", NSStringFromSelector(_cmd));
    NSLog(@"函数中,block作为参数");
    NSLog(@"%@",block);
    block();
    printf("\n\n");
    
    //把参数block赋值给全局变量test2试试
    self.test2 = block;
    self.test2();
    NSLog(@"参数block赋值给全局变量, block: %@, test2: %@", block, self.test2);
    printf("\n\n");
    
}

打印结果:


image.png

再次印证了我们的结论,ARC下,即使用strong修饰Block,把栈区Block赋值给变量,得到的也是堆区Block。

(3)为什么加了_ _block,就可以访问外部的变量了?

我看看一段熟悉的代码:

void blockTest(void);

int main(int argc, const char * argv[]) {
    
    blockTest();
    
    return 0;
}

void blockTest(void) {
    
    int age = 100;
    void (^myBlock)(void) = ^{
        printf("age = %d\n", age);
    };
    myBlock();
}

我们使用终端clang命令,看看生成的c++文件。我们cd到main.mm所在的目录,然后使用
clang -rewrite-objc main.mm
命令,在当前目录下生成main.cpp文件。


image.png

打开文件,由于内容太多,我们搜索我们需要的内容:


image.png

这时,我们尝试修改age的值:


image.png

会报错。我们使用_ _block修饰变量,不在报错。再次clang看看有何不同。


image.png

第三个参数变成了一个指针,_ block修饰的int类型的局部变量也变成了一个指针。我们知道,Block就是一个匿名函数,使用 _block修饰的变量,在block使用的时候,相当于一个引用传递,参数是一个指针。

关于值传递,引用传递,地址传递的扩展知识,篇幅有限,请看我的另一篇文章:https://www.jianshu.com/p/560197c17e48

如果局部变量不用_ _block修饰,相当于值传递,即使在Block中进行了更改,也是对外部无效的。只有加了
_ _block修饰,才是引用传递,在Block内部做的更改才会对外部变量起作用。

5.Block原理

源码地址:
https://github.com/weiman152/PropertyAndBlockTest

相关文章

网友评论

    本文标题:由简入深,洞悉Block,源码亲测

    本文链接:https://www.haomeiwen.com/subject/jugsfktx.html