iOS的Block详解

Block是带有自动变量的匿名函数,是C语言的一个扩充功能。Block本质上也是一个OC对象,内部也有一个isa指针,其内部封装了函数调用以及函数调用环境。

(一)Block的基本使用

1. block声明

//返回值(^block变量名)(参数)
void(^block)();

2. block定义

    //三种方式 = ^(参数){};
    // 第一种:没返回值,没参数
    void(^block1)() = ^{
        NSLog(@"调用了block1");
    };
    
    // 第二种:没返回值,有参数 
    //如果没有参数,参数可以隐藏,如果有参数,定义的时候,必须要写参数,而且必须要有参数变量名
    void(^block2)(int) = ^(int a){
        
    };
    
    // 第三种 有返回值
    //block返回可以省略,不管有没有返回值,都可以省略
    int(^block3)() = ^int{
        return 3;
    };

3. block类型

// block类型:int(^)(NSString *)
    int(^block4)(NSString *) = ^(NSString *name){
        return 2;
    };

4. block调用

block1();

// block快捷方式 inline
//    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
//        <#statements#>
//    };

(二)Block的基本使用

1. 保存代码到模型中

CellItem.h

#import <Foundation/Foundation.h>

@interface CellItem : NSObject

// 设计模型:控件需要展示什么内容,就定义什么属性
@property (nonatomic, strong) NSString *title;

// 保存每个cell做的事情
@property (nonatomic, strong) void(^block)();

+ (instancetype)itemWithTitle:(NSString *)title;

@end

CellItem.m

#import "CellItem.h"

@implementation CellItem
+ (instancetype)itemWithTitle:(NSString *)title
{
    CellItem *item = [[self alloc] init];
    
    item.title = title;
    
    return item;
}
@end

TableViewController.m

#import "TableViewController.h"
#import "CellItem.h"
// 1.tableView展示3个cell,打电话,发短信,发邮件
@interface TableViewController ()
/** 注释 */
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建模型
    CellItem *item1 = [CellItem itemWithTitle:@"打电话"];
    item1.block = ^{
        NSLog(@"打电话");
    };
    CellItem *item2 = [CellItem itemWithTitle:@"发短信"];
    item2.block = ^{
        NSLog(@"发短信");
    };
    CellItem *item3 = [CellItem itemWithTitle:@"发邮件"];
    item3.block = ^{
        NSLog(@"发邮件");
    };
    _items = @[item1,item2,item3];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *ID = @"cell";
    
    // 1.从缓存池取
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
    }
    
    CellItem *item = self.items[indexPath.row];
    cell.textLabel.text = item.title;
    
    return cell;
}

// 点击cell就会调用
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 把要做的事情(代码)保存到模型

    CellItem *item = self.items[indexPath.row];
    
    if (item.block) {
        item.block();
    }    
}


@end

2. block传值

传值:只要能拿到对方就能传值

 顺传:给需要传值的对象,直接定义属性就能传值

 逆传:用代理,block,就是利用block去代替代理

先用代理进行传值:

ModalViewController.h

#import <UIKit/UIKit.h>
@class ModalViewController;
@protocol ModalViewControllerDelegate <NSObject>

@optional
// 设计方法:想要代理做什么事情
- (void)modalViewController:(ModalViewController *)modalVc sendValue:(NSString *)value;

@end

@interface ModalViewController : UIViewController

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

@end

ModalViewController.m

#import "ModalViewController.h"

@interface ModalViewController ()

@end

@implementation ModalViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 传值给ViewController
    if ([_delegate respondsToSelector:@selector(modalViewController:sendValue:)]) {
        [_delegate modalViewController:self sendValue:@"123"];
    }
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

ViewController.m

#import "ViewController.h"
#import "ModalViewController.h"
/*
    传值:1.只要能拿到对方就能传值
 
    顺传:给需要传值的对象,直接定义属性就能传值
    逆传:用代理,block,就是利用block去代替代理
 */

@interface ViewController ()<ModalViewControllerDelegate>

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    ModalViewController *modalVc = [[ModalViewController alloc] init];
    modalVc.view.backgroundColor = [UIColor brownColor];
    modalVc.delegate = self;
    
    // 跳转
    [self presentViewController:modalVc animated:YES completion:nil];
}

#pragma mark - ModalViewControllerDelegate
// 传值给ViewController
- (void)modalViewController:(ModalViewController *)modalVc sendValue:(NSString *)value
{
    NSLog(@"%@",value);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

在用block进行传值:

ModalViewController.h

#import <UIKit/UIKit.h>

@interface ModalViewController : UIViewController


@property (nonatomic, strong) void(^block)(NSString *value);

@end

ModalViewController.m

#import "ModalViewController.h"

@interface ModalViewController ()

@end

@implementation ModalViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (_block) {
        _block(@"123");
    }

}


@end

ViewController.m

