在Objective-C通过块(Passing blocks in Objective-C)

2019-06-24 03:48发布

在编写接受块作为参数的方法,做我需要做什么特别的事情,例如在执行前复制块堆? 举例来说,如果我有以下方法:

- (void)testWithBlock:(void (^)(NSString *))block {
    NSString *testString = @"Test";
    block(testString);
}

我应该做任何block调用它,或输入法时过吗? 或者是使用传入的模块正确的方式上面? 此外,为调用该方法的正确方式如下,或者我应该做的事挡传递过吗?

[object testWithBlock:^(NSString *test){
    NSLog(@"[%@]", test);
}];

我在哪里需要复制块? 怎么会,如果我没有使用ARC此有所不同?

Answer 1:

当您收到一个块作为方法的参数,该块可能是已经在栈上创建的原始,也可能是复制(堆块)。 据我所知,我们告诉没办法。 所以,一般的经验法则是:如果你要的是正在接受它的方法内执行该块,你不需要将其复制。 如果您打算在该块传递到另一个方法(可能会或可能不会立即执行),那么你也不需要复制它(如果它打算把它周围的接收方法应该复制)。 但是,如果您打算在块存储以任何方式对一些地方为以后的执行,你需要复制它。 主要的例子,许多人使用的某种举行一个实例变量建成块:

typedef void (^IDBlock) (id);
@implementation MyClass{
    IDBlock _completionBlock;
}

但是,您还需要复制它,如果你将它添加到任何类型的集合类的,像一个NSArray或NSDictionary的。 否则,当您稍后尝试执行块,你会得到错误(最有可能EXC_BAD_ACCESS)或可能的数据损坏。

当你执行一个块,它首先测试是否块是很重要nil 。 Objective-C的将允许您通过nil到块方法参数。 如果该块是零,你会在尝试执行它获得EXC_BAD_ACCESS。 幸运的是,这是很容易做到。 在你的榜样,你会写:

- (void)testWithBlock:(void (^)(NSString *))block {
    NSString *testString = @"Test";
    if (block) block(testString);
}

有在复制块的性能方面的考虑。 与分别创建堆栈上的块,复制块的堆是不平凡的。 这不是一般的重大交易,但如果您使用的是块反复或使用一堆块的重复和复制他们在每次执行时,它会创建一个性能命中。 所以,如果你的方法- (void)testWithBlock:(void (^)(NSString *))block; 是在某种循环,复制,如果你并不需要复制该块可能会伤害你的表现。

你需要复制一个块的另一个地方是,如果你打算调用本身块(block递归)。 这是不是所有的常见的,但如果你打算这样做,你必须复制块。 见我的问题/所以在这里回答: 递归块在Objective-C 。

最后,如果你要存储块,你需要非常小心如何创建保留周期。 块将保留传递给它的任何对象,如果对象是一个实例变量,它会保留实例变量的类(个体经营)。 我个人喜欢的块,并使用他们所有的时间。 但有一个理由苹果不使用/存储块的UIKit类,而是坚持使用任何一个目标/行动或委托模式。 如果(类创建块)被保持正在接收/复制/存储块,并且在你要么自己或任何类的实例变量引用该块,创建了保持周期的类(CLASSA - > CLASSB - >块 - > CLASSA)。 这是非常容易做到的,而且它的东西,我已经做了太多次。 此外,在仪器“泄漏”不抓住它。 要解决这个问题的方法很简单:只需创建一个临时__weak (用于ARC)变量或__block可变(非ARC)和块不会保留该变量。 因此,举例来说,下面将是一个保留周期,如果“对象”拷贝/存储块:

[object testWithBlock:^(NSString *test){
    _iVar = test;
    NSLog(@"[%@]", test);
}];

但是,要解决这个问题(使用ARC):

__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
    iVar = test;
    NSLog(@"[%@]", test);
}];

你也可以做这样的事情:

__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
    _self->_iVar = test;
    NSLog(@"[%@]", test);
}];

需要注意的是,因为他们认为它十分脆弱很多人不喜欢上面的,但它是访问变量的一个有效途径。 更新 - “ - >”目前的编译器现在,如果你试图直接访问使用变量警告。 出于这个原因(以及安全的原因),最好创建您要访问的变量的属性。 因此,而不是_self->_iVar = test; 你可以使用: _self.iVar = test;

UPDATE(详细信息)

一般来说,最好考虑接收块作为负责确定块是否需要被复制,而不是调用者的方法。 这是因为接收方法可以是知道块需要多么长的时间来维持生命,或者如果它需要复制唯一的一个。 您(作为程序员)显然知道这个信息,当你写的电话,但如果你精神上考虑在单独的对象调用者和接收者,主叫方给接收器块,并用它做。 因此,它不应该需要知道它的消失后,什么是与块完成。 在另一面,它很可能是主叫方可能已经复制的块(也许它存储的块,现在交给受另一种方法),但接收器(谁也打算在存储块)仍应复制块(即使块作为已经被复制)。 接收器无法知道该块已经被复制,并收到一些块可被复制,而其他人可能不会。 因此,接收器应始终复制,它打算在保持各地块? 合理? 这本质上是面向设计实践的好对象。 基本上,谁拥有信息是负责处理它。

块是苹果的GCD(大中央调度)广泛用于轻松实现多线程。 一般情况下,你不需要你的时候派遣它的GCD复制块。 奇怪的是,这是稍有反直觉的(如果你认为这件事),因为如果你异步分派块,往往块创建将前挡返回方法执行,这通常意味着该块将到期,因为它是一个堆栈对象。 我不认为GCD复制块堆栈(我读的地方,但一直未能再次找到它),而不是我觉得线程的寿命是由被提上另一个线程扩展。

