Kann ich Objective-C-Blöcke als Eigenschaften verwenden?


321

Ist es möglich, Blöcke als Eigenschaften mit der Standardeigenschaftssyntax zu haben?

Gibt es Änderungen für ARC ?


1
Nun, weil es sehr praktisch wäre. Ich würde nicht wissen müssen, was es ist, solange ich die richtige Syntax habe und es sich wie ein NSObject verhält.
Gurghet

5
Wenn Sie nicht wissen, was es ist, woher wissen Sie, dass es sehr praktisch wäre?
Stephen Canon

5
Sie sollten sie nicht verwenden, wenn Sie nicht wissen, was sie sind :)
Richard J. Ross III

5
@ Moshe hier sind einige Gründe, die mir in den Sinn kommen. Blöcke sind einfacher zu implementieren als eine vollständige Delegatenklasse, Blöcke sind leichtgewichtig und Sie haben Zugriff auf Variablen, die sich im Kontext dieses Blocks befinden. Event Callbacks können effektiv mit Blöcken durchgeführt werden (cocos2d verwendet sie fast ausschließlich).
Richard J. Ross III

2
Nicht vollständig verwandt, aber da sich einige Kommentare über die "hässliche" Blocksyntax
Paulrehkugler

Antworten:


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

Wenn Sie denselben Block an mehreren Stellen wiederholen möchten, verwenden Sie den Typ def

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

3
Mit xCode 4.4 oder neuer müssen Sie nicht synthetisieren. Das wird es noch prägnanter machen. Apple Doc
Eric

wow, das wusste ich nicht, danke! ... obwohl ich es oft tue@synthesize myProp = _myProp
Robert

7
@ Robert: Sie haben wieder Glück, denn ohne @synthesizedie Standardeinstellung zu setzen, tun Sie @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric

1
@CharlieMonroe - Ja, Sie haben wahrscheinlich Recht, aber benötigen Sie keine Dealloc-Implementierung, um die Block-Eigenschaft ohne ARC auf Null zu setzen oder freizugeben? (Es ist schon eine Weile her, seit ich Nicht-ARC verwendet habe)
Robert

1
@imcaptor: Ja, es kann zu Speicherverlusten führen, falls Sie es nicht im Dealloc freigeben - genau wie bei jeder anderen Variablen.
Charlie Monroe

210

Hier ist ein Beispiel, wie Sie eine solche Aufgabe erfüllen würden:

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

Das einzige, was sich ändern müsste, wenn Sie die Art des Vergleichs ändern müssten, wäre das typedef int (^IntBlock)(). Wenn Sie zwei Objekte übergeben müssen, ändern Sie dies in: typedef int (^IntBlock)(id, id)und ändern Sie Ihren Block in:

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

Ich hoffe das hilft.

BEARBEITEN 12. März 2012:

Für ARC sind keine spezifischen Änderungen erforderlich, da ARC die Blöcke für Sie verwaltet, solange sie als Kopie definiert sind. Sie müssen die Eigenschaft in Ihrem Destruktor auch nicht auf Null setzen.

Weitere Informationen finden Sie in diesem Dokument: http://clang.llvm.org/docs/AutomaticReferenceCounting.html


158

Verwenden Sie für Swift einfach Verschlüsse: Beispiel.


In Ziel-C:

@property (Kopie) nichtig

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

So einfach ist das.

Hier ist die aktuelle Apple-Dokumentation, in der genau angegeben ist, was verwendet werden soll:

Apple Doco.

In Ihrer .h-Datei:

// 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".

Hier ist Ihre .m-Datei:

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

Achten Sie auf veralteten Beispielcode.

Machen Sie mit modernen Systemen (2014+) das, was hier gezeigt wird. So einfach ist das.


Vielleicht solltest du auch sagen, dass es jetzt (2016) in Ordnung ist, strongstatt zu verwenden copy?
Nik Kov

Können Sie erklären, warum die Eigenschaft in den nonatomicmeisten anderen Fällen, in denen Eigenschaften verwendet werden, nicht anders sein sollte als Best Practices?
Alex Pretzlav

WorkingwithBlocks.html von Apple "Sie sollten copy als Eigenschaftsattribut angeben, weil ..."
Fattie

20

Der Nachwelt / Vollständigkeit halber ... Hier sind zwei vollständige Beispiele, wie man diese lächerlich vielseitige "Art, Dinge zu tun" umsetzt. @ Roberts Antwort ist selig präzise und korrekt, aber hier möchte ich auch Wege zeigen, wie man die Blöcke tatsächlich "definiert".

@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

