web-dev-qa-db-fra.com

Puis-je utiliser des blocs Objective-C en tant que propriétés?

Est-il possible d'avoir des blocs en tant que propriétés en utilisant la syntaxe de propriété standard?

Y at-il des changements pour ARC?

316
gurghet
@property (nonatomic, copy) void (^simpleBlock)(void);
@property (nonatomic, copy) BOOL (^blockWithParamter)(NSString *input);

Si vous allez répéter le même bloc à plusieurs endroits, utilisez un type def

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;
293
Robert

Voici un exemple de la façon dont vous accomplissez une telle tâche:

#import <Foundation/Foundation.h>
typedef int (^IntBlock)();

@interface myobj : NSObject
{
    IntBlock compare;
}

@property(readwrite, copy) IntBlock compare;

@end

@implementation myobj

@synthesize compare;

- (void)dealloc 
{
   // need to release the block since the property was declared copy. (for heap
   // allocated blocks this prevents a potential leak, for compiler-optimized 
   // stack blocks it is a no-op)
   // Note that for ARC, this is unnecessary, as with all properties, the memory management is handled for you.
   [compare release];
   [super dealloc];
}
@end

int main () {
    @autoreleasepool {
        myobj *ob = [[myobj alloc] init];
        ob.compare = ^
        {
            return Rand();
        };
        NSLog(@"%i", ob.compare());
        // if not ARC
        [ob release];
    }

    return 0;
}

Maintenant, la seule chose qui devrait changer si vous deviez changer le type de comparaison serait le typedef int (^IntBlock)(). Si vous devez lui transmettre deux objets, remplacez-le par ceci: typedef int (^IntBlock)(id, id) et modifiez votre bloc par:

^ (id obj1, id obj2)
{
    return Rand();
};

J'espère que ça aide.

EDIT 12 mars 2012:

Pour ARC, aucune modification spécifique n'est requise, car ARC gérera les blocs pour vous tant qu'ils sont définis en tant que copie. Vous n'avez pas non plus besoin de définir la propriété sur nil dans votre destructeur.

Pour plus de lecture, veuillez consulter ce document: http://clang.llvm.org/docs/AutomaticReferenceCounting.html

210

Pour Swift, utilisez simplement les fermetures: exemple.


En Objective-C,

@property (copy) void (^ doStuff) (void);

C'est si simple.

La documentation d'Apple, expliquant en détail ce problème:

Apple doco.

Dans votre fichier .h:

// Here is a block as a property:
//
// Someone passes you a block. You "hold on to it",
// while you do other stuff. Later, you use the block.
//
// The property 'doStuff' will hold the incoming block.

@property (copy)void (^doStuff)(void);

// Here's a method in your class.
// When someone CALLS this method, they PASS IN a block of code,
// which they want to be performed after the method is finished.

-(void)doSomethingAndThenDoThis:(void(^)(void))pleaseDoMeLater;

// We will hold on to that block of code in "doStuff".

Voici votre fichier .m:

 -(void)doSomethingAndThenDoThis:(void(^)(void))pleaseDoMeLater
    {
    // Regarding the incoming block of code, save it for later:
    self.doStuff = pleaseDoMeLater;

    // Now do other processing, which could follow various paths,
    // involve delays, and so on. Then after everything:
    [self _alldone];
    }

-(void)_alldone
    {
    NSLog(@"Processing finished, running the completion block.");
    // Here's how to run the block:
    if ( self.doStuff != nil )
       self.doStuff();
    }

Méfiez-vous des exemples de code obsolètes.

Avec les systèmes modernes (2014+), faites ce qui est montré ici. C'est aussi simple que ça. J'espère que ça aide quelqu'un. Joyeux Noël 2013!

156
Fattie

Pour la postérité/l'exhaustivité… Voici deux exemples complets de la mise en œuvre de cette "manière de faire" ridiculement polyvalente. @ La réponse de Robert est merveilleusement concise et correcte, mais je souhaite également montrer ici les moyens de "définir" les blocs.

@interface       ReusableClass : NSObject
@property (nonatomic,copy) CALayer*(^layerFromArray)(NSArray*);
@end

@implementation  ResusableClass
static  NSString const * privateScope = @"Touch my monkey.";

- (CALayer*(^)(NSArray*)) layerFromArray { 
     return ^CALayer*(NSArray* array){
        CALayer *returnLayer = CALayer.layer
        for (id thing in array) {
            [returnLayer doSomethingCrazy];
            [returnLayer setValue:privateScope
                         forKey:@"anticsAndShenanigans"];
        }
        return list;
    };
}
@end

Idiot? Oui. Utile? Hells yeah. Voici une façon différente, "plus atomique" de définir la propriété .. et une classe qui est ridiculement utile…

@interface      CALayoutDelegator : NSObject
@property (nonatomic,strong) void(^layoutBlock)(CALayer*);
@end

@implementation CALayoutDelegator
- (id) init { 
   return self = super.init ? 
         [self setLayoutBlock: ^(CALayer*layer){
          for (CALayer* sub in layer.sublayers)
            [sub someDefaultLayoutRoutine];
         }], self : nil;
}
- (void) layoutSublayersOfLayer:(CALayer*)layer {
   self.layoutBlock ? self.layoutBlock(layer) : nil;
}   
@end

Ceci illustre la définition de la propriété de bloc via l’accesseur (bien qu’à l’intérieur de init, pratique discutable discutable ..) par rapport au mécanisme "getatom" "nonatomique" du premier exemple. Dans les deux cas ... les implémentations "codées en dur" peuvent toujours être écrasées, par instance .. a ..

