block 用法详解及注意事项

block介绍

block 也叫做语法块或者闭包或者匿名函数。通常来说,block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。而在很多框架中,block越来越经常被用作回调函数,取代传统的回调方式。自己平时在开发中也比较喜欢用 block 进行开发, 今天就来说一说 block.

优点

用block作为回调函数,可以使得程序员在写代码更顺畅,不用中途跑到另一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比较合适。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。
另一个好处,就是采用block作为回调,可以直接访问局部变量。比如我要在一批用户中修改一个用户的name,修改完成后通过回调更新对应用户的单元格UI。这时候我需要知道对应用户单元格的index,如果采用传统回调方式,要嘛需要将index带过去,回调时再回传过来;要嘛通过外部作用域记录当前操作单元格的index(这限制了一次只能修改一个用户的name);要嘛遍历找到对应用户。而使用block,则可以直接访问单元格的index。

block 用法详解

定义block

//block语法块 其实就是一个匿名函数,但是他和其他函数不一样,他可以嵌套定义,如果想使用 block 语法块就需要用对应数据类型的 block 变量取接收,使用变量进行调用
   void (^sayBlock)() = ^ void (){
       NSLog(@"没有名字的函数");
   };
   //使用 block变量
   sayBlock();

PS: 1.数据类型为 void (^)()
2.变量名位 sayBlock
3.初值为^ void(){NSLog(@“没有名字的函数”);}

block 简单用法

- (void)viewDidLoad
{
    [super viewDidLoad];
    //(1)定义无参无返回值的Block
    void (^printBlock)() = ^(){
        printf("no number");
    };
    printBlock();
    printBlock(9);
     
    int mutiplier = 7;
    //(3)定义名为myBlock的代码块,返回值类型为int
    int (^myBlock)(int) = ^(int num){
        return num*mutiplier;
    }
    //使用定义的myBlock
    int newMutiplier = myBlock(3);
    printf("newMutiplier is %d",myBlock(3));
}
//定义在-viewDidLoad方法外部
//(2)定义一个有参数,没有返回值的Block
void (^printNumBlock)(int) = ^(int num){
    printf("int number is %d",num);
};

定义Block变量,就相当于定义了一个函数。但是区别也很明显,因为函数肯定是在-viewDidLoad方法外面定义,而Block变量定义在了viewDidLoad方法内部。当然,我们也可以把Block定义在-viewDidLoad方法外部,例如上面的代码块printNumBlock的定义,就在-viewDidLoad外面。

再来看看上面代码运行的顺序问题,以第(3)个myBlock距离来说,在定义的地方,并不会执行Block{}内部的代码,而在myBlock(3)调用之后才会执行其中的代码,这跟函数的理解其实差不多,就是只要在调用Block(函数)的时候才会执行Block体内(函数体内)的代码。
根据上面的简单代码得到总结:

(1)在类中,定义一个Block变量,就像定义一个函数;
(2)Block可以定义在方法内部,也可以定义在方法外部;
(3)只有调用Block时候,才会执行其{}体内的代码;
(PS:关于第(2)条,定义在方法外部的Block,其实就是文件级别的全局变量)

block 页面传值

场景如此: 第一个页面中有一个 UILabel , 第二个页面中有个一 UITextField, 在第二个页面中输入文字, 传入至第一个页面中, 更新至第一个页面的 Label 上.
即页面传值的从后向前传值, 我认为页面传值 block 还是性能较好, 而且代码不繁杂, 代码清晰. 或者使用通知中心传值也很方便, 但使用代理来做的话就代码就繁杂一些了.

ViewController :

@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *showLabel;
- (IBAction)btnPress:(id)sender;
@end

#import "ViewController.h"
#import "SecondViewController.h"

@interface ViewController ()
@property(strong,nonatomic) SecondViewController *vc;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _showLabel.text = @"第一次";
    self.vc = [self.storyboard instantiateViewControllerWithIdentifier:@"second"];
    __weak ViewController *weakSelf = self;
    self.vc.returnTextBlock = ^(NSString *str) {
        weakSelf.showLabel.text = str;
    };
}
- (IBAction)btnPress:(id)sender {
    [self.navigationController pushViewController:self.vc animated:YES];
}
@end

SecondViewController :

typedef void (^ReturnTextBlock)(NSString *str);

@interface SecondViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *inputText;

@property (nonatomic,copy) ReturnTextBlock returnTextBlock;
@end

#import "SecondViewController.h"
@interface SecondViewController ()

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}
-(void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    if (self.returnTextBlock != nil) {
        self.returnTextBlock(self.inputText.text);
    }
}
@end

block 封装回调应用

