Tôi có thể sử dụng các khối Objective-C làm thuộc tính không?


321

Có thể có các khối làm thuộc tính bằng cú pháp thuộc tính tiêu chuẩn không?

Có bất kỳ thay đổi cho ARC ?


1
Vâng, bởi vì nó sẽ rất hữu ích. Tôi sẽ không cần biết nó là gì miễn là tôi có cú pháp đúng và nó hoạt động như một NSObject.
gurghet

5
Nếu bạn không biết nó là gì, làm sao bạn biết rằng nó sẽ rất tiện dụng?
Stephen Canon

5
Bạn không nên sử dụng chúng Nếu bạn không biết chúng là gì :)
Richard J. Ross III

5
@Moshe đây là một số lý do mà đến với tâm trí. Các khối dễ thực hiện hơn một lớp đại biểu đầy đủ, các khối rất nhẹ và bạn có quyền truy cập vào các biến trong ngữ cảnh của khối đó. Gọi lại sự kiện có thể được thực hiện hiệu quả bằng cách sử dụng các khối (cocos2d sử dụng chúng gần như độc quyền).
Richard J. Ross III

2
Không hoàn toàn liên quan, nhưng vì một số ý kiến ​​phàn nàn về cú pháp khối "xấu xí", đây là một bài viết tuyệt vời rút ra cú pháp từ các nguyên tắc đầu tiên: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler

Câu trả lời:


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

Nếu bạn sẽ lặp lại cùng một khối ở một số nơi, hãy sử dụng loại def

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

3
Với xCode 4.4 trở lên, bạn không cần phải tổng hợp. Điều đó sẽ làm cho nó thậm chí ngắn gọn hơn. Apple Doc
Eric

wow, tôi không biết điều đó, cảm ơn! ... Mặc dù tôi thường làm@synthesize myProp = _myProp
Robert

7
@Robert: Bạn lại gặp may, vì không đặt @synthesizemặc định là những gì bạn đang làm @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric

1
@CharlieMonroe - Có thể bạn đúng, nhưng bạn không cần triển khai dealloc để không hoặc giải phóng thuộc tính khối mà không có ARC? (đã được một thời gian kể từ khi tôi sử dụng phi ARC)
Robert

1
@imcaptor: Có, nó có thể gây rò rỉ bộ nhớ trong trường hợp bạn không giải phóng nó trong dealloc - giống như với bất kỳ biến nào khác.
Charlie Monroe

210

Đây là một ví dụ về cách bạn sẽ hoàn thành một nhiệm vụ như vậy:

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

Bây giờ, điều duy nhất cần thay đổi nếu bạn cần thay đổi kiểu so sánh sẽ là typedef int (^IntBlock)(). Nếu bạn cần truyền hai đối tượng cho nó, hãy đổi nó thành: typedef int (^IntBlock)(id, id)và thay đổi khối của bạn thành:

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

Tôi hi vọng cái này giúp được.

EDIT ngày 12 tháng 3 năm 2012:

Đối với ARC, không có thay đổi cụ thể cần thiết, vì ARC sẽ quản lý các khối cho bạn miễn là chúng được xác định là bản sao. Bạn cũng không cần đặt thuộc tính thành số không trong hàm hủy của mình.

Để đọc thêm, vui lòng xem tài liệu này: http://clang.llvm.org/docs/AutomaticReferenceCounting.html


158

Đối với Swift, chỉ cần sử dụng bao đóng: ví dụ.


Trong Mục tiêu-C:

@property (bản sao) void

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

Nó đơn giản mà.

Dưới đây là tài liệu thực tế của Apple, trong đó nêu chính xác những gì sẽ sử dụng:

Apple doco.

Trong tệp .h của bạn:

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

Đây là tập tin .m của bạn:

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

Cẩn thận với mã ví dụ lỗi thời.

Với các hệ thống hiện đại (2014+), hãy làm những gì được hiển thị ở đây. Nó là đơn giản.


Có lẽ bạn cũng nên nói rằng, bây giờ (2016) bạn có thể sử dụng strongthay thế copykhông?
Nik Kov

Bạn có thể giải thích lý do tại sao tài sản không nên không nonatomicgiống như thực tiễn tốt nhất cho hầu hết các trường hợp khác sử dụng tài sản không?
Alex Pretzlav

WorkwithBlocks.html từ Apple "Bạn nên chỉ định bản sao làm thuộc tính thuộc tính, bởi vì ..."
Fattie

20

Vì lợi ích của hậu thế / sự hoàn thiện Đây là hai ví dụ ĐẦY ĐỦ về cách thực hiện "cách làm việc" linh hoạt kỳ cục này. Câu trả lời của @ Robert rất ngắn gọn và chính xác, nhưng ở đây tôi cũng muốn chỉ ra các cách để thực sự "định nghĩa" các khối.

@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

