Objective-C pass block sebagai parameter

Jawaban:

257

Jenis blok bervariasi tergantung pada argumen dan jenis kembalinya. Dalam kasus umum, tipe blok dideklarasikan dengan cara yang sama dengan tipe pointer fungsi, tetapi mengganti *dengan a ^. Salah satu cara untuk meneruskan blok ke metode adalah sebagai berikut:

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

Tapi seperti yang Anda lihat, itu berantakan. Anda bisa menggunakan a typedefuntuk membuat pembersih jenis blok:

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

Dan kemudian meneruskan blok itu ke metode seperti:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;
Jonathan Grynspan
sumber
Mengapa Anda memberikan id sebagai argumen? Apakah tidak mungkin untuk dengan mudah melewati NSNumber misalnya? Bagaimana kelihatannya?
bas
7
Anda tentu saja dapat melewati argumen yang sangat diketik seperti NSNumber *atau std::string&atau apa pun yang Anda bisa berikan sebagai argumen fungsi. Ini hanya sebuah contoh. (Untuk blok yang setara kecuali untuk diganti iddengan NSNumber, typedefakan menjadi typedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
Jonathan Grynspan
Ini menunjukkan deklarasi metode. Satu masalah dengan blok adalah bahwa gaya deklarasi "berantakan" tidak membuatnya jelas dan mudah untuk menulis panggilan metode aktual dengan argumen blok nyata.
uchuugaka
Typedef tidak hanya membuat kode lebih mudah untuk ditulis, tetapi secara signifikan lebih mudah dibaca karena sintaksis blok / fungsi bukan yang terbersih.
pyj
@ JonathanGrynspan, datang dari dunia Swift tetapi harus menyentuh beberapa kode Objective-C lama, bagaimana saya bisa tahu apakah ada blok yang lolos atau tidak? Saya membaca bahwa secara default, blok keluar kecuali jika didekorasi dengan NS_NOESCAPE, tetapi enumerateObjectsUsingBlocksaya diberi tahu tidak melarikan diri, namun saya tidak melihat NS_NOESCAPEdi mana pun di situs, juga tidak melarikan diri disebutkan sama sekali dalam dokumen Apple. Bisakah kamu menolong?
Mark A. Donohoe
62

Penjelasan termudah untuk pertanyaan ini adalah ikuti templat ini:

1. Blok sebagai parameter metode

Templat

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

Contoh

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

Penggunaan kasus lainnya:

2. Blokir sebagai Properti

Templat

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

Contoh

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

3. Blokir sebagai argumen metode

Templat

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

Contoh

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

4. Blokir sebagai variabel lokal

Templat

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

Contoh

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

5. Blokir sebagai typedef

Templat

typedef returnType (^typeName)(parameters);

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

Contoh

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

completionBlock didComplete = ^(NSArray *array, NSError *error){
    // your code
};
EnriMR
sumber
1
[self saveWithCompletionBlock: ^ (array NSArray *, kesalahan NSError *) {// kode Anda}]; Dalam contoh ini tipe pengembalian diabaikan karena tidak berlaku?
Alex
51

Ini mungkin membantu:

- (void)someFunc:(void(^)(void))someBlock;
quaertym
sumber
Anda kehilangan tanda kurung
newacct
Yang ini bekerja untuk saya sedangkan yang sebelumnya tidak. Btw terima kasih sobat, itu, memang, sangat membantu!
tanou
23

Anda dapat melakukannya seperti ini, melewati blok sebagai parameter blok:

//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
sumber
8

Satu lagi cara untuk melewatkan blok menggunakan fungsi с pada contoh di bawah ini. Saya sudah membuat fungsi untuk melakukan apa saja di latar belakang dan antrian utama.

file blocks.h

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

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

Dari impor blok. H bila perlu dan aktifkan:

- (void)loadInBackground {

    performInBackground(^{

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

        performOnMainQueue(^{
            //completion hadler code on main queue
        });
    });
}
Dren
sumber
6

Anda juga dapat mengatur blok sebagai properti sederhana jika itu berlaku untuk Anda:

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

pastikan bahwa properti blok adalah "salin"!

dan tentu saja Anda juga dapat menggunakan typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;
iiFreeman
sumber
4

Saya selalu cenderung lupa tentang sintaks blok. Ini selalu muncul di pikiran saya ketika saya harus mendeklarasikan blok. Saya harap ini membantu seseorang :)

http://fuckingblocksyntax.com

Juan Sagasti
sumber
Ini menghemat waktu saya
Le Ding
3

Saya menulis sebuahlockBlock untuk kelas yang akan mengembalikan nilai-nilai dadu setelah mereka diguncang:

  1. Tentukan typedef dengan returnType ( deklarasi di .hatas @interface)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. Tentukan a @propertyuntuk blok ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
  3. Tetapkan metode dengan finishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  4. Masukkan metode yang didefinisikan sebelumnya dalam .mfile dan komit finishBlockuntuk @propertydidefinisikan sebelumnya

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
  5. Untuk memicu completionBlocklulus Variabel jenis yang telah ditentukan sebelumnya (Jangan lupa untuk memeriksa apakah completionBlockada)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }
Alex Cio
sumber
2

Terlepas dari jawaban yang diberikan pada utas ini, saya benar-benar berjuang untuk menulis suatu fungsi yang akan mengambil Blok sebagai fungsi - dan dengan parameter. Akhirnya, inilah solusi yang saya buat.

Saya ingin menulis fungsi umum loadJSONthread, yang akan mengambil URL Layanan Web JSON, memuat beberapa data JSON dari URL ini di utas latar belakang, lalu mengembalikan hasil NSArray * kembali ke fungsi pemanggilan.

Pada dasarnya, saya ingin menyembunyikan semua kompleksitas background-thread dalam fungsi generik yang dapat digunakan kembali.

Inilah cara saya memanggil fungsi ini:

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

} ];

... dan ini adalah bagian yang saya perjuangkan: cara mendeklarasikannya, dan cara membuatnya memanggil fungsi Blok begitu data dimuat, dan meneruskan BlockNSArray * dari catatan yang dimuat:

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

        });
    });
}

Pertanyaan StackOverflow ini menyangkut cara memanggil fungsi, melewati Blok sebagai parameter, jadi saya menyederhanakan kode di atas, dan tidak termasuk loadJSONDataFromURL fungsi.

Tetapi, jika Anda tertarik, Anda dapat menemukan salinan fungsi pemuatan JSON ini di blog ini: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

Semoga ini bisa membantu beberapa pengembang XCode lainnya! (Jangan lupa memilih pertanyaan ini dan jawaban saya, jika ya!)

Mike Gledhill
sumber
1
Ini benar-benar salah satu trik terbaik yang pernah saya lihat untuk ios dan blok. Love it man !!!!
portforwardpodcast
1

Templat lengkapnya terlihat seperti

- (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
sumber