Block

作者: 开发者zhang | 来源:发表于2018-03-25 10:11 被阅读0次

    Block


    Block 可以捕获外部变量

    Block 可以捕获来自外部作用域的变量,这是Block一个很强大的特性。

    - (void)testMethod {
        int anInteger = 42;
        void (^testBlock)(void) = ^{
            NSLog(@"Integer is: %i", anInteger);
        };
        testBlock();
    }
    

    默认情况下,Block 中捕获的到变量是不能修改的,如果想修改,需要使用__block来声明:

    __block int anInteger = 42;
    
    • 对于 id 类型的变量

    在 MRC 情况下,使用 __block id x 不会 retain 变量,而在 ARC 情况下则会对变量进行 retain(即和其他捕获的变量相同)。

    • 如果不想在 block 中进行 retain 可以使用 __unsafe_unretained __block id x,不过这样可能会导致野指针出现。更好的办法是使用 __weak 的临时变量:
    MyViewController *myController = [[MyViewController alloc] init…];
    // ...
    MyViewController * __weak weakMyViewController = myController;
    myController.completionHandler =  ^(NSInteger result) {
        [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
    };
    
    • 或者把使用 __block 修饰的变量设为 nil,以打破引用循环:
    MyViewController * __block myController = [[MyViewController alloc] init…];
    // ...
    myController.completionHandler =  ^(NSInteger result) {
        [myController dismissViewControllerAnimated:YES completion:nil];
        myController = nil;
    };
    

    使用 Block 时的注意事项

    • 在非 ARC 的情况下,对于 block 类型的属性应该使用 copy ,因为 block 需要维持其作用域中捕获的变量

    • 在 ARC 中编译器会自动对 block 进行 copy 操作,因此使用 strong 或者 copy 都可以,没有什么区别,但是苹果仍然建议使用 copy 来指明编译器的行为。


    • block 在捕获外部变量的时候,会保持一个强引用,当在 block 中捕获 self 时,由于对象会对 block 进行 copy,于是便形成了强引用循环:
    @interface XYZBlockKeeper : NSObject
    @property (copy) void (^block)(void);
    @end
    
    @implementation XYZBlockKeeper
    - (void)configureBlock {
        self.block = ^{
            [self doSomething];    // capturing a strong reference to self
                                   // creates a strong reference cycle
        };
    }
    ...
    @end
    
    • 为了避免强引用循环,最好捕获一个 self 的弱引用:
    - (void)configureBlock {
        XYZBlockKeeper * __weak weakSelf = self;
        self.block = ^{
            [weakSelf doSomething];   // capture the weak reference
                                      // to avoid the reference cycle
        }
    }
    
    • 使用弱引用会带来另一个问题,weakSelf 有可能会为 nil,如果多次调用 weakSelf 的方法,有可能在 block 执行过程中 weakSelf 变为 nil。因此需要在 block 中将 weakSelf “强化“
    __weak __typeof__(self) weakSelf = self;
    NSBlockOperation *op = [[[NSBlockOperation alloc] init] autorelease];
    [ op addExecutionBlock:^ {
        __strong __typeof__(self) strongSelf = weakSelf;
        [strongSelf doSomething];
        [strongSelf doMoreThing];
    } ];
    [someOperationQueue addOperation:op];
    
    • __strong 这一句在执行的时候,如果 WeakSelf 还没有变成 nil,那么就会 retain self,让 self 在 block 执行期间不会变为 nil。这样上面的 doSomething 和 doMoreThing 要么全执行成功,要么全失败,不会出现一个成功一个失败(即执行到中间 self 变成 nil 的情况)。

    会变成nil的原因

    涉及到 weak 本身的机制了。weak 置 nil 的操作发生在 dealloc 中,最后一个持有 object 的对象被释放的时候,会触发对象的 dealloc,而这个持有者的释放操作就不一定保证发生在哪个线程了。因此 block 执行的过程中 weakSelf 有可能在另外的线程中被置为 nil。

    Block 在堆上还是在栈上?

    在 MRC 下,Block 默认是分配在栈上的,除非进行显式的 copy:

    __block int val = 10;
    blk stackBlock = ^{NSLog(@"val = %d", ++val);};
    NSLog(@"stackBlock: %@", stackBlock); // stackBlock: <__NSStackBlock__: 0xbfffdb28>
    
    tempBlock = [stackBlock copy];
    NSLog(@"tempBlock: %@", tempBlock);  // tempBlock: <__NSMallocBlock__: 0x756bf20>
    

    想把 Block 用作返回值的时候,也要加入 copy 和 autorelease:

    - (blk)myTestBlock {
        __block int val = 10;
        blk stackBlock = ^{NSLog(@"val = %d", ++val);};
        return [[stackBlock copy] autorelease];
    }
    

    在 ARC 环境下,Block 使用简化了很多,同时 ARC 也更加倾向于把 Block 放到堆上:

    
    __block int val = 10;
    __strong blk strongPointerBlock = ^{NSLog(@"val = %d", ++val);};
    NSLog(@"strongPointerBlock: %@", strongPointerBlock); // strongPointerBlock: <__NSMallocBlock__: 0x7625120>
    
    __weak blk weakPointerBlock = ^{NSLog(@"val = %d", ++val);};
    NSLog(@"weakPointerBlock: %@", weakPointerBlock); // weakPointerBlock: <__NSStackBlock__: 0xbfffdb30>
    
    NSLog(@"mallocBlock: %@", [weakPointerBlock copy]); // mallocBlock: <__NSMallocBlock__: 0x714ce60>
    
    NSLog(@"test %@", ^{NSLog(@"val = %d", ++val);}); // test <__NSStackBlock__: 0xbfffdb18>
    

    只有显式的 __weak 以及纯匿名 Block 是放到栈上的,赋值给 __strong 指针(也就是默认赋值)都会导致在堆上创建 Block。

    对于把 Block 作为函数返回值的情况,ARC 也能自动处理。

    - (__unsafe_unretained blk) blockTest {
        int val = 11;
        return ^{NSLog(@"val = %d", val);};
    }
    
    NSLog(@"block return from function: %@", [self blockTest]); // block return from function: <__NSMallocBlock__: 0x7685640>
    

    参考:Objective-C Runtime

    相关文章

      网友评论

          本文标题:Block

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