Can I use Objective-C blocks as properties?
Asked Answered
S

8

331

Is it possible to have blocks as properties using the standard property syntax?

Are there any changes for ARC?

Settler answered 14/10, 2010 at 16:55 Comment(8)
Well, because it would be very in handy. I wouldn't need to know what it is as long as I have the syntax right and it behaves like an NSObject.Settler
If you don't know what it is, how do you know that it would be very handy?Credulity
@Stephen Because I use them a lot :)Settler
You shouldn't use them If you dont know what they are :)Middaugh
Why would you want to do this?Litchfield
@Litchfield here are some reasons that come to mind. Blocks are easier to implement than a full delegate class, blocks are lightweight, and you have access to variables that are in the context of that block. Event Callbacks can be done effectively using blocks (cocos2d uses them almost exclusively).Middaugh
Not completely related, but since some of the comments complain about "ugly" block syntax, here is a great article that derives the syntax from first principles: nilsou.com/blog/2013/08/21/objective-c-blocks-syntaxFringe
Purely for the record, this question is very old and some people were asking "why use blocks as properties?" I guess, it's now used very frequently by Apple; it's commonplace. BTW great link Paul thanks.Swartz
M
320
@property (nonatomic, copy) void (^simpleBlock)(void);
@property (nonatomic, copy) BOOL (^blockWithParamter)(NSString *input);

If you are going to be repeating the same block in several places use a type def

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;
Monocular answered 23/10, 2012 at 15:20 Comment(19)
With xCode 4.4 or newer you dont need to synthesize. That will make it even more concise. Apple DocEldoraeldorado
wow, I didn't know that, thanks! ... Although I often do @synthesize myProp = _myPropMonocular
@Robert: You are in luck again, because without putting @synthesize the default is what you are doing @synthesize name = _name; https://mcmap.net/q/89497/-xcode-4-automatically-generates-ivars-when-using-property-where-can-i-find-the-official-doc-for-this-featureEldoraeldorado
ARC is not a condition for the example above. Works fine with non-ARC code as well.King
@CharlieMonroe - Yes you are probably right, but dont you need a dealloc implementation to nil or release the block property without ARC? (its been a while since I used non-ARC)Monocular
Yes, you do - just like anything else - NSString, NSArray, etc. Blocks are really just instances of a private class that is a wrapper around a C++ structure actually implementing the blocks behavior.King
Can this cause memory leak?Appetence
@CharlieMonroe that is incorrect. They aren't using a c++ structure, just a plain old C structure. See the ABI used by clang here: clang.llvm.org/docs/Block-ABI-Apple.htmlMiddaugh
@RichardJ.RossIII: This is interesting, since when you run a "clang -rewrite-objc test.m", then you get a struct __SomeClass__secondMethod_withTwoArguments__block_impl_0, which has a C++ constructor (SomeClass is a sample ObjC class, sedondMethod:withTwoArguments: is a method on that class) and when you then see the declaration in the method, the exact C++ constructor is used… This means either that doc is not up-to-date, the -rewrite-objc option isn't accurate, or Apple's using different structures when C++ is allowed (not that the source file would be ObjC++).King
@imcaptor: Yes, it can cause memory leaks in case you don't release it in dealloc - just like with any other variable.King
@Charlie I think you're confused on what rewrite-objc does. It converts objc to C++, not to C, at least according to this.Middaugh
@RichardJ.RossIII - AFAIK, this is mostly due to the fact that Apple's starting to use C++ features quite heavily throughout the run-time code and they use C++ unwind library for exceptions in runtime 2.0 - -rewrite-objc should really be the result clang is then using to compile simply using the C(++) compiler (output of a step in the compilation process, where the compiler replaces the ObjC syntax with C/C++)...King
@CharlieMonroe I would agree with you if the assembly generated agreed, which as far as I can tell does not include C++ destructors.Middaugh
@RichardJ.RossIII - it doesn't need to include C++ destructors since all it does is simple assignment to the variables (no copying, or memory allocation). There really isn't a difference between C and C++ structures as such (at least in this case) and the C++ methods on structures are simply to make the work with them easier and AFAIK the compiler inlines the C++ structure methods anyway, so it's just a syntactical sugar so that there is no need for extra functions such as "my_struct_init" and the compiler makes sure the structure is inited before using it...King
Its much cleaner if you remove the (void) suffix and instead use void (^simpleBlock)()Dvina
sorry to dig this up, but why @property (nonatomic) MyCompletionBlock completion; without copy here? I remember default is strong. And block needs to use copy. This is consistent hereByssus
hi @Byssus - the answer is simply wrong; it was wrong 6 yrs ago and it's wrong now. the Apple doco gives and explains the best approach.Swartz
@Swartz excuse me? Could you give the link? ThanksByssus
in the answer below @ByssusSwartz
M
210

