Czy mogę używać bloków Objective-C jako właściwości?


321

Czy można mieć bloki jako właściwości przy użyciu standardowej składni właściwości?

Czy są jakieś zmiany w ARC ?


1
Cóż, ponieważ byłoby to bardzo przydatne. Nie musiałbym wiedzieć, co to jest, dopóki mam odpowiednią składnię i zachowuje się jak obiekt NSO.
gurghet

5
Jeśli nie wiesz, co to jest, skąd wiesz, że byłoby to bardzo przydatne?
Stephen Canon

5
Nie powinieneś ich używać, jeśli nie wiesz, czym one są :)
Richard J. Ross III

5
@Moshe oto kilka powodów, które przychodzą mi na myśl. Bloki są łatwiejsze do wdrożenia niż pełna klasa delegata, bloki są lekkie i masz dostęp do zmiennych, które są w kontekście tego bloku. Wywołania zwrotne zdarzeń można skutecznie wykonywać za pomocą bloków (cocos2d używa ich prawie wyłącznie).
Richard J. Ross III

2
Nie do końca powiązane, ale ponieważ niektóre komentarze narzekają na „brzydką” składnię bloków, oto świetny artykuł, który wywodzi składnię z pierwszych zasad: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler

Odpowiedzi:


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

Jeśli zamierzasz powtarzać ten sam blok w kilku miejscach, użyj def

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

3
Z xCode 4.4 lub nowszym nie musisz syntezować. Dzięki temu będzie jeszcze bardziej zwięzły. Apple Doc
Eric

wow, nie wiedziałem tego, dzięki! ... Chociaż często to robię@synthesize myProp = _myProp
Robert,

7
@Robert: Znowu masz szczęście, ponieważ bez @synthesizeustawiania domyślnego jest to, co robisz @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric,

1
@CharlieMonroe - Tak, prawdopodobnie masz rację, ale czy nie potrzebujesz implementacji dealloc, aby zerować lub zwolnić właściwość bloku bez ARC? (minęło trochę czasu, odkąd użyłem non-ARC)
Robert

1
@imcaptor: Tak, może powodować wycieki pamięci, jeśli nie zwolnisz go w dealloc - tak jak w przypadku każdej innej zmiennej.
Charlie Monroe,

210

Oto przykład, w jaki sposób możesz wykonać takie zadanie:

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

Teraz jedyną rzeczą, która musiałaby się zmienić, gdybyś musiał zmienić typ porównania, byłaby typedef int (^IntBlock)(). Jeśli musisz przekazać do niego dwa obiekty, zmień to na: typedef int (^IntBlock)(id, id)i zmień blok na:

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

Mam nadzieję, że to pomoże.

EDYCJA 12 marca 2012:

W przypadku ARC nie są wymagane żadne konkretne zmiany, ponieważ ARC będzie zarządzać blokami, o ile są one zdefiniowane jako kopiowanie. Nie musisz również ustawiać właściwości na zero w swoim destruktorze.

Więcej informacji można znaleźć w tym dokumencie: http://clang.llvm.org/docs/AutomaticReferenceCounting.html


158

W przypadku Swift wystarczy użyć zamknięć: przykład.


W celu C:

@property (copy) void

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

To takie proste.

Oto aktualna dokumentacja Apple, która dokładnie określa, czego użyć:

Dokument Apple

W twoim pliku .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".

Oto twój plik .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();
    }

Uważaj na nieaktualny przykładowy kod.

W nowoczesnych systemach (2014+) rób to, co pokazano tutaj. To takie proste.


Może powinieneś również powiedzieć, że teraz (2016) można używać strongzamiast copy?
Nik Kov

Czy możesz wyjaśnić, dlaczego właściwość nie powinna być nonatomicodmienna od najlepszych praktyk w większości innych przypadków korzystania z właściwości?
Alex Pretzlav

WorkingwithBlocks.html z Apple'a „Powinieneś określić kopię jako atrybut właściwości, ponieważ ...”
Fattie

20

Dla potomności / kompletności… Oto dwa PEŁNE przykłady wdrożenia tego absurdalnie wszechstronnego „sposobu robienia rzeczy”. @ Odpowiedź Roberta jest błogo zwięzła i poprawna, ale tutaj chcę również pokazać sposoby „zdefiniowania” bloków.

@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

