Блок передачі Objective-C як параметр


Відповіді:


257

Тип блоку змінюється залежно від його аргументів та типу повернення. У загальному випадку типи блоків оголошуються так само, як і типи функцій вказівників, але замінюючи на *a ^. Один із способів передачі блоку методу полягає в наступному:

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

Але, як бачите, це безладно. Ви можете замість цього використовувати typedefтип a, щоб зробити типи блоків чистішими:

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

А потім передайте цей блок такому методу:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;

Чому ви передаєте ідентифікатор як аргумент? Неможливо, наприклад, легко передати NSNumber? Як би це виглядало?
bas

7
Ви, звичайно, можете передавати сильно набраний аргумент, такий як NSNumber *або std::string&або що-небудь інше, що ви могли б передавати як аргумент функції. Це лише приклад. (Для блоку, еквівалентного, за винятком заміни idна NSNumber, typedefбуло б typedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
Джонатан Грінспан,

Це показує декларацію методу. Одна з проблем з блоками полягає в тому, що "брудний" стиль декларування не дає зрозуміти і легко написати фактичний виклик методу з реальним аргументом блоку.
uchuugaka

Typedefs не тільки полегшує запис, але значно простіше для читання, оскільки синтаксис вказівника блоку / функції не є найчистішим.
pyj

@JonathanGrynspan, який походить зі світу Свіфта, але має торкнутися якогось старого коду Objective-C, як я можу дізнатися, чи блокується вхід чи ні? Я читав, що за замовчуванням блоки виходять, за винятком випадків, коли вони прикрашені NS_NOESCAPE, але, enumerateObjectsUsingBlockяк мені кажуть, не залишаються, але я не бачу NS_NOESCAPEніде на сайті, і не згадуюсь взагалі в документах Apple. Ви можете допомогти?
Марк А. Донохое

62

Найпростішим поясненням цього питання є наступні шаблони:

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 * масив, NSError * помилка) {// ваш код}]; У цьому прикладі тип повернення ігнорується, оскільки він недійсний?
Олексій

51

Це може бути корисно:

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

вам не вистачає дужок
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));

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

Тоді імпортуйте 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;


4

Я завжди схильний забувати про синтаксис блоків. Це мені завжди спадає на думку, коли мені потрібно оголосити блок. Я сподіваюся, що це комусь допоможе :)

http://fuckingblocksyntax.com


Це врятувало мій час
Le Ding

3

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

  1. Визначте typedef з returnType ( .hвище @interfaceдекларації)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. Визначте @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передачі до нього заздалегідь визначеної змінноїType (Не забудьте перевірити, чи 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 {

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.