下面封装了一个类继承于 UIView, 上面有一个按钮, 当点击按钮的时候, 会执行在controller 中的回调, 这时候也会拿到从 TitleView 类里面传出来的数据, 在这里传入的是 UIButton, 你可以根据自己需要进行封装.

TitleView :

@interface TitleView : UIView
-(void)settingTitleViewWithShowMarketListHandle:(void(^)())showMarketListHandle;

@end


#import "TitleView.h"

@interface TitleView ()
@property (nonatomic, copy) void(^showMarketList)(UIButton *button);
@end

@implementation TitleView

-(void)settingTitleViewWithShowMarketListHandle:(void(^)(UIButton *button))showMarketListHandle
{
    self.showMarketList = showMarketListHandle;

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(0, 0, 200, 100);
    [button addTarget:self action:@selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
    button.tag = 888;
    [self addSubview:button];

}

#pragma mark - 私有方法

- (void)clickButton:(UIButton *)button
{
    NSLog(@"点击了");
    _showMarketList(button.tag);
}

@end

ViewController :

TitleView *titleView = [[TitleView alloc] initWithFrame:CGRectMake(0, 0, 300, 100)];
[self.view addSubviews:titleView];
[titleView settingTitleViewWithShowMarketListHandle:^(UIButton *button) {
    NSLog(@"button's tag = %ld",button.tag);
}];

block 注意事项

1.在使用block前需要对block指针做判空处理。
比如在之前例子中的代码:

    if (self.returnTextBlock != nil) {
        self.returnTextBlock(self.inputText.text);
    }

2.在Block的{}体内,是不可以对block外的变量进行更改的,所以需要在 block 外用__block关键字对变量进行修饰, 这样在Block内,就可以修改外部变量了。

//将Block定义在方法内部
    int x = 100;
    void (^sumXAndYBlock)(int) = ^(int y){
    x = x+y;
    printf("new x value is %d",x);
    };
    sumXAndYBlock(50);

3.循环引用的问题, 在使用 block 的时候, 尤其是在 MRC 中, 使用不当会造成 ViewController或者封装类不能正常 dealloc. 比如在controller 的 block中直接使用 self 时, MRC 中没有 release 时,等等.
4.在MRC的编译环境下,在使用block之后要对,block指针做赋空值处理,要先release掉block对象。block作为类对象的成员变量,使用block的人有可能用类对象参与block中的运算而产生上面第3点循环引用的问题。将block赋值为空,是解掉循环引用的重要方法。(不能只在dealloc里面做赋空值操作,这样已经产生的循环引用不会被破坏掉)

 if (data != nil && _sucBlock != NULL) {
        _sucBlock(data);
    }
    //MRC下:要先将[_sucBlock release];(之前copy过)
    _sucBlock = nil; //Importent: 在使用之后将Block赋空值,解引用 !!!

5.block 添加属性时, 声明的关键字应该用 copy, 因为 block在外传时, 需要通过赋值上栈才能传出去, 就可以想到 strong / copy , 但如果用 strong 的时候, 可能会引起循环引用, 故 copy 是最佳选择.
6.在controller 中回调 block 时, 不可以直接使用 self, 否则controller 的 dealloc 不会正常执行, 因为在 block 中可能会持有当前 controller 中的一些成员变量, 所以双方都不会释放. 所以就需要在 block 外进行__weak 将 self 转化.
但是在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。weakSelf比如在获取属性时, weakSelf.name 引用计数不会+1, 但 strongSlef.name 引用计数会+1 , 所以不会被析构. 注意, Block的引用循环和弱引用问题与单线程、多线程无关。只与内存模型和生存周期有关.
第四、第五条合起来有个名词叫weak–strong dance,以下代码来自AFNetworking,堪称使用weak–strong dance的经典。

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }
};

7.weakSelf 的析构出现的几率很低, 是不容易出现的. 除了autorelease之外的延迟效应,通常的情况是,外层有引用 指向 这个self对象。self 指向block, block里面有一个弱引用会指向self。由于外层的引用不释放,那么里面的这些引用都有效. 那个weakSelf 变为nil的机会,只有以下条件同时满足:
(1)外层没有其他引用 执行 Self对象
(2)Self对象没有接受过autorelease消息
(3)发现Self现在的retain count为0,ARC触发 回收Self对象,然后将weakSelf置为nil

8.根据官方文档Transitioning to ARC Release Notes中所说的, 平时开发中不重要的 block 可以一直用 __weak的指针, 但是在比较重要的 block 中,需要写成如下(官方文档例子):

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;
    if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

不仅使用过了weak–strong dance, 而且还有一个if (strongMyController) 的判断, 所以比6中的写法更加严谨.

最后再推荐一篇关于block 更深层探究的好文章: 对Objective-C中Block的追探

参考资料: 关于block使用的5点注意事项
block中weak-strong dance 的讨论整理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值