Głupi? Tak. Przydatny?Do diabła Oto inny, „bardziej atomowy” sposób ustawiania właściwości .. i klasa, która jest absurdalnie przydatna…

@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

To ilustruje ustawianie właściwości bloku za pomocą akcesorium (choć wewnątrz init, co jest dyskusyjnie dicey praktyką ..) w porównaniu z mechanizmem „nieatomowego” „gettera” z pierwszego przykładu. W obu przypadkach… implementacje „zakodowane na stałe” zawsze można nadpisać, na przykład .. a lá ..

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

Również ... jeśli chcesz dodać właściwość bloku do kategorii ... powiedz, że chcesz użyć bloku zamiast jakiejś oldskulowej „akcji” celu / akcji ... Możesz po prostu użyć powiązanych wartości do, cóż ... skojarzyć bloki.

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

Teraz, kiedy naciśniesz przycisk, nie musisz ustawiać IBActiondramatu. Po prostu powiąż pracę, którą należy wykonać przy tworzeniu ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Ten wzór można zastosować OVER i OVER do API Cocoa. Właściwości mechanicznych przynieść odpowiednie fragmenty kodu bliżej siebie , eliminować zawiłe paradygmaty delegowania , i wykorzystać moc obiektów poza tym po prostu działając jako głupi „kontenerów”.


Alex, świetny powiązany przykład. Wiesz, zastanawiam się nad tym, co nieatomowe. Myśli?
Fattie

2
Bardzo rzadko „atomowy” byłby właściwym rozwiązaniem dla nieruchomości. Byłoby bardzo dziwne, aby ustawić właściwość bloku w jednym wątku i czytać go w innym wątku w tym samym czasie lub ustawić właściwość bloku jednocześnie z wielu wątków. Koszt „atomowy” vs. „nieatomowy” nie daje żadnych realnych korzyści.
gnasher729

8

Oczywiście możesz użyć bloków jako właściwości. Ale upewnij się, że są zadeklarowane jako @property (kopia) . Na przykład:

typedef void(^TestBlock)(void);

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

W MRC bloki przechwytujące zmienne kontekstowe są przydzielane na stosie ; zostaną zwolnione, gdy rama stosu zostanie zniszczona. Jeśli zostaną skopiowane, nowy blok zostanie przydzielony na stosie , który można wykonać później po wyskakowaniu ramki stosu.


Dokładnie. Oto faktyczne doco firmy Apple na temat tego, dlaczego powinieneś używać kopiowania i nic więcej. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie

7

Disclamer

To nie ma być „dobra odpowiedź”, ponieważ w tym pytaniu zadaje się wyraźnie Objective-C. Gdy Apple przedstawił Swift na WWDC14, chciałbym podzielić się różnymi sposobami używania bloków (lub zamknięć) w Swift.

Cześć, Szybki

Masz wiele sposobów na przekazanie bloku odpowiadającego funkcji w Swift.

Znalazłem trzy.

Aby to zrozumieć, sugeruję przetestowanie na placu zabaw tego małego fragmentu kodu.

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)

Szybki, zoptymalizowany do zamknięć

Ponieważ Swift jest zoptymalizowany pod kątem rozwoju asynchronicznego, Apple pracował więcej na zamknięciach. Po pierwsze, można wnioskować o podpisie funkcji, więc nie trzeba go przepisywać.

Dostęp do parametrów według numerów

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

Wnioskowanie z nazwami

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

Zamknięcie końcowe

Ten szczególny przypadek działa tylko wtedy, gdy blok jest ostatnim argumentem, nazywa się to końcowym zamknięciem

Oto przykład (połączony z wywnioskowanym podpisem, aby pokazać moc Swift)

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

Wreszcie:

Wykorzystując całą tę moc, chciałbym połączyć końcowe zamykanie i wnioskowanie o typie (z nazewnictwem dla czytelności)

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

Cześć, Szybki

Uzupełnienie odpowiedzi udzielonej przez @Francescu.

Dodanie dodatkowych parametrów:

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

Możesz postępować zgodnie z poniższym formatem i możesz użyć testingObjectiveCBlockwłaściwości w klasie.

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

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

Aby uzyskać więcej informacji, zajrzyj tutaj


2
Czy ta odpowiedź naprawdę dodaje coś więcej do innych już dostarczonych odpowiedzi?
Richard J. Ross III
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.