iOS开发之block使用详解

苹果自从iOS4.0开始推出block,方便开发者使用,现代ios应用的开发,不使用GCD和block,效率会降低很多。在编程过程中,blocks被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行。Blocks可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它和传统的函数指针很类似,但是有区别:blocks是inline的,并且它对局部变量是只读的。

下面来看看block到底是什么鬼:

  1. block的定义

    // 声明和实现写在一起,就像变量的声明实现 int a = 10;
    int (^aBlock)(int, int) = ^(int num1, int num2) {
            return num1 * num2;
           };
    // 声明和实现分开,就像变量先声明后实现 int a;a = 10;
    int (^cBlock)(int,int);
     cBlock = ^(int num1,int num2)
    {
        return num1 * num2;
    };
    //其中,定义了一个名字为aBlock的blocks对象,并携带了相关信息:
    
      1. aBlock 有两个形式参数,分别为int类型;
    
      2. aBlock 的返回值为int 类型;
    
      3. 等式右边就是blocks的具体实现;
    
      4. ^ 带边blocks声明和实现的标示(关键字);
    
    //当然,你可以定义其他形式的block:无返回值,无形式参数等;
    
    void (^bBlock)() = ^()
    {
        int a = 10;
        printf(num = %d,a);
    };
    
  2. blocks 访问权限

    //1.blocks可以访问局部变量,但是不能修改。
    int a = 10;
    int (^dBlock)(int) = ^(int num)
    {
        a++;//not work!
        return num * a;
    };
    //此处不能修改的原因是在编译期间确定的,编译器编译的时候把a的值复制到block作为一个新变量(假设是a‘ = 10),此时a'和a是没有关系的。
    
    //2.这个地方就是函数中的值传递。如果要修改就要加关键字:__block或者static
     __block int a = 7;
     int (^dBlock)(int) = ^(int num)
     {
         a++;// work!
         return num * a;
     };
    
  3. block的调用

    //1.block调用就像调用函数一样。e.g:
    int c = aBlock(10,10);
    bBlock();
    
  4. block 应用

  1. block返回对象以及参数是对象的使用
  
  头文件如下,实现文件中不需要实现任何代码。

#import <Foundation/Foundation.h>
typedef int(^compareBlock)(int a, int b);
@interface HBTestBlock : NSObject

//注意以下两种方式的不同
@property(nonatomic, copy) compareBlock compare;
@property(nonatomic, copy) UIView *(^viewGetter)(NSString *imageName); //注意其返回类型为UIView *
@end

下面这两个函数,展示的是如何在其他的类中,使用这两个属性。

#pragma mark 测试对象的属性为block
- (void)testObjPropertyBlock
{
    HBTestBlock *objPropertyBlockObj = [[HBTestBlock alloc] init];
    objPropertyBlockObj.viewGetter = ^(NSString *imageName){
//        return [[UIView alloc] init]; //特别注意此处,若对象不匹配,则会报错,设置为nil也会报错。
        return [self currentView];
    };
    objPropertyBlockObj.viewGetter(@"hello"); //实际执行block
}

- (UIView *)currentView
{
    NSLog(@"now I am in currentView");
    return nil;
}

- (void)testPropertyBlock
{
    HBTestBlock *properBlockObj = [[HBTestBlock alloc] init];
    properBlockObj.compare = ^(int a,int b)
    {
        int result = [self maxer:a another:b];
        NSLog(@"the result is %d",result);
        return result;
    };
    NSLog(@"the properBlockObj.compare is %d",properBlockObj.compare(100,200));
}

- (int)maxer:(int)a another:(int)b
{
    if (a > b) {
        return a;
    }
    return  b;
}

  

2. Block作为property属性实现页面之间传值

假设我们熟悉代理递值的话,对代理我们可能又爱有恨!我们先建立模型A页面 push B页面,如果把A页面的值传递到B页面,属性和单例传值可以搞定!但是如果Pop过程中把B页面的值传递到A页面,那就可以用单例或者代理了!说到代理,我们要先声明协议,创建代理,很是麻烦。常常我们传递一个数值需要在两个页面间写很多代码,这些代码改变页面的整体顺序,可读性也打了折扣。所以,此时,block是一种优化方案!

需求:在ViewController中,点击Button,push到下一个页面NextViewController,在NextViewController的输入框TextField中输入一串字符,返回的时候,在ViewController的Label上面显示文字内容,

(1)第一种方法:首先看看通过“协议/代理”是怎么实现两个页面之间传值的吧,

//NextViewController是push进入的第二个页面
//NextViewController.h 文件
//定义一个协议,前一个页面ViewController要服从该协议,并且实现协议中的方法
@protocol NextViewControllerDelegate <NSObject>
- (void)passTextValue:(NSString *)tfText;
@end

