Objective-C pass block เป็นพารามิเตอร์


145

ฉันจะส่งผ่านBlockไปยัง a Function/ ได้Methodอย่างไร

ฉันพยายาม- (void)someFunc:(__Block)someBlockโดยไม่มีประโยชน์

กล่าวคือ อะไรคือสิ่งที่ชนิดหาBlock?


7
การอ้างอิงที่ฉันใช้มากกว่าที่ฉันสนใจที่จะยอมรับ: goshdarnblocksyntax.com
oltman

คำตอบ:


257

ประเภทของบล็อกจะแตกต่างกันไปขึ้นอยู่กับอาร์กิวเมนต์และประเภทการส่งคืน ในกรณีทั่วไปประเภทบล็อกมีการประกาศในลักษณะเดียวกันประเภทตัวชี้ฟังก์ชันมี แต่แทนที่ด้วย* ^วิธีหนึ่งในการส่งบล็อกไปยังเมธอดมีดังนี้:

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

แต่อย่างที่คุณเห็นนั่นมันยุ่ง คุณสามารถใช้ a typedefเพื่อสร้างประเภทบล็อกที่สะอาดขึ้นแทน:

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

จากนั้นส่งผ่านบล็อกนั้นไปยังวิธีการดังนี้:

- (void)iterateWidgets:(IteratorBlock)iteratorBlock;

ทำไมคุณผ่าน ID เป็นอาร์กิวเมนต์? เป็นไปไม่ได้ที่จะส่งผ่าน NSNumber ได้อย่างง่ายดายเช่น? มันจะมีลักษณะอย่างไร
รูปปั้น

7
แน่นอนคุณสามารถผ่านการโต้เถียงอย่างรุนแรงพิมพ์เช่นNSNumber *หรือstd::string&หรือสิ่งอื่นที่คุณสามารถผ่านเป็นอาร์กิวเมนต์ฟังก์ชั่น นี่เป็นเพียงตัวอย่าง (สำหรับบล็อกที่เทียบเท่ายกเว้นแทนที่idด้วยNSNumberที่typedefจะtypedef void (^ IteratorWithNumberBlock)(NSNumber *, int);.)
โจนาธาน Grynspan

นี่แสดงให้เห็นถึงการประกาศวิธีการ ปัญหาหนึ่งของบล็อกคือรูปแบบการประกาศ "ยุ่ง" ไม่ได้ทำให้ชัดเจนและง่ายต่อการเขียนการเรียกเมธอดจริงด้วยอาร์กิวเมนต์บล็อกจริง
uchuugaka

Typedefs ไม่เพียง แต่ทำให้การเขียนโค้ดง่ายขึ้น แต่การอ่านได้ง่ายขึ้นอย่างมากเนื่องจากไวยากรณ์ตัวชี้บล็อก / ฟังก์ชั่นนั้นไม่ได้สะอาดที่สุด
pyj

@JonathanGrynspan มาจากโลก Swift แต่ต้องสัมผัสกับรหัส Objective-C เก่าฉันจะทราบได้อย่างไรว่าบล็อกกำลังหลบหนีหรือไม่ ฉันอ่านแล้วโดยค่าเริ่มต้นบล็อกกำลังหลบหนียกเว้นถ้าตกแต่งด้วยNS_NOESCAPEแต่enumerateObjectsUsingBlockฉันบอกว่าไม่หนีเลย แต่ฉันไม่เห็นNS_NOESCAPEที่ใดก็ได้ในไซต์และไม่ได้กล่าวถึงในเอกสาร Apple คุณช่วยได้ไหม
Mark A. Donohoe

62

คำอธิบายที่ง่ายที่สุดสำหรับคำถามนี้เป็นไปตามเทมเพลตเหล่านี้:

1. บล็อกเป็นพารามิเตอร์วิธี

แบบ

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

ตัวอย่าง

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

การใช้งานอื่น ๆ ของกรณี:

2. บล็อกเป็นคุณสมบัติ

แบบ

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

ตัวอย่าง

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

3. บล็อกเป็นอาร์กิวเมนต์วิธี

แบบ

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

ตัวอย่าง

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

4. บล็อกเป็นตัวแปรในเครื่อง

แบบ

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

ตัวอย่าง

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

5. บล็อกเป็น typedef

แบบ

typedef returnType (^typeName)(parameters);

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

ตัวอย่าง

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

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

1
[self saveWithCompletionBlock: ^ (อาร์เรย์ NSArray * ข้อผิดพลาด NSError *) {// รหัสของคุณ}]; ในตัวอย่างนี้ประเภทการคืนจะถูกละเว้นเพราะมันเป็นโมฆะ?
Alex

51

สิ่งนี้อาจมีประโยชน์:

- (void)someFunc:(void(^)(void))someBlock;

คุณไม่มีวงเล็บอยู่
newacct

อันนี้ใช้ได้กับฉันในขณะที่อันก่อนไม่ได้ Btw ขอบคุณเพื่อนที่เป็นประโยชน์จริง ๆ !
tanou

23

คุณสามารถทำสิ่งนี้ผ่านบล็อกเป็นพารามิเตอร์บล็อก:

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

อีกวิธีหนึ่งในการส่งบล็อกโดยใช้ฟังก์ชันсในตัวอย่างด้านล่าง ฉันได้สร้างฟังก์ชันเพื่อดำเนินการทุกอย่างในพื้นหลังและบนคิวหลัก

ไฟล์ blocks.h

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

ไฟล์ 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);
}