#import "ViewController.h"
#import "ModalViewController.h"
/*
 
    传值:1.只要能拿到对方就能传值
 
    顺传:给需要传值的对象,直接定义属性就能传值
    逆传:用代理,block,就是利用block去代替代理
 
 */

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    ModalViewController *modalVc = [[ModalViewController alloc] init];
    modalVc.view.backgroundColor = [UIColor brownColor];
    
    modalVc.block = ^(NSString *value) {
      
        NSLog(@"%@",value);
    };
    
    // 跳转
    [self presentViewController:modalVc animated:YES completion:nil];
}

@end

(三)Block的内存管理

   block是不是一个对象?是一个对象

    如何判断当前文件是MRC,还是ARC

    1.dealloc 能否调用super,只有MRC才能调用super

    2.能否使用retain,release.如果能用就是MRC

    ARC管理原则:只要一个对象没有被强指针修饰就会被销毁,默认局部变量对象都是强指针,存放到堆里面

    MRC了解开发常识:

    1.MRC没有strong,weak,局部变量对象就是相当于基本数据类型

    2.MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值

    MRC:管理block

  •     总结:只要block没有引用外部局部变量,block放在全局区
  •     只要Block引用外部局部变量,block放在栈里面.
  •     block只能使用copy,不能使用retain,使用retain,block还是在栈里面, 当方法结束时, 大括号内的在栈里的block会销毁, 再引用block就会报错。
  •     MRC环境下,在定义block为属性时,使用copy的原因,是把block从栈区拷贝到堆区(深拷贝),因为栈区中的变量出了作用域之后就会被销毁,无法在全局使用,所以应该把栈区的属性拷贝到堆区中全局共享,这样就不会被销毁了。
  • 浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝
    深拷贝:直接拷贝对象到内存中一块区域,然后把新对象的指针指向这块内存
#import "ViewController.h"

/*
    block是不是一个对象?是一个对象
 
    如何判断当前文件是MRC,还是ARC
    1.dealloc 能否调用super,只有MRC才能调用super
    2.能否使用retain,release.如果能用就是MRC
 
    ARC管理原则:只要一个对象没有被强指针修饰就会被销毁,默认局部变量对象都是强指针,存放到堆里面
 
    MRC了解开发常识:1.MRC没有strong,weak,局部变量对象就是相当于基本数据类型
                  2.MRC给成员属性赋值,一定要使用set方法,不能直接访问下划线成员属性赋值
 
    MRC:管理block
            总结:只要block没有引用外部局部变量,block放在全局区
            只要Block引用外部局部变量,block放在栈里面.
            block只能使用copy,不能使用retain,使用retain,block还是在栈里面
 
 */

@interface ViewController ()

@property (nonatomic, copy) void(^block)();

@property (nonatomic, retain) NSString *name;

@end

@implementation ViewController

- (void)setName:(NSString *)name
{
    if (name != _name) {
        [_name release];
        _name = [name retain];
    }
}

- (void)dealloc
{
    [self.name release];
    [super dealloc];
}


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    // 堆 栈 全局区
    
  
    __block int a = 3;
    
    void(^block)() = ^{
        
        NSLog(@"调用block%d",a);
    };
    
    self.block = block;
    
    NSLog(@"%@",self.block);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.block();
    
}

@end

 ARC:管理block

  • 总结:只要block没有引用外部局部变量,block放在全局区
  • 只要block引用外部局部变量,block放在堆里面
  • block使用strong.最好不要使用copy, 使用copy也没问题
  • 在MRC下必须使用self.task = block;给属性赋值,在赋值的时候会调用setter方法,会把栈区的block拷贝到堆区,如果使用_task的方式赋值不会去copy,所以在MRC下属性都用copy修饰
  • 在ARC下可以使用_task,因为ARC下默认属性就是在堆区, MRC不可以使用_task.
@interface ViewController ()

//可以使用strong和copy
@property (nonatomic, strong) void(^block)();
//@property (nonatomic, copy) void(^block)();

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Do any additional setup after loading the view, typically from a nib.
    // 堆 栈 全局区
    
    int a = 3;
    
    void(^block)() = ^{
        
        NSLog(@"%d",a);
        
    };
    
    _block =  block;
    NSLog(@"%@",_block);
  
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _block();
    
    
}

@end

(四)__block的使用

1. 在block的内部,访问外部的变量时,block内部会对外部的变量进行一次拷贝,在block内部操作的是拷贝之后的副本,不会影响外部的变量,这个变量在堆区
2. 在block内部,修改外部变量,是不被允许的
3. 如果非要在block内部修改外部的变量,需要使用__block修饰外部变量
4. 一旦外部的int变量(在栈区)被__block标记了,如果block内部又修改了这个变量,那么这个变量的地址会永久的被修改在堆区 
5.  如果外部变量是NSMutableString这样本身就在堆区的,在block内部修改就不会报错
5. 为什么在block的内部不能修改外部的变量?  因为block一般是需要传递给另外一个类里面,block内部的一些变量不能存储在栈区,需要存在堆区,不然数据就容易丢失,这就是使用__block修饰的原因,这样传输数据的时候,数据就不会丢失


(五)Block的循环引用