@interface NextViewController : UIViewController
@property (nonatomic, assign) id<NextViewControllerDelegate> delegate;

@end

//NextViewController.m 文件
//点击Button返回前一个ViewController页面
- (IBAction)popBtnClicked:(id)sender {
    if (self.delegate && [self.delegate respondsToSelector:@selector(passTextValue:)]) {
        //self.inputTF是该页面中的TextField输入框
        [self.delegate passTextValue:self.inputTF.text];
    }
    [self.navigationController popViewControllerAnimated:YES];
}
接下来我们在看看ViewController文件中的内容,

//ViewController.m 文件
@interface ViewController ()<NextViewControllerDelegate>
@property (strong, nonatomic) IBOutlet UILabel *nextVCInfoLabel;

@end
//点击Button进入下一个NextViewController页面
- (IBAction)btnClicked:(id)sender
{
    NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
    nextVC.delegate = self;//设置代理
    [self.navigationController pushViewController:nextVC animated:YES];
}

//实现协议NextViewControllerDelegate中的方法
#pragma mark - NextViewControllerDelegate method
- (void)passTextValue:(NSString *)tfText
{
    //self.nextVCInfoLabel是显示NextViewController传递过来的字符串Label对象
    self.nextVCInfoLabel.text = tfText;
}
这是通过“协议/代理”来实现的两个页面之间传值的方式。

(2)第二种方法:使用Block作为property,实现两个页面之间传值,

先看看NextViewController文件中的内容,

//NextViewController.h 文件
@interface NextViewController : UIViewController
@property (nonatomic, copy) void (^NextViewControllerBlock)(NSString *tfText);

@end
//NextViewContorller.m 文件
- (IBAction)popBtnClicked:(id)sender {
    if (self.NextViewControllerBlock) {
        self.NextViewControllerBlock(self.inputTF.text);
    }
    [self.navigationController popViewControllerAnimated:YES];
}
再来看看ViewController文件中的内容,

- (IBAction)btnClicked:(id)sender
{
    NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
    __weak __typeof(self) weakSelf = self;
    nextVC.NextViewControllerBlock = ^(NSString *tfText){
        [weakSelf resetLabel:tfText];
    };
    [self.navigationController pushViewController:nextVC animated:YES];
}
#pragma mark - NextViewControllerBlock method
- (void)resetLabel:(NSString *)textStr
{
    self.nextVCInfoLabel.text = textStr;
}

好了就这么多代码,可以使用Block来实现两个页面之间传值的目的,实际上就是取代了Delegate的功能。

  1. block的内存管理

    block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。比如下面的例子。 我在view did load中创建了一个block:

    - (void)viewDidLoad
    {
    [superviewDidLoad];
    
    int number = 1;
    _block = ^(){
    
    NSLog(@number %d, number);
    };
    }
    并且在一个按钮的事件中调用了这个block:
    
    - (IBAction)testDidClick:(id)sender {
    _block();
    }
    

此时我按了按钮之后就会导致程序崩溃,解决这个问题的方法就是在创建完block的时候需要调用copy的方法。copy会把block从栈上移动到堆上,那么就可以在其他地方使用这个block了~ 修改代码如下:

_block = ^(){
NSLog(@number %d, number);
};

_block = [_blockcopy];

同理,特别需要注意的地方就是在把block放到集合类当中去的时候,如果直接把生成的block放入到集合类中,是无法在其他地方使用block,必须要对block进行copy。不过代码看上去相对奇怪一些:

[array addObject:[[^{
NSLog(@hello!);
} copy] autorelease]];
  1. 循环引用

对于非ARC下, 为了防止循环引用, 我们使用block来修饰在Block中使用的对象:
对于ARC下, 为了防止循环引用, 我们使用
weak来修饰在Block中使用的对象。原理就是:ARC中,Block中如果引用了__strong修饰符的自动变量,则相当于Block对该变量的引用计数+1。
这一点其实是在第一点的一个小的衍生。当在block内部使用成员变量的时候,比如

@interface ViewController : UIViewController
{
NSString *_string;
}
@end

在block创建中:

_block = ^(){
NSLog(@string %@, _string);
};

这里的_string相当于是self->_string;那么block是会对内部的对象进行一次retain。也就是说,self会被retain一次。当self释放的时候,需要block释放后才会对self进行释放,但是block的释放又需要等self的dealloc中才会释放。如此一来变形成了循环引用,导致内存泄露。

修改方案是新建一个block scope的局部变量,并把self赋值给它,而在block内部则使用这个局部变量来进行取值。因为block标记的变量是不会被自动retain的。

__block ViewController *controller = self;
_block = ^(){
NSLog(@string %@, controller->_string);
};