我可以使用Objective-C块作为属性吗?


321

是否可以使用标准属性语法将块作为属性?

ARC是否有任何更改?


1
好吧,因为它将非常方便。只要我有正确的语法,并且它的行为类似于NSObject,就不需要知道它是什么。
gurghet 2010年

5
如果您不知道它是什么,怎么知道它会非常方便?
斯蒂芬·佳能

5
如果您不知道它们是什么,则不应该使用它们:)
理查德·罗斯三世

5
@Moshe这是我想到的一些原因。块比完整的委托类更容易实现,块轻巧,并且您可以访问该块上下文中的变量。使用块可以有效地完成事件回调(cocos2d几乎完全使用它们)。
理查德·罗斯三世

2
并不完全相关,但是由于某些评论抱怨“丑陋”的块语法,因此,这篇很棒的文章是从第一原理中得出语法的:nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler 2013年

Answers:


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

如果要在多个位置重复相同的块,请使用def类型

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

3
使用xCode 4.4或更高版本,您不需要进行合成。这将使其更加简洁。苹果文件
埃里克

哇,我不知道,谢谢!...虽然我经常这样做@synthesize myProp = _myProp
罗伯特·罗伯特(Robert Robert)

7
@Robert:您再次碰运气,因为如果没有@synthesize使用默认设置,那么您正在做什么@synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric

1
@CharlieMonroe-是的,您可能是对的,但是您是否需要一个dealloc实现来实现nil或释放没有ARC的block属性?(自从我使用非ARC以来已经有一段时间了)
罗伯特·罗伯特(Robert Robert

1
@imcaptor:是的,如果您没有在dealloc中释放它,可能会导致内存泄漏-就像其他变量一样。
查理·梦露

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)()。如果需要向其传递两个对象,请将其更改为this typedef int (^IntBlock)(id, id),然后将块更改为:

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

我希望这有帮助。

编辑2012年3月12日:

对于ARC,不需要任何特定更改,因为只要将块定义为副本,ARC就会为您管理块。您也不需要在析构函数中将属性设置为nil。

有关更多阅读,请查看此文档:http : //clang.llvm.org/docs/AutomaticReferenceCounting.html


158

对于Swift,只需使用闭包:示例。


在Objective-C中:

@属性(复制)无效

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

就这么简单。

这是实际的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大多数其他使用属性的情况下,属性不应与最佳做法不同?
亚历克斯·普雷茨拉夫

苹果公司的WorkingwithBlocks.html “您应将copy指定为属性属性,因为...”
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

愚蠢?是。 有用?地狱是的。 这是设置属性的另一种“更原子”的方法..和一个非常有用的类……

@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

这说明了与第一个示例的“非原子”“获取”机制相比,通过访问器(尽管在init中,有争议的实践)设置了块属性。无论哪种情况,“硬编码”实现总是可以被实例覆盖,例如

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
对于财产而言,“原子”是正确的事情是非常罕见的。在一个线程中设置一个block属性并同时在另一个线程读取它,或者同时从多个线程中设置block属性,这将是一件非常奇怪的事情。因此,“原子”与“非原子”的成本并没有给您带来任何真正的优势。
gnasher729 2014年

8

当然,您可以将块用作属性。但是请确保将它们声明为@property(copy)。例如:

typedef void(^TestBlock)(void);

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

在MRC中,捕获上下文变量的块在堆栈中分配;当堆栈框架被破坏时,它们将被释放。如果将它们复制,则会在heap中分配一个新块,可以在弹出堆栈帧之后稍后执行。


究竟。这是真正的Apple doco,确切说明了为什么您应该使用copy而不是其他任何东西。 developer.apple.com/library/ios/documentation/cocoa/conceptual/...
Fattie

7

免责声明

这并不是“好的答案”,因为此问题明确要求ObjectiveC。当Apple在WWDC14上介绍Swift时,我想分享在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已针对异步开发进行了优化,因此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
这个答案是否真的在已经提供的其他答案中添加了更多内容?
理查德·罗斯三世
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.