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

331

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

ARC 有哪些变化?


5
如果你不知道它是什么,那么你怎么知道它会非常方便呢? - Stephen Canon
1
@Stephen 因为我经常使用它们 :) - gurghet
5
如果你不知道它们是什么,就不应该使用它们 :) - Richard J. Ross III
5
这里是我想到的一些原因。块比完整的代理类更易于实现,块很轻量级,并且您可以访问在该块上下文中的变量。使用块可以有效地完成事件回调(cocos2d几乎完全使用它们)。 - Richard J. Ross III
3
虽然不完全相关,但由于一些评论抱怨“丑陋”的代码块语法,这里有一篇很好的文章,从第一原理推导出了该语法:http://nilsou.com/blog/2013/08/21/objective-c-blocks-syntax/。 - paulrehkugler
显示剩余3条评论
8个回答

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

如果你要在多个地方重复使用相同的代码块,可以使用类型定义(type def)。

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

3
从xCode 4.4或更新版本开始,您不需要合成。这将使代码更加简洁。Apple文档 - Eric
8
@Robert:你很幸运,因为如果不使用@synthesize,默认情况下就是你现在所做的 @synthesize name = _name;。https://dev59.com/SmfWa4cB1Zd3GeqPja0E#12119360 - Eric
1
@CharlieMonroe - 是的,你可能是对的,但是如果没有使用ARC,难道不需要dealloc实现来将块属性设置为nil或释放它吗?(我已经有一段时间没有使用非ARC了) - Robert
@RichardJ.RossIII:这很有趣,因为当你运行“clang -rewrite-objc test.m”时,你会得到一个名为struct __SomeClass__secondMethod_withTwoArguments__block_impl_0的结构体,它有一个C++构造函数(SomeClass是一个示例ObjC类,sedondMethod:withTwoArguments:是该类上的一个方法),当你看到方法中的声明时,确切的C++构造函数被使用...这意味着要么文档不是最新的,要么-rewrite-objc选项不准确,或者当允许C++时,苹果正在使用不同的结构体(不是源文件将是ObjC++)。 - Charlie Monroe
1
@imcaptor:是的,如果你在dealloc中没有释放它,就像任何其他变量一样,它会导致内存泄漏。 - Charlie Monroe
显示剩余14条评论

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

2012年3月12日编辑:

对于ARC来说,不需要进行特定的更改,只要它们被定义为copy,ARC将为您管理块。在析构函数中,您也不需要将属性设置为nil。

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


162

@property (copy)void

@property (copy)void在Objective-C中是一种属性声明,表示在setter方法中使用传入值的副本而不是引用值。
@property (copy)void (^doStuff)(void);

准确说明使用方法的实际Apple文档:

Apple文档。

您的.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”了? - Nike Kov
你可以解释一下为什么在属性的使用中,与大多数其他情况最佳实践不同,它不应该是nonatomic吗? - Alex Pretzlav
1
你应该将copy作为属性特质来指定,因为...。 来自苹果的WorkingwithBlocks.html - Fattie

22

为了记录完整性,以下是两个完整的示例,展示如何实现这种极其灵活的“事情处理方式”。@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内部,这是一个有争议的做法..)设置块属性与第一个示例中的“nonatomic”“getter”机制。 在任一情况下...硬编码的实现始终可以被覆盖,每个实例都可以进行...

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

另外,如果您想在类别中添加块属性...例如,您想使用块代替某些老式的目标/操作"action"... 您可以使用关联值来关联这些块。

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

这种模式可以应用于Cocoa API的各个方面。使用属性将代码中相关部分“聚合”在一起,消除复杂的委托范式,并利用对象的能力,超越仅充当“容器”的作用。


Alex,很棒的相关示例。你知道吗,我在想关于nonatomic的事情。有什么想法吗? - Fattie
2
“atomic” 对于属性来说是非常罕见的正确选择。在一个线程中设置块属性并在另一个线程中同时读取它,或者同时从多个线程设置块属性,这将是非常奇怪的事情。因此,“atomic” 和 “nonatomic” 的成本并没有给您带来任何真正的优势。 - gnasher729

8
当然,您可以将块作为属性使用。但请确保它们声明为@property(copy)。例如:
typedef void(^TestBlock)(void);

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

在MRC中,捕获上下文变量的块被分配在堆栈中;当堆栈帧被销毁时,它们将被释放。如果它们被复制,一个新的块将被分配在中,可以在堆栈帧弹出后执行。

没错。这里是苹果官方文档,详细说明为什么你应该使用copy而不是其他选项。https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html - Fattie

6

免责声明

本文旨在为需要Objective-C的人提供帮助,但由于苹果在WWDC14上推出了Swift,因此我想分享在Swift中使用块(或闭包)的不同方法。这并不意味着这是唯一正确的答案。

Hello, Swift

在Swift中,你有多种方式来传递一个类似于函数的块。

我发现了三种方法。

为了更好地理解,建议在playground中测试以下代码片段。

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针对异步开发进行了优化,因此苹果公司更加注重闭包的使用。 第一个优化是函数签名可以被推断,因此您不必重新编写它。

通过数字访问参数

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

-1

你好,Swift

补充@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)

-4

您可以按照以下格式,并且可以在类中使用testingObjectiveCBlock属性。

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

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

欲了解更多信息,请查看此处


2
这个回答是否真的为已经提供的其他答案增加了更多的内容? - Richard J. Ross III

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接