Posso usare i blocchi Objective-C come proprietà?


321

È possibile avere blocchi come proprietà usando la sintassi delle proprietà standard?

Ci sono cambiamenti per ARC ?


1
Bene, perché sarebbe molto utile. Non avrei bisogno di sapere che cosa è finché ho la sintassi giusta e si comporta come un oggetto NSO.
Gurghet,

5
Se non sai di cosa si tratta, come fai a sapere che sarebbe molto utile?
Stephen Canon,

5
Non dovresti usarli Se non sai cosa sono :)
Richard J. Ross III,

5
@ Ecco alcune ragioni che mi vengono in mente. I blocchi sono più facili da implementare rispetto a una classe delegata completa, i blocchi sono leggeri e si ha accesso alle variabili che si trovano nel contesto di quel blocco. I callback degli eventi possono essere eseguiti in modo efficace usando i blocchi (cocos2d li usa quasi esclusivamente).
Richard J. Ross III,

2
Non completamente correlato, ma poiché alcuni dei commenti si lamentano della sintassi del blocco "brutta", ecco un ottimo articolo che deriva la sintassi dai primi principi: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler,

Risposte:


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

Se stai per ripetere lo stesso blocco in più punti, usa un tipo def

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

3
Con xCode 4.4 o successivo non è necessario sintetizzare. Ciò lo renderà ancora più conciso. Apple Doc
Eric

wow, non lo sapevo, grazie! ... Anche se lo faccio spesso@synthesize myProp = _myProp
Robert

7
@Robert: sei di nuovo fortunato, perché senza mettere @synthesizeil valore predefinito è quello che stai facendo @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric

1
@CharlieMonroe - Sì, probabilmente hai ragione, ma non hai bisogno di un'implementazione dealloc per azzerare o rilasciare la proprietà del blocco senza ARC? (è da un po 'che non uso ARC)
Robert

1
@imcaptor: Sì, può causare perdite di memoria nel caso in cui non lo si rilasci in dealloc, proprio come con qualsiasi altra variabile.
Charlie Monroe,

210

Ecco un esempio di come svolgere un simile compito:

#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;
}

Ora, l'unica cosa che dovrebbe cambiare se fosse necessario cambiare il tipo di confronto sarebbe la typedef int (^IntBlock)(). Se è necessario passarci due oggetti, cambiarlo in questo: typedef int (^IntBlock)(id, id)e cambiare il blocco in:

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

Spero che aiuti.

MODIFICA 12 marzo 2012:

Per ARC, non sono necessarie modifiche specifiche, poiché ARC gestirà i blocchi per te purché siano definiti come copia. Non è nemmeno necessario impostare la proprietà su zero nel distruttore.

Per ulteriori informazioni, consulta questo documento: http://clang.llvm.org/docs/AutomaticReferenceCounting.html


158

Per Swift, basta usare le chiusure: esempio.


In Objective-C:

@property (copy) void

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

È così semplice.

Ecco la documentazione Apple attuale, che indica esattamente cosa usare:

Documento Apple.

Nel tuo file .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".

Ecco il tuo file .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();
    }

Attenzione al codice di esempio non aggiornato.

Con i sistemi moderni (2014+), fai ciò che viene mostrato qui. È così semplice


Forse dovresti anche dire che ora (2016) va bene usare stronginvece di copy?
Nik Kov,

Puoi spiegare perché la proprietà non dovrebbe essere nonatomicdiversa dalle best practice per la maggior parte degli altri casi che usano le proprietà?
Alex Pretzlav,

WorkingwithBlocks.html da Apple "Devi specificare copia come attributo della proprietà, perché ..."
Fattie

20

Per i posteri / completezza ... Ecco due esempi COMPLETI di come implementare questo "modo di fare" ridicolmente versatile. @La risposta di Robert è beatamente concisa e corretta, ma qui voglio anche mostrare modi per "definire" effettivamente i blocchi.

@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