CALayoutDelegator *littleHelper = CALayoutDelegator.new;
littleHelper.layoutBlock = ^(CALayer*layer){
  [layer.sublayers do:^(id sub){ [sub somethingElseEntirely]; }];
};
someLayer.layoutManager = littleHelper;

Aussi .. si vous voulez ajouter une propriété de bloc dans une catégorie ... disons que vous voulez utiliser un bloc à la place d'une action/action de la vieille école ... Vous pouvez simplement utiliser les valeurs associées pour, ainsi .. associer les blocs.

typedef    void(^NSControlActionBlock)(NSControl*); 
@interface       NSControl            (ActionBlocks)
@property (copy) NSControlActionBlock  actionBlock;    @end
@implementation  NSControl            (ActionBlocks)

- (NSControlActionBlock) actionBlock { 
    // use the "getter" method's selector to store/retrieve the block!
    return  objc_getAssociatedObject(self, _cmd); 
} 
- (void) setActionBlock:(NSControlActionBlock)ab {

    objc_setAssociatedObject( // save (copy) the block associatively, as categories can't synthesize Ivars.
    self, @selector(actionBlock),ab ,OBJC_ASSOCIATION_COPY);
    self.target = self;                  // set self as target (where you call the block)
    self.action = @selector(doItYourself); // this is where it's called.
}
- (void) doItYourself {

    if (self.actionBlock && self.target == self) self.actionBlock(self);
}
@end

Désormais, lorsque vous créez un bouton, vous n'avez pas besoin de configurer un drame IBAction. Associez simplement le travail à effectuer lors de la création ...

_button.actionBlock = ^(NSControl*thisButton){ 

     [doc open]; [thisButton setEnabled:NO]; 
};

Ce modèle peut être appliqué OVER et OVER aux API Cocoa. Utilisez les propriétés pour rapprocher les parties pertinentes de votre code , éliminez les paradigmes de délégation compliqués , et exploite la puissance des objets au-delà de la simple fonction de "conteneurs" stupides.

20
Alex Gray

Bien sûr, vous pouvez utiliser des blocs en tant que propriétés. Mais assurez-vous qu'ils sont déclarés comme @ propriété (copie) . Par exemple:

typedef void(^TestBlock)(void);

@interface SecondViewController : UIViewController
@property (nonatomic, copy) TestBlock block;
@end

Dans MRC, les blocs capturant des variables de contexte sont alloués dans pile ; ils seront libérés lorsque le cadre de pile sera détruit. S'ils sont copiés, un nouveau bloc sera alloué dans le tas , qui pourra être exécuté ultérieurement, une fois le cadre de pile affiché.

8
Mindy

Disclamer

Ce n'est pas censé être "la bonne réponse", car cette question demande explicitement à ObjectiveC. Comme Apple a présenté Swift à la WWDC14, j'aimerais partager les différentes manières d'utiliser le blocage (ou les fermetures) dans Swift.

Bonjour, rapide

Vous avez plusieurs possibilités pour passer un bloc équivalent à fonctionner dans Swift.

J'ai trouvé trois.

Pour comprendre cela, je vous suggère de tester dans le terrain de jeu ce petit morceau de code.

func test(function:String -> String) -> String
{
    return function("test")
}

func funcStyle(s:String) -> String
{
    return "FUNC__" + s + "__FUNC"
}
let resultFunc = test(funcStyle)

let blockStyle:(String) -> String = {s in return "BLOCK__" + s + "__BLOCK"}
let resultBlock = test(blockStyle)

let resultAnon = test({(s:String) -> String in return "ANON_" + s + "__ANON" })


println(resultFunc)
println(resultBlock)
println(resultAnon)

Swift, optimisé pour les fermetures

Comme Swift est optimisé pour le développement asynchrone, Apple a davantage travaillé sur les fermetures. La première est que la signature de la fonction peut être déduite afin que vous n'ayez pas à la réécrire.

Paramètres d'accès par numéros

let resultShortAnon = test({return "ANON_" + $0 + "__ANON" })

Paramètre inférence avec nommage

let resultShortAnon2 = test({myParam in return "ANON_" + myParam + "__ANON" })

Fermeture de fuite

Ce cas spécial ne fonctionne que si le bloc est le dernier argument, il s'appelle fermeture finale

Voici un exemple (fusionné avec une signature inférée pour montrer Swift power)

let resultTrailingClosure = test { return "TRAILCLOS_" + $0 + "__TRAILCLOS" }

Finalement:

En utilisant tout ce pouvoir, je mélangerais fin de clôture et inférence de type (avec nommage pour la lisibilité)

PFFacebookUtils.logInWithPermissions(permissions) {
    user, error in
    if (!user) {
        println("Uh oh. The user cancelled the Facebook login.")
    } else if (user.isNew) {
        println("User signed up and logged in through Facebook!")
    } else {
        println("User logged in through Facebook!")
    }
}
7
Francescu

bonjour, rapide

En complément de ce que @Francescu a répondu.

Ajout de paramètres supplémentaires:

func test(function:String -> String, param1:String, param2:String) -> String
{
    return function("test"+param1 + param2)
}

func funcStyle(s:String) -> String
{
    return "FUNC__" + s + "__FUNC"
}
let resultFunc = test(funcStyle, "parameter 1", "parameter 2")

let blockStyle:(String) -> String = {s in return "BLOCK__" + s + "__BLOCK"}
let resultBlock = test(blockStyle, "parameter 1", "parameter 2")

let resultAnon = test({(s:String) -> String in return "ANON_" + s + "__ANON" }, "parameter 1", "parameter 2")


println(resultFunc)
println(resultBlock)
println(resultAnon)
0
Gil Beyruth