迈克灰分对块,GCD和ARC,你可能会发现有用的广泛的文章:

  • 迈克灰:实用块
  • 迈克灰:Objective-C的块VS的C ++ 0x lambda表达式:战斗!
  • 迈克灰:Q&A -关于块更多信息
  • 迈克灰分:自动引用计数他谈论这篇文章中块和ARC。
  • 迈克灰:GCD是不是块,块不是GCD
  • 苹果文档-介绍座和GCD


Answer 2:

这一切看起来不错。 虽然,你可能要仔细检查块参数:

@property id myObject;
@property (copy) void (^myBlock)(NSString *);

....

- (void)testWithBlock: (void (^)(NSString *))block
{
    NSString *testString = @"Test";
    if (block)
    {
        block(test);
        myObject = Block_copy(block);
        myBlock = block;
    }
}

...

[object testWithBlock: ^(NSString *test)
{
    NSLog(@"[%@]", test);
}];

应该没事。 而且我相信,他们甚至试图淘汰Block_copy()但他们还没有。



Answer 3:

随着块编程主题指导下,“说拷贝块 ”:

通常情况下,你不应该需要复制(或保留)块。 您只需要在您所期望的块中,它被宣布为范围的破坏后可以用来进行复印。

在你所描述的情况,你基本上可以想想块简单地被你的方法的参数,就像好像它是一个int或其他原始类型。 当方法被调用时,堆栈空间将被分配给方法的参数,因此块将你的方法的整个执行过程中居住在栈上(就像所有其它参数)。 当堆栈帧被在所述方法的返回弹出栈的顶部,分配给该块中的栈存储器将被释放。 因此,你的方法的执行过程中,该块被保证是活的,所以没有内存管理来处理这里(在ARC和非ARC的情况下)。 换句话说,你的代码是好的。 你可以简单的调用方法中的块。

由于引用的文字表明,你需要明确地复制块的唯一时间是当你希望它是从那里创建它的范围之外访问的(在你的情况,超出了你的方法的堆栈帧的寿命)。 举个例子,假设你想从网上获取一些数据,并运行的代码块当抓取完成的方法。 你的方法签名可能是这样的:

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(void))completionHandler;

由于数据读取异步发生,你会希望保持块周围(可能在你的类的属性),然后运行该块,一旦数据已完全取出。 在这种情况下,您的实现可能是这样的:

@interface MyClass

@property (nonatomic, copy) void(^dataCompletion)(NSData *);

@end



@implementation MyClass
@synthesize dataCompletion = _dataCompletion;

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler {
    self.dataCompletion = completionHandler;
    [self fetchDataFromURL:url]; 
}

- (void)fetchDataFromURL:(NSURL *)url {
    // Data fetch starts here 
}

- (void)finishedFetchingData:(NSData *)fetchedData {
    // Called when the data is done being fetched
    self.dataCompletion(fetchedData)
    self.dataCompletion = nil; 
}

在这个例子中,使用属性与一个copy语义将执行Block_copy()的块上并将其复制到堆。 这发生在该行self.dataCompletion = completionHandler 。 因此,块被从的堆栈帧移动-getDataFromURL:completionHandler:方法给堆这使得它能够在以后被调用finishedFetchingData:方法。 在后一种方法中,线self.dataCompletion = nil勾销的属性和发送Block_release()所存储的块,从而重新分配它。

以这种方式使用的属性是好的,因为它本质上处理所有的块存储管理的你(只是确保它的一个copy (或strong )财产,不是简单的retain ),并在这两个非ARC和ARC情况下工作。 相反,如果你想使用原始实例变量来存储您的块和非ARC环境中工作,你就必须调用Block_copy() Block_retain()Block_release()自己在所有适当的地方,如果你想保持块周围比其中它是作为一个参数传递的方法的寿命任何更长的时间。 相同的代码使用伊娃,而不是一个属性是这样上面写:

@interface MyClass {
    void(^dataCompletion)(NSData *);
}

@end



@implementation MyClass

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler {
    dataCompletion = Block_copy(completionHandler);
    [self fetchDataFromURL:url]; 
}

- (void)fetchDataFromURL:(NSURL *)url {
    // Data fetch starts here 
}

- (void)finishedFetchingData:(NSData *)fetchedData {
    // Called when the data is done being fetched
    dataCompletion(fetchedData)
    Block_release(dataCompletion);
    dataCompletion = nil;
}


Answer 4:

你知道有两种块:

  1. 存储在堆栈块,您明确写为^ {...},而只要他们在返回创建的功能消失,就像常规堆栈变量的人。 当你调用它属于返回功能后堆栈块,不好的事情发生。

  2. 在堆块,当您复制另一个块,你得到的,只要一些其他的对象保持对他们的引用,就像普通的对象生活的人。

为您复制块的唯一原因是,当你给一个块的,也可以是一个堆栈块(显式局部块^ {...},或方法参数的原点你不知道),而要延长其寿命出栈块的一个有限的,而编译器不已经做的工作​​适合你。

认为:保持块实例变量。

添加集合中的块如NSArray的。

这些都是在当你不知道它已经是一个堆块,你应该复制块的情况下常见的例子。

注意,编译器会为你当块被调用另一个块。



文章来源: Passing blocks in Objective-C