ฉันสามารถใช้บล็อก Objective-C เป็นคุณสมบัติได้หรือไม่


321

เป็นไปได้หรือไม่ที่จะมีบล็อกเป็นคุณสมบัติโดยใช้ไวยากรณ์คุณสมบัติมาตรฐาน

ARCมีการเปลี่ยนแปลงหรือไม่?


1
เพราะมันจะสะดวกมาก ฉันไม่จำเป็นต้องรู้ว่ามันคืออะไรตราบใดที่ฉันมีไวยากรณ์ที่ถูกต้องและมันทำงานเหมือน NSObject
gurghet

5
ถ้าคุณไม่รู้ว่ามันคืออะไรคุณจะรู้ได้อย่างไรว่ามันมีประโยชน์มาก
Stephen Canon

5
คุณไม่ควรใช้มันหากคุณไม่รู้ว่ามันคืออะไร :)
Richard J. Ross III

5
@Moshe นี่คือเหตุผลบางอย่างที่นึกขึ้นมาได้ บล็อกนั้นง่ายต่อการติดตั้งมากกว่าคลาสผู้รับมอบสิทธิ์เต็มรูปแบบบล็อกนั้นมีน้ำหนักเบาและคุณสามารถเข้าถึงตัวแปรที่อยู่ในบริบทของบล็อกนั้นได้ การโทรกลับเหตุการณ์สามารถทำได้อย่างมีประสิทธิภาพโดยใช้บล็อก (cocos2d ใช้มันเกือบทั้งหมด)
Richard J. Ross III 3

2
ไม่เกี่ยวข้องอย่างสมบูรณ์ แต่เนื่องจากความคิดเห็นบางส่วนบ่นเกี่ยวกับไวยากรณ์บล็อก "น่าเกลียด" ต่อไปนี้เป็นบทความที่ยอดเยี่ยมที่ได้มาจากไวยากรณ์จากหลักการแรก: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler

คำตอบ:


305
@property (nonatomic, copy) void (^simpleBlock)(void);
@property (nonatomic, copy) BOOL (^blockWithParamter)(NSString *input);

หากคุณกำลังจะทำซ้ำบล็อกเดียวกันในหลายแห่งให้ใช้การกำหนดประเภท

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;

3
ด้วย xCode 4.4 หรือใหม่กว่าคุณไม่จำเป็นต้องสังเคราะห์ ที่จะทำให้รัดกุมยิ่งขึ้น Apple Doc
Eric

ว้าวฉันไม่รู้เหมือนกันขอบคุณ! ... ถึงแม้ว่าฉันมักจะทำ@synthesize myProp = _myProp
Robert

7
@ Robert: คุณโชคดีอีกครั้งเพราะไม่ต้องใส่@synthesizeค่าเริ่มต้นคือสิ่งที่คุณกำลังทำ@synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric

1
@CharlieMonroe - ใช่คุณอาจจะถูก แต่คุณไม่จำเป็นต้องมีการดำเนินการ dealloc เพื่อศูนย์หรือปล่อยคุณสมบัติบล็อกโดยไม่ต้อง ARC? (เป็นเวลานานแล้วที่ฉันไม่ใช้ ARC)
Robert

1
@imcaptor: ใช่มันอาจทำให้หน่วยความจำรั่วในกรณีที่คุณไม่ปล่อยใน dealloc - เหมือนกับตัวแปรอื่น ๆ
Charlie Monroe

210

นี่คือตัวอย่างของวิธีที่คุณจะทำงานให้สำเร็จ:

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

ทีนี้สิ่งเดียวที่จะต้องเปลี่ยนถ้าคุณต้องการเปลี่ยนประเภทการเปรียบเทียบก็typedef int (^IntBlock)()คือ หากคุณต้องการส่งวัตถุสองชิ้นไปให้เปลี่ยนเป็น: typedef int (^IntBlock)(id, id)และเปลี่ยนบล็อกเป็น:

^ (id obj1, id obj2)
{
    return rand();
};

ฉันหวังว่านี่จะช่วยได้.

แก้ไข 12 มีนาคม 2555:

