美文网首页
Block用法总结

Block用法总结

作者: 原野de呼唤 | 来源:发表于2016-06-03 12:52 被阅读1082次

    一、 block语法格式,如下

    //return type (^BlockName)(list of arguments) = ^return type (list of arguments){do something;};

    根据上面的英文应该就能理解各部分是什么了,接下来是一个简单的例子,可以帮助理解,这里引用了  Sindri的小巢(简书作者)的文章

    原文链接:http://www.jianshu.com/p/29d70274374b

    int (^sumOfNumbers) (int a, int b) = ^int(int a, int b) {

    return a + b;

    };

    //Block的调用

    int result = sumOfNumbers(100, 200); //无参时,也要带上小括号

    NSLog(@"result = %d", result);

    1.等号左侧声明一个名为sumOfNumbers的代码块,名称前用^符号表示后面的字符串是block的名称;

    2.最左侧的int表示这个block的返回值;

    3.括号中间表示这个block的参数列表,这里接收两个int类型的参数。

    4. 而在等号右侧表示这个block的实现,其中返回值类型是可以省略的,编译器会根据上下文自动补充返回值类型。

    5.使用^符号衔接着一个参数列表,使用括号包起来,告诉编译器这是一个block,然后使用大括号将block的代码封装起来。

    二、 block的直接使用(匿名Block对象)

    NSArray *array = @[@"3", @"1", @"2"];

    array = [array sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {

    return[obj1 compare: obj2];

    }];

    NSLog(@"array = %@", array);

    其实理解起来也很简单,匿名的Block对象/是可以传递给/方法的Block对象/的,而不需要先赋值给变量。

    让我们先看看匿名的整数。有三种方法可以将整数传递给方法:

    方法1: 声明、赋值和使用完全分开

    int i;

    i = 5;

    NSNumber *num = [NSNumber numberWithInt:i];

    方法2: 在一行中声明赋值使用

    int i = 5;

    NSNumber *num = [NSNumber numberWithInt:i];

    方法3:跳过变量声明步骤

    NSNumber *num = [NSNumber numberWithInt:5];

    如果采用第三种方法,就是匿名地传递一个整数。因为它没有名字,所以说它是匿名的。

    而将Block对象传递给方法的办法和传递整数相同。分别用三行代码来声明Block对象,然后赋值,最后使用。但是匿名传递Block对象更加常用。

    三、类型重定义(block重命名)

    Block对象的语法可能会比较复杂。通过使用第11章介绍过的typedef关键字,可以将某个Block对象类型定义为一个新类型,以方便使用。需要注意的是,不能在方法的实现代码中使用typedef。也就是说,应该在实现文件的顶部,或者头文件内使用typedef。在main.m中,添加以下代码:

    #import

    typedef int (^SumBlock)(int a, int b);

    int main (int argc, const char * argv[])

    {

    这段代码中的typedef语句看上去与Block变量声明很像,但是,这里定义的是一个新的类型,而不是变量。跟在^字符后面的是类型名称。创建这个新类型后,就能简化相应Block对象的声明。

    现在使用新的类型声明SumBlock:

    注意,这里的Block类型只是声明了Block对象的实参和返回类型,并没有实现真正的Block对象。

    SumBlock sumBlock = ^(int a, int b){

    return a + b;

    };

    int result = sumBlock(100, 200);

    NSLog(@"result = %d", result);

    四、外部变量 —— block对栈区变量做只读拷贝操作,使用的是对变量的copy,而不是变量本身

    Block对象通常会(在其代码中)使用外部创建的其他变量(基本类型的变量,或者是指向其他对象的指针)。这些外部创建的变量叫做外部变量(external variables)。当执行Block对象时,为了确保其下的外部变量能够始终存在,相应的Block对象会捕获(captured)这些变量。对基本类型的变量,捕获意味着程序会拷贝变量的值,并用Block对象内的局部变量保存。对指针类型的变量,Block对象会使用强引用。这意味着凡是Block对象用到的对象,都会被保留。所以在相应的Block对象被释放前,这些对象一定不会被释放(这也是Block对象和函数之间的差别,函数无法做到这点)。

    使用外部变量

    int a = 200, b = 100;

    int (^minusBlock) (void) = ^(void){return a - b;};

    NSLog(@"minus1 = %d", minusBlock());

    //结果为100

    a = 500, b = 200;

    NSLog(@"minus2 = %d", minusBlock());

    //结果还是100

    只需要在定义外部变量的时候,使用 __block 或者 static 修饰。

    __block int a = 200, b = 100;//或者 static

    int (^minusBlock) (void) = ^(void){return a - b;};

    NSLog(@"minus1 = %d", minusBlock());

    //结果为100

    a = 500, b = 200;

    NSLog(@"minus2 = %d", minusBlock());

    //结果是300

    修改外部变量

    在Block对象中,被捕获的变量是常数,程序无法修改变量所保存的值。如果需要在Block对象内修改某个外部变量,则可以在声明相应的外部变量时,在前面加上__block关键字,否则,如果在block内部改变外部变量的值程序就会报错

    这样写是没问题的

    void(^oneBlock)(void) = ^(void){

    int n = 1;

    n = 2;

    NSLog(@"n = %d", n);

    };

    oneBlock();//结果是2

    下面的写法会报错

    int n = 1;

    //定义Block

    void(^oneBlock)(void) = ^(void){

    n = 2;//报错!!!!!!

    NSLog(@"n = %d", n);

    };

    oneBlock();

    同样可以使用__block修饰外部变量来解决这个问题(使用static修饰或定义成实例变量的方法解决)

    __block int n = 1;

    //定义Block

    void(^oneBlock)(void) = ^(void){

    n = 2;

    NSLog(@"n = %d", n);

    };

    oneBlock();//结果是2

    在block对象中使用self

    如果需要写一个使用self的Block对象,就必须要多做几步工作来避免循环引用问题。下面举个简单的例子

    self.nameLabel.text = @"xiaoming";

    void(^strBlock)(void) = ^(void){

    NSString*name = self.nameLabel.text;

    NSLog(@"%@", name);

    };

    strBlock();

    Block中使用了self,这个Block对象会捕获self,Block必须等到所引用的对象全部释放后才会释放,然而,self又要到程序运行结束才释放,这样Block就不可能得到释放,就陷入强引用循环了。

    为了打破这个强引用循环,可以先在Block对象外声明一个__block指针(ARC下使用__weak),然后将这个指针指向Block对象使用的self,最后在Block对象中使用这个新的指针:

    __weak RootViewController *weakSelf = self; // 一个弱引用指针

    void(^strBlock)(void) = ^(void){

    NSString*name =weakSelf.nameLabel.text;

    NSLog(@"%@", name);

    };

    如果想要了解的更深入,可以继续看完下面的内容,举例有所变化,但应该可以理解。

    =======================================

    现在这个Block对象对BNREMployee实例是弱引用,强引用循环打破了。

    然而,由于是弱引用,所以self指向的对象在Block执行的时候可能会被释放。

    为了避免这种情况的发生,可以在Block对象中创建一个对self的局部强引用:

    __weak BNREmployee *weakSelf = self; // 弱引用

    myBlock = ^{

    BNREmployee *innerSelf = weakSelf; // 局部强引用

    NSLog(@"Employee: %@", innerSelf);

    };

    通过创建innerSelf强引用,就可以在Block和BNREmployee实例中再次创建一个强引用循环。但是,由于innerSelf引用是针对Block内部的,所以只有在Block执行的时候它才会执行,而Block结束之后就会自动消失。

    每次写Block对象的时候都引用self会是一个很好的练习。

    在Block对象中无意使用self,而是使用了实例变量的情况

    如果直接在Block对象中使用实例变量,那么block会捕获self,而不会捕获实例变量。这是实例变量的一个鲜为人知的特点。例如,以下这段代码直接存取一个实例变量:

    __weak BNREmployee *weakSelf = self;

    myBlock = ^{

    BNREmployee *innerSelf = weakSelf; // 局部强引用

    NSLog(@"Employee: %@", innerSelf);

    NSLog(@"Employee ID: %d", _employeeID);

    };

    编译器是这么解读这段代码的:

    __weak BNREmployee *weakSelf = self;

    myBlock = ^{

    BNREmployee *innerSelf = weakSelf; // 局部强引用

    NSLog(@"Employee: %@", innerSelf);

    NSLog(@"Employee ID: %d", self->_employeeID);

    };

    ->语法看上去是不是很熟悉?这个语法实际是用来后去堆上的成员结构的。从最底层来说,对象实际就是结构。

    由于编译器将_employeeID看成是self->_employeeID,self就被Block对象无意地捕获了。这样又会造成之前使用weakSelf和innerSelf避免的强引用循环。

    怎样解决呢?不要直接存取实例变量。使用存取方法!

    __weak BNREmployee *weakSelf = self;

    myBlock = ^{

    BNREmployee *innerSelf = weakSelf; // 局部强引用

    NSLog(@"Employee: %@", innerSelf);

    NSLog(@"Employee ID: %d", innerSelf.employeeID);

    };

    现在没有直接地使用self了,就不会造成无意识地强引用循环。

    在这种情况下,重要的是要理解编译器是如何思考的,这样才能避免隐藏的强引用循环。

    相关文章

      网友评论

          本文标题:Block用法总结

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