กว่านำเข้า blocks.h เมื่อจำเป็นและเรียกใช้:

- (void)loadInBackground {

    performInBackground(^{

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

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

6

นอกจากนี้คุณยังสามารถตั้งค่าการบล็อกเป็นคุณสมบัติง่าย ๆ ได้ถ้ามันเหมาะสมกับคุณ:

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

ตรวจสอบให้แน่ใจว่าคุณสมบัติบล็อกคือ "คัดลอก"!

และแน่นอนคุณสามารถใช้ typedef:

typedef void (^SimpleBlock)(id);

@property (nonatomic, copy) SimpleBlock someActionHandler;

5

นอกจากนี้คุณเรียกหรือเรียกบล็อกในการใช้ไวยากรณ์ฟังก์ชั่นปกติ

-(void)iterateWidgets:(IteratorBlock)iteratorBlock{

    iteratorBlock(someId, someInt);
}

ข้อมูลเพิ่มเติมเกี่ยวกับบล็อกที่นี่

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxGettingStarted.html#//apple_ref/doc/uid/TP40007502-CH7-SW1


4

ฉันมักจะลืมเกี่ยวกับไวยากรณ์บล็อก สิ่งนี้อยู่ในใจของฉันเสมอเมื่อฉันต้องการประกาศบล็อก ฉันหวังว่ามันจะช่วยให้ใครบางคน :)

http://fuckingblocksyntax.com


สิ่งนี้ช่วยประหยัดเวลาของฉัน
Le Ding

3

ฉันเขียนส่วนที่เสร็จแล้วบล็อคสำหรับชั้นเรียนซึ่งจะคืนค่าลูกเต๋าหลังจากพวกเขาถูกเขย่า:

  1. กำหนด typedef ด้วย returnType ( ประกาศ.hด้านบน@interface)

    typedef void (^CompleteDiceRolling)(NSInteger diceValue);
  2. กำหนด a @propertyสำหรับบล็อก ( .h)

    @property (copy, nonatomic) CompleteDiceRolling completeDiceRolling;
  3. กำหนดวิธีการด้วยfinishBlock( .h)

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock;
  4. แทรกวิธีที่กำหนดไว้ก่อนหน้าใน.mไฟล์และกระทำfinishBlockเพื่อ@propertyกำหนดไว้ก่อน

    - (void)getDiceValueAfterSpin:(void (^)(NSInteger diceValue))finishBlock{
        self.completeDiceRolling = finishBlock;
    }
  5. หากต้องการเรียกใช้งานcompletionBlockVariType ที่กำหนดไว้ล่วงหน้าให้กับมัน (อย่าลืมตรวจสอบว่าcompletionBlockมีอยู่จริงหรือไม่)

    if( self.completeDiceRolling ){
        self.completeDiceRolling(self.dieValue);
    }

2

แม้จะมีคำตอบในหัวข้อนี้ แต่ฉันพยายามเขียนฟังก์ชั่นซึ่งจะใช้ Block เป็นฟังก์ชัน - และมีพารามิเตอร์ ในที่สุดนี่คือทางออกของฉัน

ฉันต้องการเขียนฟังก์ชันทั่วไปloadJSONthreadซึ่งจะใช้ URL ของ JSON Web Service โหลดข้อมูล JSON บางส่วนจาก URL นี้บนเธรดพื้นหลังจากนั้นส่งคืนผลลัพธ์ NSArray * กลับไปที่ฟังก์ชันการโทร

โดยทั่วไปฉันต้องการเก็บความซับซ้อนของแบ็คกราวน์ของเธรดไว้ในฟังก์ชันที่ใช้ซ้ำได้ทั่วไป

นี่คือวิธีที่ฉันจะเรียกใช้ฟังก์ชันนี้:

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

} ];

... และนี่คือสิ่งที่ฉันต้องดิ้นรนด้วย: จะประกาศอย่างไรและจะให้มันเรียกฟังก์ชัน Block อย่างไรเมื่อข้อมูลถูกโหลดและผ่านBlockNSArray * ของระเบียนที่โหลด:

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

        });
    });
}

คำถาม StackOverflow นี้เกี่ยวข้องกับวิธีการเรียกใช้ฟังก์ชันการส่งผ่าน Block เป็นพารามิเตอร์ดังนั้นฉันจึงทำให้โค้ดด้านบนง่ายขึ้นและไม่รวมloadJSONDataFromURLฟังก์ชั่น

แต่ถ้าคุณสนใจคุณสามารถค้นหาสำเนาของฟังก์ชั่นการโหลด JSON นี้ในบล็อกนี้: http://mikesknowledgebase.azurewebsites.net/pages/Services/WebServices-Page6.htm

หวังว่านี่จะช่วยนักพัฒนา XCode อื่น ๆ ! (อย่าลืมลงคะแนนคำถามนี้และคำตอบของฉันถ้ามัน!)


1
นี่เป็นหนึ่งในเทคนิคที่ดีที่สุดที่ฉันเคยเห็นสำหรับ iOS และบล็อก รักมันคน !!!!
portforwardpodcast

1

เทมเพลตเต็มดูเหมือนว่า

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

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.