สำหรับ ARC ไม่จำเป็นต้องมีการเปลี่ยนแปลงใด ๆ เนื่องจาก ARC จะจัดการบล็อกให้กับคุณตราบใดที่มันถูกกำหนดเป็นสำเนา คุณไม่จำเป็นต้องตั้งค่าคุณสมบัติเป็นศูนย์ใน destructor ของคุณเช่นกัน

สำหรับการอ่านเพิ่มเติมโปรดตรวจสอบเอกสารนี้: http://clang.llvm.org/docs/AutomaticReferenceCounting.html


158

สำหรับ Swift เพียงใช้การปิด: ตัวอย่าง


ในวัตถุประสงค์ -C:

@ คุณสมบัติ (สำเนา) เป็นโมฆะ

@property (copy)void (^doStuff)(void);

มันง่ายมาก

นี่คือเอกสารจริงของ Apple ซึ่งระบุว่าต้องใช้อะไร:

Apple doco

ในไฟล์. 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".

นี่คือไฟล์. 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();
    }

ระวังโค้ดตัวอย่างที่ล้าสมัย

ด้วยระบบที่ทันสมัย ​​(2014+) ทำสิ่งที่แสดงไว้ที่นี่ มันง่ายมาก


บางทีคุณอาจจะยังควรจะพูดว่าตอนนี้ (2016) ก็ตกลงที่จะใช้strongแทนcopy?
Nik Kov

คุณช่วยอธิบายได้หรือไม่ว่าทำไมคุณสมบัติไม่ควรnonatomicแตกต่างจากแนวปฏิบัติที่ดีที่สุดสำหรับกรณีส่วนใหญ่ที่ใช้คุณสมบัติ
Alex Pretzlav

WorkingwithBlocks.html จาก Apple "คุณควรระบุการคัดลอกเป็นคุณสมบัติของคุณสมบัติเพราะ ... "
Fattie

20

เพื่อประโยชน์ของลูกหลาน / ความสมบูรณ์ ... ต่อไปนี้เป็นสองตัวอย่างเต็มรูปแบบของวิธีการใช้ "วิธีการทำสิ่งต่าง ๆ " ที่น่าขันนี้ @ Robert ตอบรัดกุมและถูกต้อง แต่ที่นี่ฉันต้องการแสดงวิธีการ "กำหนด" บล็อก

@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

โง่? ใช่. มีประโยชน์หรือไม่ นรกใช่ นี่คือวิธีที่แตกต่างกันคือ "more atomic" ในการตั้งค่าคุณสมบัติ .. และคลาสที่มีประโยชน์อย่างน่าขัน ...

@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

สิ่งนี้แสดงให้เห็นถึงการตั้งค่าคุณสมบัติบล็อกผ่าน accessor (แม้ว่าจะอยู่ภายใน init, เป็นการฝึกแบบ debatably .. ) เทียบกับกลไก "nonatomic" "getter" ตัวอย่างแรก ไม่ว่าในกรณีใด…การใช้งาน "hardcoded" สามารถเขียนทับได้ตลอดเวลาเช่น .. a l ..

CALayoutDelegator *littleHelper = CALayoutDelegator.new;
littleHelper.layoutBlock = ^(CALayer*layer){
  [layer.sublayers do:^(id sub){ [sub somethingElseEntirely]; }];
};
someLayer.layoutManager = littleHelper;

นอกจากนี้ .. หากคุณต้องการเพิ่มคุณสมบัติบล็อกในหมวดหมู่ ... บอกว่าคุณต้องการใช้บล็อกแทนการกระทำ "การกระทำ" ของโรงเรียนเก่า / เป้าหมาย ... คุณสามารถใช้ค่าที่เกี่ยวข้องได้เช่นกัน .. เชื่อมโยงบล็อก

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

ตอนนี้เมื่อคุณสร้างปุ่มคุณไม่ต้องสร้างIBActionละคร ... แค่เชื่อมโยงงานที่จะทำในการสร้าง ...

_button.actionBlock = ^(NSControl*thisButton){ 

     [doc open]; [thisButton setEnabled:NO]; 
};

รูปแบบนี้สามารถใช้กับOVER และ OVER ของ Cocoa API ได้ ใช้คุณสมบัติเพื่อนำส่วนที่เกี่ยวข้องของรหัสของคุณเข้ามาใกล้กันกำจัดกระบวนทัศน์การมอบหมายที่ซับซ้อนและใช้ประโยชน์จากพลังของวัตถุที่อยู่นอกเหนือจากที่ทำหน้าที่เป็น "คอนเทนเนอร์" ที่โง่