Here's an example of how you would accomplish such a task:

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

Now, the only thing that would need to change if you needed to change the type of compare would be the typedef int (^IntBlock)(). If you need to pass two objects to it, change it to this: typedef int (^IntBlock)(id, id), and change your block to:

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

EDIT March 12, 2012:

For ARC, there are no specific changes required, as ARC will manage the blocks for you as long as they are defined as copy. You do not need to set the property to nil in your destructor, either.

For more reading, please check out this document: http://clang.llvm.org/docs/AutomaticReferenceCounting.html

Middaugh answered 14/10, 2010 at 17:9 Comment(0)
S
162

@property (copy)void

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

The actual Apple documentation which states precisely what to use:

Apple doco.

Your .h file:

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

Your .m file:

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

Beware of out-of-date example code.

With modern (2014+) systems this is the correct and documented approach.

Swartz answered 24/12, 2013 at 12:2 Comment(3)
Maybe you should also say, that now ( 2016 ) it's ok to use strong instead of copy?Titled
Can you explain why the property shouldn't be nonatomic unlike best practices for most other cases using properties?Trow
WorkingwithBlocks.html from Apple "You should specify copy as the property attribute, because..."Swartz
D
22

For posterity / completeness's sake… Here are two FULL examples of how to implement this ridiculously versatile "way of doing things". @Robert's answer is blissfully concise and correct, but here I want to also show ways to actually "define" the blocks.

@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

Silly? Yes. Useful? Hells yeah. Here is a different, "more atomic" way of setting the property.. and a class that is ridiculously useful…

@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

This illustrates setting the block property via the accessor (albeit inside init, a debatably dicey practice..) vs the first example's "nonatomic" "getter" mechanism. In either case… the "hardcoded" implementations can always be overwritten, per instance.. a lá..

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

Also.. if you want to add a block property in a category... say you want to use a Block instead of some old-school target / action "action"... You can just use associated values to, well.. associate the blocks.

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

Now, when you make a button, you don't have to set up some IBAction drama.. Just associate the work to be done at creation...

_button.actionBlock = ^(NSControl*thisButton){ 

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

This pattern can be applied OVER and OVER to Cocoa API's. Use properties to bring the relevant parts of your code closer together, eliminate convoluted delegation paradigms, and leverage the power of objects beyond that of just acting as dumb "containers".

Diapason answered 31/5, 2013 at 21:11 Comment(2)
Alex, great Associated example. You know, I'm wondering about the nonatomic. Thoughts?Swartz
It's very rare that "atomic" would be the right thing to do for a property. It would be a very strange thing to set a block property in one thread and read it in another thread at the same time, or to set the block property simultaneously from multiple threads. So the cost of "atomic" vs. "nonatomic" doesn't give you any real advantages.Verdie
Q
8

Of course you could use blocks as properties. But make sure they are declared as @property(copy). For example:

typedef void(^TestBlock)(void);

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

In MRC, blocks capturing context variables are allocated in stack; they will be released when the stack frame is destroyed. If they are copied, a new block will be allocated in heap, which can be executed later on after the stack frame is poped.

Quitt answered 16/7, 2015 at 9:8 Comment(1)
Exactly. Here is the actual Apple doco on exactly why you should use copy and nothing else. developer.apple.com/library/ios/documentation/cocoa/conceptual/…Swartz
P
6

Disclamer

This is not intended to be "the good answer", as this question ask explicitly for ObjectiveC. As Apple introduced Swift at the WWDC14, I'd like to share the different ways to use block (or closures) in Swift.

Hello, Swift

You have many ways offered to pass a block equivalent to function in Swift.

I found three.

To understand this I suggest you to test in playground this little piece of code.

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, optimized for closures

As Swift is optimized for asynchronous development, Apple worked more on closures. The first is that function signature can be inferred so you don't have to rewrite it.

Access params by numbers

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

Params inference with naming

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

Trailing Closure

This special case works only if the block is the last argument, it's called trailing closure

Here is an example (merged with inferred signature to show Swift power)

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

Finally:

Using all this power what I'd do is mixing trailing closure and type inference (with naming for readability)

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!")
    }
}
Physoclistous answered 17/6, 2014 at 13:46 Comment(0)
O
-1

Hello, Swift

Complementing what @Francescu answered.

Adding extra parameters:

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)
Onward answered 21/7, 2014 at 17:56 Comment(0)
M
-4

You can follow the format below and can use the testingObjectiveCBlock property in the class.

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

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

For more info have a look here

Millimicron answered 24/1, 2014 at 6:41 Comment(1)
Does this answer really add anything more to the other answers already provided?Middaugh

© 2022 - 2024 — McMap. All rights reserved.