美文网首页iOS KitiOSblock
Objective-C高级编程笔记二(Blocks)

Objective-C高级编程笔记二(Blocks)

作者: 酒茶白开水 | 来源:发表于2019-04-04 15:33 被阅读120次

    示例代码下载

    Blocks概要

    Blocks是c语言的扩充功能:带有自动变量(局部变量)值的匿名函数。

    语法

    完整的语法形式:

    ==^== ==返回值类型== ==(参数列表)== =={表达式}==

    省略返回值的语法形式:

    ==^== ==(参数列表)== =={表达式}==

    省略参数的语法形式:

    ==^== ==返回值类型== =={表达式}==

    ^ int (int a, int b) { return a + b; };
    ^ (int a, int b) { printf(@"a + b = %i", a + b); };
    ^ { printf(@"block"); };
    

    block变量完整语法:

    ==返回值类型== ==(^变量名称)== ==(参数列表)==

    int (^aBlock) (int, int) = ^ int (int a, int b) { return a + b; };
    void (^bBlock) (int a, int b) = ^ (int a, int b) { NSLog(@"a + b = %i", a + b); };
    void (^cBlock) (void) = ^ { NSLog(@"block"); };
    

    typedef可申明Block类型变量:

    typedef int (^ABlock) (int a, int b);
    ABlock aBlock = ^int (int a, int b) {
        return a + b;
    };
    

    Bolocks的调用和c语言函数相同,别无二致。

    截获自动变量值

    Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,所以在执行Block语法后,即使改写了Block中使用的自动变量的值也不会影响Block执行时的值——Block-带有自动变量值的匿名函数。

    int a = 1;
    void (^block) (void) = ^{
        NSLog(@"%i", a);
    };
    a = 2;
    block();
    

    打印如下:

    2019-03-29 13:54:43.758298+0800 ProfessionalExample[2688:18267238] 1
    

    自动变量截获只能保存执行Block语法瞬间的值,保存后就不能在Block中更改该值,否则产生编译错误。但是使用截获的值是没有问题的。

    若想在Block语法表达式中将值Block语法外的自动变量,需要在该自动变量上附加__block说明符。

    注意: 截获自动变量值没有对c语言数组的支持,如果使用会产生编译错误,需要使用指针代替可以解决这个问题。

    int nums[4] = {1, 2, 3, 4};
    int *p = nums;
    void (^block) (void) = ^{
        NSLog(@"%i", *(p + 1));
    };
    block();
    

    “截获自动变量值”,意味着执行Block语法时,Block表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。

    因为Block不能改写截获的自动变量的值,当编译器检查到有赋值操作时便产生编译错误。这样就无法在Block中保存值了,极为不便。有两种方法解决这个问题:

    • 允许使用静态变量、静态全局变量、全局变量的值。
    • __block说明符,用来指定Block中要变更值的自动变量。

    Blocks实现

    Block本质是一个结构体,就相当于基于objc_object结构体的Objective-C类对象的结构体。也就是可以将Block作为Objective-C对象来处理。如下图:


    image.png

    __block变量的本质是一个结构体变量,并不在Block结构体中。这样就可以多个Block使用同一个__block变量。如下图:


    image.png

    Block与__block变量本质:

    名称 实质
    Block Block结构体实例
    __block变量 __block变量结构体实例

    Block的类有3种:

    Block类 对象存储域 说明
    _NSConcreteStackBlock 在记述自动变量的地方使用Block语法生成的Block和不截获自动变量的Block
    _NSConcreteGlobalBlock 程序的数据区域(.data区) 在记述全局变量的地方使用Block语法生成的Block
    _NSConcreteMallocBlock Blocks提供将Block和__block变量从从栈复制到堆的方法

    栈上的Block,__block变量也在栈上,当作用域结束,就会被废弃。将配置在栈上的Block复制到堆上,可以在作用域结束时堆上的Block还继续存在。copy方法能将Block从栈复制到堆,并修改Block的isa指针为_NSConcreteMallocBlock。

    事实上ARC有效时,大多数情况下ARC会进行恰当的判断,自动生成Block从栈复制到堆上的代码。

    向方法或函数的参数中传递Block时,编译器不会判定生成复制到堆上的代码。但是Cocoa框架中方法名含有usingBlock和GCD的API除外。

    NSArray *array;
    {
        int a = 2;
        array = [NSArray arrayWithObjects:[^{ NSLog(@"a + a = %i", a + a); } copy], ^{ NSLog(@"a * a = %i", a * a); }, nil];
    }
    typedef void (^Block) (void);
    Block first = [array firstObject];
    first();
    
    Block类 源对象存储域 复制效果
    _NSConcreteStackBlock 从栈复制到堆
    _NSConcreteGlobalBlock 程序的数据区域(.data区) 什么也不做
    _NSConcreteMallocBlock 引用计数增加

    由此可见,不管Block在何处配置,用copy方法都不会引起任何问题,在不确定时调用copy方法即可。

    复制Block时对__block变量的影响:

    __block变量存储域 Block重栈复制到堆
    从栈复制到堆并被Block持有
    被Block持有

    __block变量结构体中有一个指向自身的__forwarding成员变量指针,当__block变量从栈复制到堆时,会将__forwarding成员变量的值替换为复制到堆上的__block变量结构体实例的指针。通过该功能,无论在Block语法中、语法外使用__block变量,还是__block变量配置在栈上、堆上,都可以顺利的访问同一个__block变量。如下图所示:


    image.png

    如下几种情况,Block会复制到堆:

    • 调用Block的copy实例方法
    • Block作为函数返回值
    • Block赋值给__strong修饰符的变量
    • 在方法名含有usingBlock的Cocoa框架方法和GCD的API中传递Block

    复制到堆上的Block会持有__strong修饰符的自动变量和复制到堆上的__block变量,使其超出作用域而存在。如下示例:

    NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
    NSMutableArray *array1 = [NSMutableArray arrayWithCapacity:1];
    block = ^(id obj) {
        [array addObject:obj];
        NSLog(@"array: %@, array count: %li", array, array.count);
    };
    block1 = ^(id obj) {
        [array1 addObject:obj];
        NSLog(@"array1: %@, array1 count: %li", array1, array1.count);
    };
    

    打印结果:

    2019-04-02 11:42:30.155444+0800 ProfessionalExample[71676:22254773] array: (null), array count: 0
    2019-04-02 11:42:30.155680+0800 ProfessionalExample[71676:22254773] array1: (
        "<NSObject: 0x600000d8e0f0>"
    ), array1 count: 1
    
    • __block变量为附有__strong修饰符的id类型和对象类型的自动变量,当__block变量从栈复制到堆时,持有赋值给__block变量的对象。当堆上的__block变量释放时,释放赋值给__block变量的对象。
    • __block变量为附有__weak修饰符的id类型和对象类型的自动变量,__block变量不持有对象。
    • __block变量不能为附有__autoreleasing修饰符的id类型和对象类型的自动变量。
    • __block变量为附有__unsafe_unretained修饰符的id类型和对象类型的自动变量,与指针相同。
    typedef void (^Block) (id);
    Block block;
    {
        NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
        __block NSMutableArray __weak *array1 = array;
        block = ^(id obj) {
            [array1 addObject:obj];
            NSLog(@"array1: %@, array1 count: %li", array1, array1.count);
        };
    }
    block([[NSObject alloc] init]);
    

    打印结果:

    2019-04-02 15:20:35.135112+0800 ProfessionalExample[74596:22578767] array1: (null), array1 count: 0
    

    Block使用__strong修饰符的自动变量,当Block从栈复制到堆时该对象被Block持有,容易形成循环引用。

    __weak修饰符、__unsafe_unretained修饰符(iOS4中替代__weak修饰符)可以避免循环引用。

    另外__block变量也可以避免循环引用,例如如下对象:

    @interface BlockObject : NSObject
    
    @property (strong, nonatomic) void (^block)(void);
    
    @end
    
    @implementation BlockObject
    
    - (void)dealloc {
        NSLog(@"%@销毁了", self);
    }
    
    @end
    

    创建一个BlockObject对象,并设置其block属性的值。在执行block之前,__block变量obj持有BlockObject对象,BlockObject对象持有block,block持有__block变量,形成了循环引用。执行block时,向__block变量obj赋值nil,至此__block变量不再持有object对象,打破循环引用。如下所以:

    __block BlockObject *obj = [[BlockObject alloc] init];
    obj.block = ^{
        NSLog(@"%@", obj);
        obj = nil;
    };
    obj.block();
    

    使用__block变量与__weak修饰符、__unsafe_unretained修饰符(iOS4中替代__weak修饰符)避免循环引用的比较:

    • 通过__block变量可控制对象的持有时间
    • 在不能使用__weak修饰符、__unsafe_unretained修饰符(iOS4中替代__weak修饰符)的环境下,如此__block变量能够赋值
    • 使用__block变量避免循环引用,必须执行Block并手动释放__block变量对对象的引用(如置空或其它值)

    ARC无效时使用Blocks注意事项

    ARC无效时,一般需要手动用copy方法将Block从栈复制到堆,并手动用release方法来释放。

    __block说明符用来避免循环引用。这是由于当Block从栈复制到堆时,若Block使用__block说明符的id类型或对象类型的自动变量,不会被retain;若没有__block说明符,则会被retain。

    相关文章

      网友评论

        本文标题:Objective-C高级编程笔记二(Blocks)

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