循环引用:我引用你,你也引用,就会造成循环引用,双方都不会被销毁,导致内存泄露问题 

block造成循环利用:Block会对里面所有强指针变量都强引用一次

//会报错 循环引用的错误
 _block = ^{

         NSLog(@"%@",self);
    };
    
    _block();

循环引用原因图解: 

      

解决循环引用方法:

用__weak typeof修饰self

__weak typeof(self) weakSelf = self;
    
    _block = ^{

           NSLog(@"%@",weakSelf);
        
    };
    
    _block();

比较复杂的情况,比如block中有使用block,在调用self的时候又有延迟操作:

错误情况:延迟输出的waekSelf已经提前销毁,输出的为null

__weak typeof(self) weakSelf = self;
    
    _block = ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //输出的weakSelf为null
             NSLog(@"%@", weakSelf);
            
        });
        
    };
    
    _block();

正确的修改方式: 在内部在用__strong typeof修饰weakSelf

__weak typeof(self) weakSelf = self;
    
    _block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //此时输出不为null
             NSLog(@"%@",strongSelf);
            
        });
        
    };
    
    _block();

正确的图解:


(六)Block的变量传递

如果是局部变量,Block是值传递

int a = 3;
    
// 如果是局部变量,Block是值传递
    
 void(^block)() = ^{
        //此时输出的a为3
        NSLog(@"%d",a);
        
  };
    
  a = 5;
    
  block();

如果是静态变量,全局变量,__block修饰的变量,block都是指针传递。

static int a = 3; 
//__block int a = 3;
    
    // 如果是局部变量,Block是值传递
    
    // 如果是静态变量,全局变量,__block修饰的变量,block都是指针传递
    
    void(^block)() = ^{
        //此时a为5
        NSLog(@"%d",a);
        
    };
    
    a = 5;
    
    block();

(七)Block的变开发使用场景

  • 怎么区分参数是block,就看有没有^,只要有^.把block当做参数
  •  把block当做参数,并不是马上就调用Block,什么时候调用,由方法内部决定
  •  什么时候需要把block当做参数去使用:做的事情由外界决定,但是什么时候做由内部决定.

1. 设计一个计算器: 

CacultorManager.h

#import <Foundation/Foundation.h>

@interface CacultorManager : NSObject

@property (nonatomic, assign) NSInteger result;

// 计算
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;

@end

CacultorManager.m

#import "CacultorManager.h"

@implementation CacultorManager
- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock
{
    
    if (cacultorBlock) {
      _result =  cacultorBlock(_result);
    }
}

@end

ViewController.m

#import "ViewController.h"
#import "CacultorManager.h"

// 怎么区分参数是block,就看有没有^,只要有^.把block当做参数
// 把block当做参数,并不是马上就调用Block,什么时候调用,由方法内部决定
// 什么时候需要把block当做参数去使用:做的事情由外界决定,但是什么时候做由内部决定.

/*
    需求:封装一个计算器,提供一个计算方法,怎么计算由外界决定,什么时候计算由内部决定.
 
 */


@interface ViewController ()

@end

@implementation ViewController
- (void)test:(int)a
{
    
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self test:3];
    
    // 创建计算器管理者
    CacultorManager *mgr = [[CacultorManager alloc] init];
    [mgr cacultor:^(NSInteger result){
        result += 5;
        result += 6;
        result *= 2;
        return result;
    }];
    
    NSLog(@"%ld",mgr.result);
    
    
//    [UIView animateWithDuration:0 animations:^{
//        
//    }];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

2. Block做返回值

链式编程思想:把所有的语句用.号连接起来,好处:可读性非常好

CalculatorManager.h

#import <Foundation/Foundation.h>

@interface CalculatorManager : NSObject

@property (nonatomic, assign) int result;

//- (CalculatorManager *)add:(int)value;

- (CalculatorManager *(^)(int))add;

@end

CalculatorManager.m

#import "CalculatorManager.h"

@implementation CalculatorManager
- (CalculatorManager *(^)(int))add
{
    return ^(int value){
        _result += value;
        
        return self;
    };
}

- (CalculatorManager *)add:(int)value
{
    _result += value;
    
    return self;
}

@end

ViewController.m

#import "ViewController.h"
#import "CalculatorManager.h"

@interface ViewController ()

@end

@implementation ViewController
/*
    链式编程思想:把所有的语句用.号连接起来,好处:可读性非常好
 */

/*
    需求:封装一个计算器,提供一个加号方法
 */

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    CalculatorManager *mgr = [[CalculatorManager alloc] init];

//    [[[[[mgr add:5] add:5] add:5] add:6] add:7];
   
    mgr.add(5).add(5).add(5).add(5);
    
    NSLog(@"%d",mgr.result);
    
    
//    void(^block)() = ^{
//        NSLog(@"调用了block");
//    }();//会直接调用block
    self.test();
}

- (void(^)())test
{
    return ^{
        NSLog(@"调用了block");
    };
}

@end

猜你喜欢

转载自blog.csdn.net/weixin_42433480/article/details/89055054
今日推荐