Bloc de passe Objective-C comme paramètre

Réponses:

257

Le type d'un bloc varie en fonction de ses arguments et de son type de retour. Dans le cas général, les types de bloc sont déclarés de la même manière que les types de pointeur de fonction, mais en remplaçant le *par un ^. Une façon de passer un bloc à une méthode est la suivante:

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

Mais comme vous pouvez le voir, c'est compliqué. Vous pouvez à la place utiliser a typedefpour rendre les types de blocs plus propres:

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

Et puis passez ce bloc à une méthode comme celle-ci:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;
Jonathan Grynspan
la source
Pourquoi passez-vous id comme argument? N'est-il pas possible de passer facilement un NSNumber par exemple? À quoi cela ressemblerait-il?
bas
7
Vous pouvez certainement passer un argument fortement typé tel que NSNumber *ou std::string&ou tout autre élément que vous pourriez passer comme argument de fonction. C'est juste un exemple. (Pour un bloc équivalent à l'exception du remplacement idpar NSNumber, le typedefserait typedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
Jonathan Grynspan
Cela montre la déclaration de méthode. Un problème avec les blocs est que le style de déclaration "désordonné" ne permet pas d'écrire clairement et facilement l'appel de méthode réel avec un argument de bloc réel.
uchuugaka
Les typedefs rendent non seulement le code plus facile à écrire, mais beaucoup plus facile à lire car la syntaxe du pointeur de bloc / fonction n'est pas la plus propre.
pyj
@JonathanGrynspan, venant du monde Swift mais ayant à toucher à un ancien code Objective-C, comment puis-je savoir si un bloc s'échappe ou non? J'ai lu que par défaut, les blocs s'échappent sauf s'ils sont décorés avec NS_NOESCAPE, mais on enumerateObjectsUsingBlockme dit qu'ils ne s'échappent pas, mais je ne vois NS_NOESCAPEnulle part sur le site, et l'échappement n'est pas du tout mentionné dans les documents Apple. Pouvez-vous m'aider?
Mark A. Donohoe le
62

L'explication la plus simple de cette question est de suivre ces modèles:

1. Bloquer comme paramètre de méthode

Modèle

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

Exemple

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

Autre utilisation des cas:

2. Bloquer en tant que propriété

Modèle

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

Exemple

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

3. Bloquer comme argument de méthode

Modèle

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

Exemple

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

4. Bloquer en tant que variable locale

Modèle

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

Exemple

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

5. Bloquer comme typedef

Modèle

typedef returnType (^typeName)(parameters);

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

Exemple

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

completionBlock didComplete = ^(NSArray *array, NSError *error){
    // your code
};
EnriMR
la source
1
[self saveWithCompletionBlock: ^ (NSArray * array, NSError * error) {// votre code}]; Dans cet exemple, le type de retour est ignoré car il est void?
Alex le
51

Cela peut être utile:

- (void)someFunc:(void(^)(void))someBlock;
quaertym
la source
il vous manque une parenthèse
newacct
Celui-ci a fonctionné pour moi alors que le précédent n'a pas fonctionné. Btw merci mon pote, c'était vraiment utile!
tanou
23

Vous pouvez faire comme ceci, en passant block comme paramètre de bloc:

//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);
Aleksei Minaev
la source
8

Une autre façon de passer un bloc en utilisant les fonctions ñ dans l'exemple ci-dessous. J'ai créé des fonctions pour exécuter n'importe quoi en arrière-plan et sur la file d'attente principale.

fichier blocks.h

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

fichier 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);
}

Ensuite, importez blocks.h si nécessaire et invoquez-le:

- (void)loadInBackground {

    performInBackground(^{

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

        performOnMainQueue(^{
            //completion hadler code on main queue
        });
    });
}
Dren
la source
6

Vous pouvez également définir block comme une propriété simple si cela s'applique pour vous:

@property (nonatomic, copy) void (^didFinishEditingHandler)(float rating, NSString *reviewString);

assurez-vous que la propriété de bloc est "copie"!

et bien sûr, vous pouvez également utiliser typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;
iiFreeman
la source
4

J'ai toujours tendance à oublier la syntaxe des blocs. Cela me vient toujours à l'esprit lorsque je dois déclarer un blocage. J'espère que cela aide quelqu'un :)

http://fuckingblocksyntax.com

Juan Sagasti
la source
Cela m'a fait gagner du temps
Le Ding
3

J'ai écrit un completionBlock pour une classe qui retournera les valeurs des dés après avoir été secoués:

  1. Définissez typedef avec returnType ( déclaration .hci-dessus @interface)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. Définissez un @propertypour le bloc ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
  3. Définissez une méthode avec finishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  4. Insérer méthode définie précédente dans le .mfichier et engager finishBlockà @propertydéfinir avant

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
  5. Pour déclencher, completionBlockpassez-lui un type de variable prédéfini (n'oubliez pas de vérifier si le paramètre completionBlockexiste)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
Alex Cio
la source
2

Malgré les réponses données sur ce fil, j'ai vraiment eu du mal à écrire une fonction qui prendrait un Block comme fonction - et avec un paramètre. Finalement, voici la solution que j'ai trouvée.

Je voulais écrire une fonction générique loadJSONthread, qui prendrait l'URL d'un service Web JSON, chargerait certaines données JSON de cette URL sur un thread d'arrière-plan, puis retournerait un NSArray * de résultats à la fonction appelante.

Fondamentalement, je voulais garder toute la complexité du thread d'arrière-plan cachée dans une fonction générique réutilisable.

Voici comment j'appellerais cette fonction:

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)
    }

} ];

... et c'est le bit avec lequel j'ai eu du mal: comment le déclarer, et comment le faire appeler la fonction Block une fois les données chargées, et passer le BlockNSArray * des enregistrements chargés:

+(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);

        });
    });
}

Cette question StackOverflow concerne la façon d'appeler des fonctions, en passant un bloc en tant que paramètre, j'ai donc simplifié le code ci-dessus et non inclus la loadJSONDataFromURLfonction.

Mais, si vous êtes intéressé, vous pouvez trouver une copie de cette fonction de chargement JSON sur ce blog: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

J'espère que cela aidera d'autres développeurs XCode! (N'oubliez pas de voter pour cette question et ma réponse, si c'est le cas!)

Mike Gledhill
la source
1
C'est sérieusement l'une des meilleures astuces que j'ai vues pour iOS et les blocs. J'adore ça mec !!!!
portforwardpodcast
1

Le modèle complet ressemble à

- (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
la source