Block学习笔记

在iOS开发中,Block属于一种比较实用的技巧,它的作用是保存一段代码。Block经常用于传值,或作为参数使用。

BlockDemo链接

Block的基本使用

  • Block是一种特殊的数据类型,用来保存一段代码。Block类似于匿名函数。

  • Block声明

    // 无参声明
    void(^emptyBlock)();
    // 有参声明(第二个小括号里写的是参数的类型,多个参数需要逗号隔开)
    void(^nonEmptyBlock)(NSString *,NSString *);
  • Block定义:3种方式
    // 1.在有参数的情况下,=号后面需要列出参数。
    void(^defineBlock1)(NSString *) = ^(NSString *str){
        NSLog(@"defineBlock1:%@",str);
    };

    // 2.在无参的情况下,=号后面可以隐藏参数
    void(^defineBlock2)() = ^{
        NSLog(@"defineBlock2");
    };

    // 3.=号后面可以列出返回值,也可以隐藏返回值
    // defineBlock3的类型:NSString *(^)(NSString *),实际上就是Block的声明去掉Block的名字
    NSString *(^defineBlock3)(NSString *) = ^NSString *(NSString *str){
        NSLog(@"defineBlock3:%@",str);
        return str;
    };
  • Block调用
    defineBlock1(@"我是1");
    defineBlock2();
    defineBlock3(@"我是3");
  • Block快捷方式:输入inlineBlock
  // block快捷方式(新手福利): 输入inlineBlock,系统会给出block模板
    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
        <#statements#>
    };
  • 定义Block属性:2种方式
// 1.怎么声明Block就怎么定义Block属性;
@property (nonatomic, strong) void(^blockName)();

// 2.定义一个Block类型,然后用这个Block类型声明Block。
// BlockType:Block的类型。
typedef void(^BlockType)();
// blockName:使用Block时使用的名字,相当于方法名……
@property (nonatomic, strong) BlockType blockName;
  • Block在MRC下的内存管理

    只要block没有引用外部局部变量,block放在全局区;只要block引用外部局部变量,block放在栈里面。定义block属性,只能使用copy,不能使用retain;当使用copy时,block在堆里,而使用retain时,block还是在栈里。

  • Block在ARC下的内存管理

    只要block没有引用外部局部变量,block放在全局区;只要block引用外部局部变量,block放在堆里面(这里和MRC下不同,MRC下放在栈里面);block在ARC下使用strong,最好不要使用copy,copy会多做许多事情。

  • Block循环引用

    判断自身是否释放的方法:在-dealloc方法中打印下字符串,看是否会进入该方法。

- (void)dealloc
{
    NSLog(@"ViewController已销毁");
}

下面写法会造成循环引用,因为Block会对里面所有强指针变量都强引用一次。


    void(^block)() = ^{
        NSLog(@"%@",self);
    };

    block();
    __weak typeof(self) weakSelf = self;
    _block = ^{
       NSLog(@"%@",weakSelf);
    };


  • Block变量传值

如果是局部变量,Block是值传递
int i = 3;
void(^block)() = ^{
    NSLog(@"%d",i);
};
i = 4;
block();
// 打印结果是3

如果是静态变量(static修饰)、全局变量、__block修饰的变量,Block都是引用传递

__block int i = 3;
void(^block)() = ^{
    NSLog(@"%d",i);
};
i = 4;
block();
// 打印结果是4

Block使用场景:方法间传值

// 定义block属性
@property (nonatomic, strong) void(^blockName)();

// 两个block方法
- (void)test1
{
    self.blockName = ^{
        NSLog(@"在一个方法中定义,在另一个方法中使用");
    };
}

- (void)test2
{
    self.blockName();
}

// 调用block方法
    [self test1];    
    [self test2];

上面的Demo演示了如何利用Block在方法间传值,但是上面的写法没有任何意义。我们完全可以在test1方法中调用test2方法,block使问题复杂化了。
下面我要演示一个Demo,设置一个tableView,点击tableView的不同行,执行不同的动作。

// viewController.m
#import "ViewController.h"
#import "CellItem.h"

#define screen_width [UIScreen mainScreen].bounds.size.width
#define screen_height [UIScreen mainScreen].bounds.size.height

@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>

@property (nonatomic, weak) UITableView *tableView;