Ngớ ngẩn? Đúng. Hữu ích? Địa ngục yeah. Đây là một cách khác nhau, "nguyên tử hơn" để thiết lập tài sản .. và một lớp có ích vô lý

@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

Điều này minh họa việc thiết lập thuộc tính khối thông qua trình truy cập (mặc dù bên trong init, một thực hành xúc xắc gây tranh cãi ..) so với cơ chế "getter" "không biến đổi" của ví dụ đầu tiên. Trong cả hai trường hợp, các triển khai "mã hóa cứng" luôn có thể được ghi đè, ví dụ .. một lá ..

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

Ngoài ra .. nếu bạn muốn thêm thuộc tính khối trong danh mục ... giả sử bạn muốn sử dụng Khối thay vì "hành động" mục tiêu / hành động cũ của trường học ... Bạn chỉ có thể sử dụng các giá trị được liên kết để, tốt .. liên kết các khối.

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

Bây giờ, khi bạn tạo một nút, bạn không cần phải thiết lập một số IBActionbộ phim truyền hình .. Chỉ cần liên kết công việc sẽ được thực hiện khi tạo ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Mẫu này có thể được áp dụng QUÁ và QUÁ cho API của Cacao. Sử dụng các thuộc tính để mang các phần có liên quan của mã của bạn lại gần nhau hơn , loại bỏ các mô hình ủy nhiệm phức tạp và tận dụng sức mạnh của các đối tượng vượt ra ngoài việc chỉ đóng vai trò là "các thùng chứa" câm.


Alex, ví dụ liên kết tuyệt vời. Bạn biết đấy, tôi đang tự hỏi về nonatomic. Suy nghĩ?
Fattie

2
Rất hiếm khi "nguyên tử" sẽ là điều đúng đắn để làm cho một tài sản. Sẽ là một điều rất kỳ lạ khi đặt thuộc tính khối trong một luồng và đọc nó trong luồng khác cùng một lúc hoặc để đặt thuộc tính khối đồng thời từ nhiều luồng. Vì vậy, chi phí "nguyên tử" so với "không nguyên tử" không mang lại cho bạn bất kỳ lợi thế thực sự nào.
gnasher729

8

Tất nhiên bạn có thể sử dụng các khối làm tài sản. Nhưng hãy chắc chắn rằng chúng được khai báo là @property (bản sao) . Ví dụ:

typedef void(^TestBlock)(void);

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

Trong MRC, các khối bắt các biến bối cảnh được phân bổ trong ngăn xếp ; chúng sẽ được giải phóng khi khung stack bị phá hủy. Nếu chúng được sao chép, một khối mới sẽ được phân bổ thành từng đống , có thể được thực thi sau đó sau khi khung stack được bật lên.


Chính xác. Dưới đây là tài liệu thực tế của Apple về chính xác lý do tại sao bạn nên sử dụng bản sao và không có gì khác. developer.apple.com/l
Library / ios / documentation / cocoa / conceptionual / từ

7

Từ chối trách nhiệm

Đây không phải là "câu trả lời tốt", vì câu hỏi này hỏi rõ ràng cho ObjectiveC. Như Apple đã giới thiệu Swift tại WWDC14, tôi muốn chia sẻ các cách khác nhau để sử dụng khối (hoặc đóng) trong Swift.

Xin chào, Swift

Bạn có nhiều cách được cung cấp để vượt qua một khối tương đương với chức năng trong Swift.

Tôi tìm thấy ba.

Để hiểu điều này, tôi khuyên bạn nên thử nghiệm trong sân chơi đoạn mã nhỏ này.

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, được tối ưu hóa cho việc đóng cửa

Vì Swift được tối ưu hóa để phát triển không đồng bộ, Apple đã làm việc nhiều hơn trong việc đóng cửa. Đầu tiên là chữ ký hàm có thể được suy ra để bạn không phải viết lại.

Truy cập thông số bằng số

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

Params suy luận với việc đặt tên

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

Đóng cửa

Trường hợp đặc biệt này chỉ hoạt động nếu khối là đối số cuối cùng, nó được gọi là đóng dấu

Dưới đây là một ví dụ (được hợp nhất với chữ ký được suy ra để thể hiện sức mạnh của Swift)

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

Cuối cùng:

Sử dụng tất cả sức mạnh này, những gì tôi sẽ làm là trộn các lần đóng dấu và nhập suy luận (với cách đặt tên để dễ đọc)

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

Xin chào, Swift

Bổ sung những gì @Francescu đã trả lời.

Thêm tham số bổ sung:

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

Bạn có thể làm theo định dạng dưới đây và có thể sử dụng thuộc testingObjectiveCBlocktính trong lớp.

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

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

Để biết thêm thông tin có một cái nhìn ở đây


2
Câu trả lời này có thực sự bổ sung thêm bất cứ điều gì cho các câu trả lời khác đã được cung cấp không?
Richard J. Ross III
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.