Objective-C将块作为参数传递

150
我应该如何将一个 Block 传递给一个函数或方法?
我尝试使用 - (void)someFunc:(__Block)someBlock,但无济于事。
例如,一个 Block 的类型是什么?

7
我经常使用但不愿承认的参考网站:http://goshdarnblocksyntax.com/ - oltman
11个回答

264

块的类型取决于其参数和返回类型。在一般情况下,块类型的声明方式与函数指针类型相同,只是将 * 替换为 ^。将块传递给方法的一种方式如下:

- (void)iterateWidgets:(void (^)(id, int))iteratorBlock;

但是,正如您所看到的那样,这很混乱。您可以使用 typedef 来使代码块类型更清晰:

typedef void (^ IteratorBlock)(id, int);

然后将该块传递给方法,如下所示:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;

为什么要将id作为参数传递?难道不能轻松地传递NSNumber之类的对象吗?那会是什么样子呢? - bas
7
你可以传递强类型参数,比如 NSNumber * 或者 std::string& 等任何可以作为函数参数传递的内容。这只是一个例子。(如果要用等效于 id 但替换为 NSNumber 的块,那么 typedef 将是 typedef void (^ IteratorWithNumberBlock)(NSNumber *, int); - Jonathan Grynspan
这是方法声明。块的一个问题是“凌乱”的声明风格不清晰,也不容易编写具有实际块参数的实际方法调用。 - uchuugaka
typedef不仅使代码更易编写,而且由于块/函数指针语法并不是最清晰的,因此显着地提高了代码的可读性。 - pyj
@JonathanGrynspan,我来自Swift世界,但不得不接触一些旧的Objective-C代码,如何判断一个块是否逃逸?我读到默认情况下,块是逃逸的,除非用NS_NOESCAPE修饰,但我被告知enumerateObjectsUsingBlock是非逃逸的,然而我在任何地方都没有看到NS_NOESCAPE,也没有在Apple文档中提到逃逸。你能帮忙吗? - Mark A. Donohoe
当它没有展示如何将一个块作为参数传递给一个方法时,这怎么能成为被接受的答案呢?所有的答案都展示了如何声明块参数,但是没有人真正使用它们。奇怪。 - Elise van Looij

68

最简单的解释是按照以下模板进行操作:

1. 将块作为方法参数

模板

- (void)aMethodWithBlock:(returnType (^)(parameters))blockName {
        // your code
}

例子

-(void) saveWithCompletionBlock: (void (^)(NSArray *elements, NSError *error))completionBlock{
        // your code
}

其他用例:

2. 作为属性的块

模板

@property (nonatomic, copy) returnType (^blockName)(parameters);

例子

@property (nonatomic,copy)void (^completionBlock)(NSArray *array, NSError *error);

3. 将代码块作为方法参数

模板

[anObject aMethodWithBlock: ^returnType (parameters) {
    // your code
}];

例子

[self saveWithCompletionBlock:^(NSArray *array, NSError *error) {    // your code}];

4. 块作为局部变量

模板

returnType (^blockName)(parameters) = ^returnType(parameters) {
    // your code
};

例子

void (^completionBlock) (NSArray *array, NSError *error) = ^void(NSArray *array, NSError *error){
    // your code
};

5. 块作为typedef

模板

typedef returnType (^typeName)(parameters);

typeName blockName = ^(parameters) {
    // your code
}

例子

typedef void(^completionBlock)(NSArray *array, NSError *error);

completionBlock didComplete = ^(NSArray *array, NSError *error){
    // your code
};

1
[self saveWithCompletionBlock:^(NSArray *array, NSError *error) { // 你的代码 }]; 在这个例子中,返回类型被忽略了,因为它是void? - Alex

52

这可能会有所帮助:

- (void)someFunc:(void(^)(void))someBlock;

你缺少一个括号。 - newacct
这个对我起作用了,而之前那个没有。顺便说一句,谢谢伙计,确实很有帮助! - tanou

22
你可以像这样做,将块作为块参数传递:
//creating a block named "completion" that will take no arguments and will return void
void(^completion)() = ^() {
    NSLog(@"bbb");
};

//creating a block namd "block" that will take a block as argument and will return void
void(^block)(void(^completion)()) = ^(void(^completion)()) {
    NSLog(@"aaa");
    completion();
};

//invoking block "block" with block "completion" as argument
block(completion);

9

以下是使用C函数传递块的另一种方法。在下面的示例中,我创建了函数来在后台和主队列中执行任何操作。

blocks.h文件

void performInBackground(void(^block)(void));
void performOnMainQueue(void(^block)(void));

blocks.m文件

#import "blocks.h"

void performInBackground(void(^block)(void)) {
    if (nil == block) {
        return;
    }

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block);
}

void performOnMainQueue(void(^block)(void)) {
    if (nil == block) {
        return;
    }

    dispatch_async(dispatch_get_main_queue(), block);
}

在需要时导入blocks.h并调用它:
- (void)loadInBackground {

    performInBackground(^{

        NSLog(@"Loading something in background");
        //loading code

        performOnMainQueue(^{
            //completion hadler code on main queue
        });
    });
}

6
您也可以将块设置为简单属性,如果适用的话:
@property (nonatomic, copy) void (^didFinishEditingHandler)(float rating, NSString *reviewString);

请确保块属性为"copy"!

当然,您也可以使用typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;

4

4
我总是容易忘记块语法。每当我需要声明块时,这个问题总是出现在我的脑海中。希望这能帮助到别人 :) http://fuckingblocksyntax.com

这节省了我的时间。 - Le Ding
一篇关于“如何在Objective-C中声明块”的页面并没有回答“如何将块作为参数传递”的问题。说真的,在SO上看到问题和答案之间如此彻底的脱节是很罕见的。 - Elise van Looij

2

我为一个类编写了一个completionBlock,在摇晃骰子后返回它们的值:

  1. Define typedef with returnType (.h above @interface declaration)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
    
  2. Define a @property for the block (.h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
    
  3. Define a method with finishBlock (.h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
    
  4. Insert previous defined method in .m file and commit finishBlock to @property defined before

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
    
  5. To trigger completionBlock pass predefined variableType to it (Don't forget to check whether the completionBlock exists)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
    

1
尽管这个线程上的答案已经给出,我仍然很难编写一个函数来将块作为函数并带有参数。最终,我想出了以下解决方案。
我想编写一个通用函数 loadJSONthread,该函数将获取 JSON Web 服务的 URL,在后台线程中从该 URL 加载一些 JSON 数据,然后将结果作为 NSArray* 返回给调用函数。
基本上,我想把所有后台线程的复杂性隐藏在一个通用的可重用函数中。
以下是如何调用此函数:
NSString* WebServiceURL = @"http://www.inorthwind.com/Service1.svc/getAllCustomers";

[JSONHelper loadJSONthread:WebServiceURL onLoadedData:^(NSArray *results) {

    //  Finished loading the JSON data
    NSLog(@"Loaded %lu rows.", (unsigned long)results.count);

    //  Iterate through our array of Company records, and create/update the records in our SQLite database
    for (NSDictionary *oneCompany in results)
    {
        //  Do something with this Company record (eg store it in our SQLite database)
    }

} ];

...这是我遇到困难的部分:如何声明它,以及如何在数据加载后调用Block并传递一个已加载记录的NSArray*:

+(void)loadJSONthread:(NSString*)urlString onLoadedData:(void (^)(NSArray*))onLoadedData
{
    __block NSArray* results = nil;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{

        // Call an external function to load the JSON data 
        NSDictionary * dictionary = [JSONHelper loadJSONDataFromURL:urlString];
        results = [dictionary objectForKey:@"Results"];

        dispatch_async(dispatch_get_main_queue(), ^{

            // This code gets run on the main thread when the JSON has loaded
            onLoadedData(results);

        });
    });
}

这个StackOverflow的问题涉及如何调用函数并将Block作为参数传递,因此我已经简化了上面的代码,并未包含loadJSONDataFromURL函数。
但是,如果您感兴趣,您可以在此博客中找到该JSON加载函数的副本: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm 希望这能帮助其他XCode开发人员!
(如果确实有帮助,请不要忘记投票支持这个问题和我的答案!)

1
这真的是我见过的关于iOS和块的最棒的技巧之一。太喜欢了! - benathon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接