Puis-je utiliser des blocs Objective-C comme propriétés?

321

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

Y a-t-il des changements pour ARC ?

gurghet
la source
1
Eh bien, parce que ce serait très pratique. Je n'aurais pas besoin de savoir ce que c'est tant que j'ai la bonne syntaxe et qu'elle se comporte comme un NSObject.
gurghet
5
Si vous ne savez pas ce que c'est, comment savez-vous que ce serait très pratique?
Stephen Canon
5
Vous ne devriez pas les utiliser si vous ne savez pas ce qu'ils sont :)
Richard J. Ross III
5
@Moshe voici quelques raisons qui me viennent à l'esprit. Les blocs sont plus faciles à implémenter qu'une classe déléguée complète, les blocs sont légers et vous avez accès aux variables qui se trouvent dans le contexte de ce bloc. Les rappels d'événements peuvent être effectués efficacement à l'aide de blocs (cocos2d les utilise presque exclusivement).
Richard J. Ross III
2
Pas complètement lié, mais comme certains commentaires se plaignent de la syntaxe de bloc "moche", voici un excellent article qui dérive la syntaxe des premiers principes: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler

Réponses:

305
@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;
Robert
la source
3
Avec xCode 4.4 ou plus récent, vous n'avez pas besoin de synthétiser. Cela le rendra encore plus concis. Apple Doc
Eric
wow, je ne le savais pas, merci! ... Bien que je le fasse souvent@synthesize myProp = _myProp
Robert
7
@Robert: Vous avez de la chance à nouveau, car sans mettre @synthesizela valeur par défaut, c'est ce que vous faites @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric
1
@CharlieMonroe - Oui, vous avez probablement raison, mais n'avez-vous pas besoin d'une implémentation de dealloc pour annuler ou libérer la propriété de bloc sans ARC? (ça fait un moment que je n'ai pas utilisé ARC)
Robert
1
@imcaptor: Oui, cela peut provoquer des fuites de mémoire au cas où vous ne le libéreriez pas dans dealloc - comme avec n'importe quelle autre variable.
Charlie Monroe
210

Voici un exemple de la façon dont vous accomplirez 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 passer deux objets, changez-le en ceci: typedef int (^IntBlock)(id, id)et changez votre bloc en:

^ (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ère les blocs pour vous tant qu'ils sont définis comme copie. Vous n'avez pas non plus besoin de définir la propriété sur zéro dans votre destructeur.

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

Richard J. Ross III
la source
158

Pour Swift, utilisez simplement des fermetures: exemple.


Dans l'objectif-C:

@property (copy) void

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

C'est si simple.

Voici la documentation Apple actuelle, qui indique précisément ce qu'il faut utiliser:

Doco d'Apple.

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 cela.

Fattie
la source
Peut-être devriez-vous également dire que maintenant (2016), c'est ok à utiliser strongau lieu de copy?
Nik Kov
Pouvez-vous expliquer pourquoi la propriété ne devrait pas être nonatomicdifférente des meilleures pratiques pour la plupart des autres cas d'utilisation de propriétés?
Alex Pretzlav
WorkingwithBlocks.html d'Apple "Vous devez spécifier la copie comme attribut de propriété, car ..."
Fattie
20

Par souci de postérité / exhaustivité… Voici deux exemples complets de la façon de mettre en œuvre cette "façon de faire" ridiculement polyvalente. @ La réponse de Robert est parfaitement concise et correcte, mais ici je veux aussi montrer des façons de réellement "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 ouais. Voici une manière 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

Cela illustre la définition de la propriété de bloc via l'accesseur (bien qu'à l'intérieur d'init, une pratique discutable ..) par rapport au mécanisme "getter" "non anatomique" du premier exemple. Dans les deux cas… les implémentations "codées en dur" peuvent toujours être écrasées, par exemple .. a lá ..

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 ... dites que vous voulez utiliser un bloc au lieu d'une "action" cible / action à l'ancienne ... Vous pouvez simplement utiliser des valeurs associées pour, eh bien .. 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

Maintenant, quand vous faites un bouton, vous n'avez pas à mettre en place un IBActiondrame .. Associez simplement le travail à faire à la création ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Ce modèle peut être appliqué OVER et OVER aux API Cocoa. Utiliser les propriétés pour amener les parties pertinentes de votre code rapprocher , éliminer les paradigmes de la délégation alambiquée , et de tirer parti de la puissance des objets au - delà de tout agir comme des « conteneurs » muets.

Alex Gray
la source
Alex, grand exemple associé. Vous savez, je me pose des questions sur le non-atomique. Pensées?
Fattie
2
Il est très rare que "atomique" soit la bonne chose à faire pour une propriété. Ce serait une chose très étrange de définir une propriété de bloc dans un thread et de la lire dans un autre thread en même temps , ou de définir la propriété de bloc simultanément à partir de plusieurs threads. Ainsi, le coût de «atomique» par rapport à «non atomique» ne vous donne aucun avantage réel.
gnasher729
8

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

typedef void(^TestBlock)(void);

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

Dans MRC, les blocs capturant les variables de contexte sont alloués en pile ; ils seront libérés lorsque le cadre de pile sera détruit. S'ils sont copiés, un nouveau bloc sera alloué en tas , qui peut être exécuté plus tard après que le cadre de pile ait été sauté.

Mindy
la source
Exactement. Voici le vrai doco Apple sur pourquoi vous devriez utiliser la copie et rien d'autre. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie
7

Disclamer

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

Bonjour Swift

Vous avez plusieurs façons de passer un bloc équivalent à fonctionner dans Swift.

J'en ai trouvé trois.

Pour comprendre cela je vous propose de tester en terrain de jeu ce petit bout 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)

Rapide, 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 fonction peut être déduite de sorte que vous n'avez pas à la réécrire.

Accéder aux paramètres par numéros

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

Inférence de paramètres avec nommage

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

Fermeture arrière

Ce cas spécial ne fonctionne que si le bloc est le dernier argument, il est appelé fermeture de fin

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

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

Finalement:

En utilisant toute cette puissance, je mélangerais la fermeture de fin et l'inférence de type (avec un nom 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!")
    }
}
Francescu
la source
0

Bonjour Swift

Complétant 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)
Gil Beyruth
la source
-3

Vous pouvez suivre le format ci-dessous et utiliser la testingObjectiveCBlockpropriété dans la classe.

typedef void (^testingObjectiveCBlock)(NSString *errorMsg);

@interface MyClass : NSObject
@property (nonatomic, strong) testingObjectiveCBlock testingObjectiveCBlock;
@end

Pour plus d'informations jetez un œil ici

Sujith Thankachan
la source
2
Cette réponse ajoute-t-elle vraiment quelque chose de plus aux autres réponses déjà fournies?
Richard J. Ross III