【问题标题】:Can I use Objective-C blocks as properties?我可以使用 Objective-C 块作为属性吗?
【发布时间】:2011-04-25 12:50:35
【问题描述】:

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

ARC有什么变化吗?

【问题讨论】:

  • 好吧,因为它会非常方便。只要我有正确的语法并且它的行为就像一个 NSObject,我就不需要知道它是什么。
  • 如果你不知道它是什么,你怎么知道它会很方便?
  • 如果你不知道它们是什么,你不应该使用它们:)
  • @Moshe 这里有一些我想到的原因。块比完整的委托类更容易实现,块是轻量级的,并且您可以访问该块上下文中的变量。使用块可以有效地完成事件回调(cocos2d 几乎完全使用它们)。
  • 不完全相关,但由于一些 cmets 抱怨“丑陋”的块语法,这里有一篇很棒的文章,它从第一原则派生了语法:nilsou.com/blog/2013/08/21/objective-c-blocks-syntax

标签: ios objective-c automatic-ref-counting objective-c-blocks


【解决方案1】:

以下是您如何完成此类任务的示例:

#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,不需要进行特定更改,因为只要将块定义为副本,ARC 就会为您管理这些块。您也不需要在析构函数中将该属性设置为 nil。

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

【讨论】:

    【解决方案2】:
    @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;
    

    【讨论】:

    • 使用 xCode 4.4 或更高版本,您不需要合成。这将使它更加简洁。 Apple Doc
    • 哇,我不知道,谢谢! ...虽然我经常做@synthesize myProp = _myProp
    • @Robert:你又走运了,因为没有放@synthesize,默认就是你正在做的@synthesize name = _name;stackoverflow.com/a/12119360/1052616
    • @CharlieMonroe - 是的,你可能是对的,但你不需要一个 dealloc 实现来消除或释放没有 ARC 的块属性吗? (自从我使用非 ARC 以来已经有一段时间了)
    • @imcaptor:是的,如果你不在 dealloc 中释放它,它可能会导致内存泄漏 - 就像任何其他变量一样。
    【解决方案3】:

    为了后代/完整性......这里有两个完整的示例,说明如何实现这种可笑的多功能“做事方式”。 @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 内部,这是一个值得商榷的冒险实践..)与第一个示例的“非原子”“getter”机制。在任何一种情况下......“硬编码”实现总是可以被覆盖,每个实例..一个 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。使用属性将代码的相关部分更紧密地结合在一起,消除复杂的委托范例,并利用对象的力量,而不仅仅是充当愚蠢的“容器”。

    【讨论】:

    • Alex,伟大的关联示例。你知道,我想知道非原子的。想法?
    • 很少有“原子”会成为属性的正确做法。在一个线程中设置块属性并同时在另一个线程中读取它,或者从多个线程同时设置块属性是一件非常奇怪的事情。因此,“原子”与“非原子”的成本并没有给您带来任何真正的优势。
    【解决方案4】:

    @property(复制)无效

    @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
    • 您能解释一下为什么属性不应该是nonatomic,这与大多数其他使用属性的情况的最佳做法不同吗?
    • WorkingwithBlocks.html from Apple "你应该指定 copy 作为属性属性,因为..."
    【解决方案5】:

    你可以按照下面的格式,在类中使用testingObjectiveCBlock属性。

    typedef void (^testingObjectiveCBlock)(NSString *errorMsg);
    
    @interface MyClass : NSObject
    @property (nonatomic, strong) testingObjectiveCBlock testingObjectiveCBlock;
    @end
    

    更多信息请查看here

    【讨论】:

    • 这个答案是否真的为已经提供的其他答案添加了更多内容?
    【解决方案6】:

    免责声明

    这不是“好的答案”,因为这个问题明确要求 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,针对闭包进行了优化

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

    【讨论】:

      【解决方案7】:

      你好,斯威夫特

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

      【讨论】:

        【解决方案8】:

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

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

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

        【讨论】:

        猜你喜欢
        • 2012-06-03
        • 1970-01-01
        • 2013-12-02
        • 1970-01-01
        • 2021-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-30
        相关资源
        最近更新 更多