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.