@property (nonatomic, strong) NSArray *cellItems;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.创建tableView
    [self setupTableView];

    // 2.编写tableView的代理方法和数据源方法
    // 思路:点击cell,执行特定的动作。可读性较差的写法就是无限嵌套 if else 来完成,但是我们可以封装一个cellItem对象,并且将要执行的动作保存在一个Block之中,这样写会大大增强代码的可读性。
    // 额,这个方法名,,,算了,懒得改了
    CellItem *item1 = [CellItem cellWithTitle:@"执行动作1"];
    item1.block = ^{
        NSLog(@"这里执行动作1");
    };

    CellItem *item2 = [CellItem cellWithTitle:@"执行动作2"];
    item2.block = ^{
        NSLog(@"这里执行动作2");
    };

    CellItem *item3 = [CellItem cellWithTitle:@"执行动作3"];
    item3.block = ^{
        NSLog(@"这里执行动作3");
    };

    _cellItems = @[item1,item2,item3];

}


#pragma mark - Demo2
- (void)setupTableView
{
    UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 20, screen_width, screen_height - 20) style:UITableViewStylePlain];
    tableView.delegate = self;
    tableView.dataSource = self;
    [self.view addSubview:tableView];

    _tableView = tableView;
}

#pragma mark ====== UITableViewDataSource & UITableViewDelegate ======

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


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *cellID = @"cellID";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
    }

    // 可读性差的写法
//    if (indexPath.row == 0) {
//        cell.textLabel.text = @"执行动作1";
//    } else if (indexPath.row == 1) {
//        cell.textLabel.text = @"执行动作2";
//    } else if (indexPath.row == 2) {
//        cell.textLabel.text = @"执行动作3";
//    }

    CellItem * item = _cellItems[indexPath.row];
    cell.textLabel.text = item.title;

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 可读性差的写法
    //    if (indexPath.row == 0) {
    //    } else if (indexPath.row == 1) {
    //    } else if (indexPath.row == 2) {
    //    }

    CellItem *item = _cellItems[indexPath.row];
    item.block();
}

// CellItem.h

#import <Foundation/Foundation.h>

@interface CellItem : NSObject

@property (nonatomic, strong) NSString *title;

// block的属性定义,按照block的声明去写就好。inlineBlock黑魔法可以帮助你回忆block的书写格式,妈妈再也不用担心我不会写block了!
@property (nonatomic, strong) void(^block)();

// 初始化Item的方法
+ (instancetype)cellWithTitle:(NSString *)title;

@end
// CellItem.m

#import "CellItem.h"

@implementation CellItem

+ (instancetype)cellWithTitle:(NSString *)title
{
    CellItem *item = [[CellItem alloc] init];

    item.title = title;

    return item;
}

@end

Block使用场景:逆向传值

逆向传值的方法有代理传值、Block传值等,其实思路都是大同小异。我们假设有两个VC:A和B,A跳转到B,B返回A时给A传个值。

  • 1.在B添加添加一个Block属性
@property (nonatomic, strong) void(^block)(NSString *);
  • 2.在A界面执行跳转代码,并调用返回值
-(void)touchesBegan:(NSSet<UITouch *> *)touches
          withEvent:(UIEvent *)event
{
    RedViewController *redVc = [[RedViewController alloc] init];
    redVc.view.backgroundColor = [UIColor redColor];

    redVc.block = ^(NSString *value){
        NSLog(@"这是从redVc传递过来的值:%@",value);
    };

    [self presentViewController:redVc animated:YES completion:nil];
}
  • 3.在B界面判断,是否定义Block,如果定义Block,则实现Block并传值。
 if (self.block) {
        self.block(@"我是值");
    }
  • 4.执行返回代码,,即可获取到逆向传递的值。
-(void)touchesBegan:(NSSet<UITouch *> *)touches
          withEvent:(UIEvent *)event
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

Block使用场景:参数使用

方法的参数如果含有 ^ ,则该参数就是个Block参数。
把Block当做参数,并不是马上就调用Block,何时调用由方法的内部决定。
何时需要使用Block当参数:当做的事情由外部决定,而执行时间(顺序)由内部决定。

// Person.h
- (void)wantToEat:(void(^)(NSString *))block;

// Person.m
- (void)wantToEat:(void (^)(NSString *))block
{
    // 在这里,我们调整了Block的执行顺序,他可以在log之前执行,也可以在log之后执行,取决于内部实现。
    NSLog(@"我饿了!");

    if (block) {
        block(@"橘子");
    }
}
// ViewController.m 
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p = [[Person alloc] init];
    [p wantToEat:^(NSString *food) {
        NSLog(@"我想吃%@!",food);
    }];
}

Block使用场景:方法返回值

我们在使用第三方框架Masonry时,看到它的用法:make.center.equalTo(ws.view); 这种 . 语法的形式,其实就是通过使用Block方法返回值来实现的

// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    self.test(@"1").test(@"2");
}

- (ViewController *(^)(NSString *))test
{
    // 第一个return,是return该方法的返回值,一个Block
    return ^ViewController *(NSString *value){

        NSLog(@"%@",value);
        // 第二个return是return Block的返回值。
        return self;
    };
}

未完待续……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值