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;

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

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;

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

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

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;



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

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


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 {

}
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.