Blok przejścia celu-C jako parametr

Odpowiedzi:

256

Typ bloku różni się w zależności od jego argumentów i typu zwracanego. W ogólnym przypadku typy bloków są deklarowane w taki sam sposób, jak typy wskaźników do funkcji, ale zastępując *znak ^. Jeden ze sposobów przekazania bloku do metody jest następujący:

- (void)iterateWidgets:(void (^)(id, int))iteratorBlock;

Ale jak widać, jest to niechlujne. Zamiast tego możesz użyć a, typedefaby uczynić typy bloków czystszymi:

typedef void (^ IteratorBlock)(id, int);

Następnie przekaż ten blok do takiej metody:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;
Jonathan Grynspan
źródło
Dlaczego podajesz id jako argument? Czy na przykład nie jest możliwe łatwe przekazanie numeru NSNumber? Jak by to wyglądało?
bas
7
Można oczywiście zdać silnie wpisany argumentu takiego jak NSNumber *lub std::string&czy cokolwiek innego można przekazać jako argument funkcji. To tylko przykład. (Dla bloku, który jest równoważny, z wyjątkiem zastąpienia idprzez NSNumber, typedefbyłoby typedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
Jonathan Grynspan
To pokazuje deklarację metody. Problem z blokami polega na tym, że styl deklaracji „niechlujny” nie pozwala jasno i łatwo napisać rzeczywistego wywołania metody z prawdziwym argumentem blokowym.
uchuugaka
Typedefs nie tylko ułatwiają pisanie kodu, ale są też znacznie łatwiejsze do odczytania, ponieważ składnia wskaźnika bloków / funkcji nie jest najczystsza.
pyj
@JonathanGrynspan, pochodzący ze świata Swift, ale po dotknięciu jakiegoś starego kodu Objective-C, jak mogę stwierdzić, czy blok ucieka, czy nie? Czytałem, że domyślnie bloki uciekają, chyba że są ozdobione NS_NOESCAPE, ale enumerateObjectsUsingBlockpowiedziano mi, że nie są uciekające, ale nie widzę NS_NOESCAPEnigdzie w witrynie ani w ogóle nie wspomniano o ucieczce w dokumentach Apple. Możesz pomóc?
Mark A. Donohoe
62

Najłatwiejszym wyjaśnieniem tego pytania jest skorzystanie z tych szablonów:

1. Blok jako parametr metody

Szablon

- (void)aMethodWithBlock:(returnType (^)(parameters))blockName {
        // your code
}

Przykład

-(void) saveWithCompletionBlock: (void (^)(NSArray *elements, NSError *error))completionBlock{
        // your code
}

Inne zastosowania przypadków:

2. Blok jako właściwość

Szablon

@property (nonatomic, copy) returnType (^blockName)(parameters);

Przykład

@property (nonatomic,copy)void (^completionBlock)(NSArray *array, NSError *error);

3. Blok jako argument metody

Szablon

[anObject aMethodWithBlock: ^returnType (parameters) {
    // your code
}];

Przykład

[self saveWithCompletionBlock:^(NSArray *array, NSError *error) {
    // your code
}];

4. Blokuj jako zmienną lokalną

Szablon

returnType (^blockName)(parameters) = ^returnType(parameters) {
    // your code
};

Przykład

void (^completionBlock) (NSArray *array, NSError *error) = ^void(NSArray *array, NSError *error){
    // your code
};

5. Block as a typedef

Szablon

typedef returnType (^typeName)(parameters);

typeName blockName = ^(parameters) {
    // your code
}

Przykład

typedef void(^completionBlock)(NSArray *array, NSError *error);

completionBlock didComplete = ^(NSArray *array, NSError *error){
    // your code
};
EnriMR
źródło
1
[self saveWithCompletionBlock: ^ (tablica NSArray *, błąd NSError *) {// twój kod}]; W tym przykładzie zwracany typ jest ignorowany, ponieważ jest nieważny?
Alex
51

Może to być pomocne:

- (void)someFunc:(void(^)(void))someBlock;
quaertym
źródło
brakuje ci nawiasów
nowość
Ten działał dla mnie, podczas gdy poprzedni nie. Przy okazji, kolego, to było rzeczywiście pomocne!
tanou
23

Możesz to zrobić, przekazując blok jako parametr bloku:

//creating a block named "completion" that will take no arguments and will return void
void(^completion)() = ^() {
    NSLog(@"bbb");
};

//creating a block namd "block" that will take a block as argument and will return void
void(^block)(void(^completion)()) = ^(void(^completion)()) {
    NSLog(@"aaa");
    completion();
};

//invoking block "block" with block "completion" as argument
block(completion);
Aleksei Minaev
źródło
8

Jeszcze jeden sposób na przekazanie bloku przy użyciu funkcji с w przykładzie poniżej. Stworzyłem funkcje, które wykonują wszystko w tle i w głównej kolejce.

plik blocks.h

void performInBackground(void(^block)(void));
void performOnMainQueue(void(^block)(void));

plik blocks.m

#import "blocks.h"

void performInBackground(void(^block)(void)) {
    if (nil == block) {
        return;
    }

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block);
}

void performOnMainQueue(void(^block)(void)) {
    if (nil == block) {
        return;
    }

    dispatch_async(dispatch_get_main_queue(), block);
}

Następnie zaimportuj bloki.h w razie potrzeby i wywołaj go:

- (void)loadInBackground {

    performInBackground(^{

        NSLog(@"Loading something in background");
        //loading code

        performOnMainQueue(^{
            //completion hadler code on main queue
        });
    });
}
Dren
źródło
6

Możesz również ustawić blok jako prostą właściwość, jeśli ma to zastosowanie w Twoim przypadku:

@property (nonatomic, copy) void (^didFinishEditingHandler)(float rating, NSString *reviewString);

upewnij się, że właściwość bloku to „kopia”!

i oczywiście możesz też użyć typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;
iiFreeman
źródło
4

Zawsze zapominam o składni bloków. To zawsze przychodzi mi na myśl, gdy muszę zadeklarować blok. Mam nadzieję, że komuś to pomoże :)

http://fuckingblocksyntax.com

Juan Sagasti
źródło
To zaoszczędziło mi czasu
Le Ding
3

Napisałem completeBlock dla klasy, która zwróci wartości kostek po ich wstrząśnięciu:

  1. Zdefiniuj typedef z returnType ( .hpowyżej @interfacedeklaracji)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. Zdefiniuj a @propertydla bloku ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
  3. Zdefiniuj metodę za pomocą finishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  4. Wkładka poprzedniej metody zdefiniowanej w .mpliku i zobowiązać się finishBlockdo @propertyzdefiniowanych wcześniej

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
  5. Aby wyzwolić, completionBlockpodaj do niego predefiniowany typ zmiennej (nie zapomnij sprawdzić, czy completionBlockistnieje)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
Alex Cio
źródło
2

Pomimo odpowiedzi udzielonych w tym wątku, naprawdę starałem się napisać funkcję, która przyjmowałaby blok jako funkcję - i to z parametrem. Ostatecznie oto rozwiązanie, które wymyśliłem.

Chciałem napisać funkcję ogólną loadJSONthread, która pobierałaby adres URL usługi internetowej JSON, ładowała niektóre dane JSON z tego adresu URL w wątku w tle, a następnie zwracała wyniki NSArray * z powrotem do funkcji wywołującej.

Zasadniczo chciałem ukryć całą złożoność wątku tła w ogólnej funkcji wielokrotnego użytku.

Oto jak nazwałbym tę funkcję:

NSString* WebServiceURL = @"http://www.inorthwind.com/Service1.svc/getAllCustomers";

[JSONHelper loadJSONthread:WebServiceURL onLoadedData:^(NSArray *results) {

    //  Finished loading the JSON data
    NSLog(@"Loaded %lu rows.", (unsigned long)results.count);

    //  Iterate through our array of Company records, and create/update the records in our SQLite database
    for (NSDictionary *oneCompany in results)
    {
        //  Do something with this Company record (eg store it in our SQLite database)
    }

} ];

... i to jest bit, z którym się zmagałem: jak to zadeklarować i jak zmusić go do wywołania funkcji Block po załadowaniu danych i przekazania BlockNSArray * załadowanych rekordów:

+(void)loadJSONthread:(NSString*)urlString onLoadedData:(void (^)(NSArray*))onLoadedData
{
    __block NSArray* results = nil;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{

        // Call an external function to load the JSON data 
        NSDictionary * dictionary = [JSONHelper loadJSONDataFromURL:urlString];
        results = [dictionary objectForKey:@"Results"];

        dispatch_async(dispatch_get_main_queue(), ^{

            // This code gets run on the main thread when the JSON has loaded
            onLoadedData(results);

        });
    });
}

To pytanie StackOverflow dotyczy tego, jak wywoływać funkcje, przekazując Block jako parametr, więc uprościłem powyższy kod i nie uwzględniłem loadJSONDataFromURLfunkcji.

Ale jeśli jesteś zainteresowany, możesz znaleźć kopię tej funkcji ładowania JSON na tym blogu: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

Mam nadzieję, że pomoże to innym programistom XCode! (Nie zapomnij zagłosować na to pytanie i moją odpowiedź, jeśli tak!)

Mike Gledhill
źródło
1
To naprawdę jedna z najlepszych sztuczek, jakie widziałem dla iOS i bloków. Uwielbiam to człowieku !!!!
portforwardpodcast
1

Wygląda jak pełny szablon

- (void) main {
    //Call
    [self someMethodWithSuccessBlock:^{[self successMethod];}
                    withFailureBlock:^(NSError * error) {[self failureMethod:error];}];
}

//Definition
- (void) someMethodWithSuccessBlock:(void (^) (void))successBlock
                   withFailureBlock:(void (^) (NSError*))failureBlock {

    //Execute a block
    successBlock();

//    failureBlock([[NSError alloc]init]);

}

- (void) successMethod {

}

- (void) failureMethod:(NSError*) error {

}
yoAlex5
źródło