Sciocco? Sì. Utile? Hells sì. Ecco un modo diverso, più "atomico" di impostare la proprietà ... e una classe che è ridicolmente 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

Questo illustra l'impostazione della proprietà del blocco tramite l'accessorio (anche se all'interno di init, una pratica discutibilmente rischiosa ..) rispetto al meccanismo "getter" del primo esempio. In entrambi i casi ... le implementazioni "hardcoded" possono sempre essere sovrascritte, per esempio .. a lá ..

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

Inoltre .. se si desidera aggiungere una proprietà di blocco in una categoria ... dire che si desidera utilizzare un blocco anziché una "azione" di destinazione / azione della vecchia scuola ... È possibile utilizzare solo i valori associati per, beh .. associare i blocchi.

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

Ora, quando si crea un pulsante, non è necessario impostare alcun IBActiondramma .. Basta associare il lavoro da svolgere al momento della creazione ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Questo modello può essere applicato SOPRA e SOPRA alle API Cocoa. Utilizzare le proprietà di portare le parti pertinenti del codice più vicini , eliminare paradigmi delegazione contorti , e sfruttare la potenza degli oggetti oltre che di proprio in qualità di "contenitori" muti.


Alex, ottimo esempio associato. Sai, mi chiedo del nonatomico. Pensieri?
Fattie,

2
È molto raro che "atomico" sia la cosa giusta da fare per una proprietà. Sarebbe una cosa molto strana per impostare una proprietà blocco in un thread e leggere in un altro thread contemporaneamente , o per impostare la proprietà blocco contemporaneamente da più thread. Quindi il costo di "atomico" rispetto a "nonatomico" non offre alcun vantaggio reale.
gnasher729,

8

Ovviamente potresti usare i blocchi come proprietà. Ma assicurati che siano dichiarati come @property (copia) . Per esempio:

typedef void(^TestBlock)(void);

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

In MRC, i blocchi che acquisiscono variabili di contesto sono allocati in pila ; verranno rilasciati quando il frame dello stack viene distrutto. Se vengono copiati, un nuovo blocco verrà allocato nell'heap , che può essere eseguito in seguito dopo che il frame dello stack è stato popolato.


Esattamente. Ecco il vero documento Apple sul perché esattamente dovresti usare la copia e nient'altro. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie

7

Disclamer

Non si intende che questa sia "la buona risposta", poiché questa domanda richiede esplicitamente ObjectiveC. Mentre Apple presentava Swift al WWDC14, mi piacerebbe condividere i diversi modi di utilizzare il blocco (o le chiusure) in Swift.

Ciao Swift

Sono disponibili molti modi per passare un blocco equivalente alla funzione in Swift.

Ne ho trovati tre.

Per capirlo ti consiglio di provare nel parco giochi questo piccolo pezzo di codice.

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)

Rapido, ottimizzato per chiusure

Poiché Swift è ottimizzato per lo sviluppo asincrono, Apple ha lavorato di più sulle chiusure. Il primo è che la firma della funzione può essere dedotta, quindi non è necessario riscriverla.

Accedi ai parametri tramite numeri

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

Inferenza di Params con la denominazione

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

Chiusura finale

Questo caso speciale funziona solo se il blocco è l'ultimo argomento, si chiama chiusura finale

Ecco un esempio (unito alla firma inferita per mostrare la potenza di Swift)

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

Finalmente:

Usando tutto questo potere ciò che farei è mescolare la chiusura finale e l'inferenza del tipo (con denominazione per leggibilità)

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

0

Ciao Swift

A complemento delle risposte di @Francescu.

Aggiunta di parametri extra:

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)

-3

È possibile seguire il formato seguente e utilizzare la testingObjectiveCBlockproprietà nella classe.

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

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

Per maggiori informazioni dai un'occhiata qui


2
Questa risposta aggiunge davvero qualcosa in più alle altre risposte già fornite?
Richard J. Ross III,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.