Блок прохода Objective-C как параметр

Ответы:

260

Тип блока зависит от его аргументов и типа возвращаемого значения. В общем случае, типы блоков объявляются так же , как типы указателей функции являются, но заменяя *с ^. Один из способов передать блок в метод следующий:

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

Но, как видите, это грязно. Вместо этого вы можете использовать a, 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);.)
Джонатан Гринспан,
Это показывает объявление метода. Одна проблема с блоками заключается в том, что "беспорядочный" стиль объявления не дает ясности и простоты записи фактического вызова метода с реальным аргументом блока.
uchuugaka
Typedef не только упрощает написание кода, но и значительно облегчает его чтение, поскольку синтаксис указателя блока / функции не самый чистый.
pyj
@JonathanGrynspan, пришедший из мира Swift, но должен коснуться какого-то старого кода Objective-C, как я могу определить, ускользает ли блок или нет? Я читал, что по умолчанию блоки экранируются, за исключением случаев, когда они украшены NS_NOESCAPE, но enumerateObjectsUsingBlockмне сказали, что экранирование не выполняется, но я NS_NOESCAPEнигде не вижу на сайте и не упоминается об экранировании вообще в документации Apple. Вы можете помочь?
Марк А. Донохью
63

Самое простое объяснение этого вопроса - следовать этим шаблонам:

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
};
EnriMR
источник
1
[self saveWithCompletionBlock: ^ (массив NSArray *, ошибка NSError *) {// ваш код}]; В этом примере возвращаемый тип игнорируется, потому что он недействителен?
Alex
51

Это может быть полезно:

- (void)someFunc:(void(^)(void))someBlock;
Quaertym
источник
вам не хватает скобок
newacct
Этот работал у меня, а предыдущий - нет. Кстати, спасибо, дружище, это было действительно полезно!
tanou
23

Вы можете сделать это, передав блок в качестве параметра блока:

//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);
Алексей Минаев
источник
8

Еще один способ передать блок с помощью функций с в примере ниже. Я создал функции для выполнения чего-либо в фоновом режиме и в основной очереди.

файл 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);
}

Затем импортируйте блоки .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);

убедитесь, что свойство блока - «копировать»!

и, конечно, вы также можете использовать typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;
iiFreeman
источник
4

Я всегда забываю о синтаксисе блоков. Это всегда приходит мне в голову, когда мне нужно объявить блокировку. Надеюсь, это кому-то поможет :)

http://fuckingblocksyntax.com

Хуан Сагасти
источник
Это сэкономило мне время
Ле Динг
3

Я написал блок завершения для класса, который будет возвращать значения кубиков после их встряхивания:

  1. Определите typedef с помощью returnType ( объявление .hвыше @interface)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
    
  2. Определите a @propertyдля блока ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
    
  3. Определите метод с помощью finishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
    
  4. Вставить предыдущий определенный метод в .mфайл и зафиксировать finishBlockдо @propertyопределенного ранее

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
    
  5. Чтобы вызвать, completionBlockпередайте ему предопределенный тип переменной (не забудьте проверить, completionBlockсуществует ли )

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
    
Алекс Чио
источник
2

Несмотря на ответы, данные в этом потоке, я действительно изо всех сил пытался написать функцию, которая принимала бы блок как функцию - и с параметром. В конце концов, вот решение, которое я придумал.

Я хотел написать общую функцию, loadJSONthreadкоторая будет принимать URL-адрес веб-службы JSON, загружать некоторые данные JSON из этого URL-адреса в фоновом потоке, а затем возвращать 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 после загрузки данных и передавать BlockNSArray * загруженных записей:

+(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 касается того, как вызывать функции, передавая Блок в качестве параметра, поэтому я упростил приведенный выше код и не включил loadJSONDataFromURLфункцию.

Но, если вам интересно, вы можете найти копию этой функции загрузки JSON в этом блоге: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

Надеюсь, это поможет другим разработчикам XCode! (Не забудьте проголосовать за этот вопрос и за мой ответ, если да!)

Майк Гледхилл
источник
1
Это серьезно один из лучших трюков, которые я видел для iOS и блоков. Любите его, чувак !!!!
portforwardpodcast
1

Полный шаблон выглядит так

- (void) main {
    //Call
    [self someMethodWithSuccessBlock:^{[self successMethod];}
                    withFailureBlock:^(NSError * error) {[self failureMethod:error];}];
}

//Definition
- (void) someMethodWithSuccessBlock:(void (^) (void))successBlock
                   withFailureBlock:(void (^) (NSError*))failureBlock {

    //Execute a block
    successBlock();

//    failureBlock([[NSError alloc]init]);

}

- (void) successMethod {

}

- (void) failureMethod:(NSError*) error {

}
yoAlex5
источник