อเล็กซ์เป็นตัวอย่างที่ดีมาก คุณรู้ฉันสงสัยเกี่ยวกับสิ่งที่ไม่ใช่เชิงอะตอม คิด?
Fattie

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

8

แน่นอนคุณสามารถใช้บล็อกเป็นคุณสมบัติได้ แต่ให้แน่ใจว่าพวกเขาจะประกาศเป็น@property (สำเนา) ตัวอย่างเช่น:

typedef void(^TestBlock)(void);

@interface SecondViewController : UIViewController
@property (nonatomic, copy) TestBlock block;
@end

ในคณะกรรมาธิการแม่น้ำโขงบล็อกจับตัวแปรบริบทได้รับการจัดสรรในสแต็ค ; พวกเขาจะได้รับการปล่อยตัวเมื่อกองเฟรมถูกทำลาย หากพวกเขาถูกคัดลอกบล็อกใหม่จะถูกจัดสรรในฮีปซึ่งสามารถดำเนินการได้ในภายหลังหลังจากสแต็กเฟรมจะปรากฏขึ้น


เผง นี่คือเอกสารของ Apple ที่แท้จริงว่าทำไมคุณควรใช้การคัดลอกและไม่มีอะไรอื่น developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie

7

DISCLAMER

นี่ไม่ใช่จุดประสงค์ที่จะเป็น "คำตอบที่ดี" เนื่องจากคำถามนี้ถามอย่างชัดเจนสำหรับ ObjectiveC ดังที่ Apple เปิดตัว Swift ที่ WWDC14 ฉันต้องการแบ่งปันวิธีต่างๆในการใช้บล็อก (หรือการปิด) ใน Swift

สวัสดีสวิฟท์

คุณมีหลายวิธีที่เสนอให้ผ่านบล็อกที่เทียบเท่ากับฟังก์ชันใน Swift

ฉันพบสาม

เพื่อทำความเข้าใจนี้ฉันขอแนะนำให้คุณทดสอบในสนามเด็กเล่นรหัสนี้เล็กน้อย

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)

Swift เหมาะสำหรับการปิด

เนื่องจาก Swift ได้รับการปรับให้เหมาะสมสำหรับการพัฒนาแบบอะซิงโครนัส Apple ทำงานได้มากขึ้นในการปิด สิ่งแรกคือลายเซ็นของฟังก์ชั่นนั้นสามารถอนุมานได้ดังนั้นคุณไม่จำเป็นต้องเขียนใหม่

เข้าถึงพารามิเตอร์ด้วยตัวเลข

let resultShortAnon = test({return "ANON_" + $0 + "__ANON" })

การอนุมานพารามิเตอร์ด้วยการตั้งชื่อ

let resultShortAnon2 = test({myParam in return "ANON_" + myParam + "__ANON" })

ปิดท้าย

กรณีพิเศษนี้ใช้งานได้เฉพาะถ้าบล็อกนั้นเป็นอาร์กิวเมนต์สุดท้ายเรียกว่าการปิดท้าย

นี่คือตัวอย่าง (ผสานกับลายเซ็นอนุมานเพื่อแสดงพลังของ Swift)

let resultTrailingClosure = test { return "TRAILCLOS_" + $0 + "__TRAILCLOS" }

สุดท้าย:

การใช้พลังทั้งหมดนี้สิ่งที่ฉันทำคือการผสมการปิดท้ายและการอนุมานประเภท (พร้อมการตั้งชื่อเพื่อให้อ่านง่าย)

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

สวัสดีสวิฟท์

การเสริมสิ่งที่ @Francescu ตอบกลับ

การเพิ่มพารามิเตอร์พิเศษ:

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

คุณสามารถทำตามรูปแบบด้านล่างและสามารถใช้testingObjectiveCBlockคุณสมบัติในชั้นเรียน

typedef void (^testingObjectiveCBlock)(NSString *errorMsg);

@interface MyClass : NSObject
@property (nonatomic, strong) testingObjectiveCBlock testingObjectiveCBlock;
@end

สำหรับข้อมูลเพิ่มเติมดูได้ที่นี่


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