Dumm? Ja. Nützlich? Höllen ja. Hier ist eine andere, "atomarere" Art, die Eigenschaft festzulegen ... und eine Klasse, die lächerlich nützlich ist ...

@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

Dies zeigt, wie die Blockeigenschaft über den Accessor (wenn auch innerhalb von init, eine umstrittene heikle Praxis) gegenüber dem "nichtatomaren" "Getter" -Mechanismus des ersten Beispiels festgelegt wird. In beiden Fällen… können die "fest codierten" Implementierungen immer überschrieben werden, pro Instanz .. a lá ..

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

Außerdem ... wenn Sie eine Blockeigenschaft zu einer Kategorie hinzufügen möchten ... sagen Sie, Sie möchten einen Block anstelle einer alten Aktion / Aktion "Aktion" verwenden ... Sie können einfach zugehörige Werte verwenden, um ... Ordnen Sie die Blöcke zu.

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

Wenn Sie jetzt einen Knopf drücken, müssen Sie kein IBActionDrama einrichten . Verknüpfen Sie einfach die Arbeit, die bei der Erstellung zu erledigen ist ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Dieses Muster kann OVER und OVER auf Cocoa-APIs angewendet werden . Verwenden Sie Eigenschaften, um die relevanten Teile Ihres Codes näher zusammenzubringen , verschlungene Delegierungsparadigmen zu beseitigen und die Leistung von Objekten zu nutzen, die über die bloße Funktion als dumme "Container" hinausgeht.


Alex, tolles assoziiertes Beispiel. Weißt du, ich wundere mich über das Nichtatomare. Gedanken?
Fattie

2
Es ist sehr selten, dass "atomar" das Richtige für eine Immobilie ist. Es wäre sehr seltsam, eine Blockeigenschaft in einem Thread festzulegen und gleichzeitig in einem anderen Thread zu lesen oder die Blockeigenschaft gleichzeitig von mehreren Threads aus festzulegen. Die Kosten für "atomar" gegenüber "nichtatomar" bieten Ihnen also keine wirklichen Vorteile.
Gnasher729

8

Natürlich können Sie Blöcke als Eigenschaften verwenden. Stellen Sie jedoch sicher, dass sie als @property (Kopie) deklariert sind . Zum Beispiel:

typedef void(^TestBlock)(void);

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

In MRC werden Blöcke, die Kontextvariablen erfassen, im Stapel zugewiesen . Sie werden freigegeben, wenn der Stapelrahmen zerstört wird. Wenn sie kopiert werden, wird ein neuer Block im Heap zugewiesen , der später ausgeführt werden kann, nachdem der Stapelrahmen eingefügt wurde.


Genau. Hier ist das eigentliche Apple-Dokument, warum Sie eine Kopie verwenden sollten und sonst nichts. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie

7

Disclamer

Dies soll nicht "die gute Antwort" sein, da diese Frage ausdrücklich nach ObjectiveC fragt. Als Apple Swift auf der WWDC14 vorstellte, möchte ich Ihnen die verschiedenen Möglichkeiten zur Verwendung von Blöcken (oder Verschlüssen) in Swift vorstellen.

Hallo, Swift

Sie haben viele Möglichkeiten, einen Block zu übergeben, der der Funktion in Swift entspricht.

Ich habe drei gefunden.

Um dies zu verstehen, empfehle ich Ihnen, dieses kleine Stück Code auf dem Spielplatz zu testen.

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)

Schnell, optimiert für Verschlüsse

Da Swift für die asynchrone Entwicklung optimiert ist, hat Apple mehr an Schließungen gearbeitet. Die erste ist, dass die Funktionssignatur abgeleitet werden kann, sodass Sie sie nicht neu schreiben müssen.

Greifen Sie über Nummern auf Parameter zu

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

Params Rückschluss auf die Benennung

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

Trailing Closure

Dieser Sonderfall funktioniert nur, wenn der Block das letzte Argument ist. Er wird als abschließender Abschluss bezeichnet

Hier ist ein Beispiel (zusammengeführt mit der abgeleiteten Signatur, um die Swift-Leistung zu zeigen)

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

Schließlich:

Mit all dieser Kraft würde ich Trailing Closure und Typinferenz mischen (mit Benennung zur besseren Lesbarkeit).

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

Hallo, Swift

Ergänzt die Antwort von @Francescu.

Hinzufügen zusätzlicher Parameter:

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

Sie können dem folgenden Format folgen und die testingObjectiveCBlockEigenschaft in der Klasse verwenden.

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

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

Weitere Informationen finden Sie hier


2
Fügt diese Antwort den anderen bereits bereitgestellten Antworten wirklich noch etwas hinzu?
Richard J. Ross III
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.