Блок передачі 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.