How to Use Objective-C Blocks as Properties

Can I use Objective-C blocks as properties?

@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;

How to store blocks in properties in Objective-C?

Edit: updated for ARC

typedef void(^MyCustomBlock)(void);

@interface MyClass : NSObject

@property (nonatomic, copy) MyCustomBlock customBlock;

@end

@implementation MyClass

@end

MyClass * c = [[MyClass alloc] init];
c.customBlock = ^{
NSLog(@"hello.....");
}

c.customBlock();

Defining Objective-C blocks as properties - best practice

By default blocks are created on the stack. Meaning they only exist in the scope they have been created in.

In case you want to access them later they have to be copied to the heap by sending a copy message to the block object. ARC will do this for you as soon as it detects a block needs to be accessed outside the scope its created in. As a best practise you declare any block property as copy because that's the way it should be under automatic memory management.

Read Stack and Heap Objects in Objective-C by Mike Ash for more info on stack vs. heap.

Objective-C accessing properties inside block

There is no problem in accessing the clients property because it is a strong (i.e. retained) property. So you don't need the __block here.

One problem can be that self might not exist anymore when the notification is sent. Then you would access the deallocated object and the app can crash! To avoid that you should remove the observer in the dealloc method.

The __block before id observer is definitely required !

EDIT:

In iOS 5 you can safely capture self using a weak reference:

__weak id weakSelf = self;

Then inside the block you can safely use weakSelf.clients. The variable weakSelf will turn into nil automatically when the object is deallocated.

OC @property with block in category

The cause of the actual crash you are getting is probably down to a misunderstanding of associated objects. When you associate an object you do so to a specific instance, not to all instances of the same type.

Looking at your code:

[str configureAppend] ;
[str configureReplacingRangeWithStringProperty] ;

At this point you have associated two objects with the particular instance that str is referring to. Now you try to do:

str = str.replacingRangeWithString(NSMakeRange(6, 5),@"iOS").append(@" hhhhh") ;

Breaking this up:

str.replacingRangeWithString

This invokes the property replacingRangeWithString on whatever object instance str is referencing. Let's call that object A. Now your previous two statements associated objects with A, so this works and you get your block reference back.

str.replacingRangeWithString(NSMakeRange(6, 5),@"iOS")

This invokes the block and returns a different string, call that object B.

str.replacingRangeWithString(NSMakeRange(6, 5),@"iOS").append

This invokes the property append on object B, you have associated no objects with object B so this returns a null reference.

str.replacingRangeWithString(NSMakeRange(6, 5),@"iOS").append(@" ahhhh")

You attempt to call your "block", but you have a null reference => memory fault.

Update

At this point I originally suggested that you need to rethink your design - you do - and that a solution might not be as simple as you might like - and since then it dawned on me that there might be a simple approach...

Provided you are just trying to replace method calls with property accesses so you can neatly chain calls together; i.e. that you've no intention of calling your configure... methods with blocks which perform different operations; then you can dynamically create and return a block in your property. Here is the outline for append. First make the property read only:

@property(nonatomic, readonly) MethodAppend append;

and then define it using:

-(MethodAppend)append
{
NSString *copied = self.copy;
return ^(NSString *string){ return [copied stringByAppendingString:string]; };
}

This first copies the string in case the instance it is called on is an NSMutableString, you don't know how long after this property is invoked that the block will be called and the string value could have been changed by that point. Then it returns a block which accepts the required arguments and calls the intended method.

That is all that is required, no setters or configure methods, and no associate objects.

HTH

Setting object property inside a block trouble on Objective-C

It's because the setting of JSONObjectsCollection happens asynchronously. So your method is returning JSONObjectsCollection before it is set.


Thus, it might look like:

- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];

// do here whatever you want to do now that you have your array, e.g.
//
// [self.tableView reloadData];

} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(@"Communication Error: %@", error);
}];

[operation start];
}

Note, retrieveAtEndpoint now has a void return type, but in the completion block, I'm invoking whatever code you want to perform once the JSON objects collection has been updated.


If this is a method inside your model object, but you want to provide an interface by which the view controller can supply a block of code that should be executed upon successful retrieval of the JSON, use a completion block:

- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(void (^)(NSError *error))completion
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat: endpointURL, fuegoWSURL]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSDictionary *dict = (NSDictionary *) JSON;
[self setJSONObjectsCollection: [dict objectForKey:rootNode]];

if (completion)
{
completion(nil);
}

} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
if (completion)
{
completion(error);
}
}];

[operation start];
}

Or, if you want to simplify your use of a block parameter, you can define a type for the completion block at the start of your model object's .h file (before the @interface block):

typedef void (^RetrievalCompleteBlock)(NSError *);

And then the method is simplified a bit:

- (void)retrieveAtEndpoint:(NSString *)endpointURL withRootNode:(NSString *)rootNode completion:(RetrievalCompleteBlock)completion
{
// the code here is like it is above
}

Anyway, regardless of whether you use the typedef or not, the view controller could do something like:

ModelObject *object = ...
NSString *rootNode = ...
[object retrieveAtEndpoint:url withRootNode:rootNode completion:^(NSError *error) {
if (error)
{
// handle the error any way you want, such as

NSLog(@"%s: retrieveAtEndPoint error: %@", __FUNCTION__, error);
}
else
{
// do whatever you want upon successful retrieval of the JSON here
}
}];

The details here will vary based upon how your view controller is accessing the model object, knows that the root node should be, etc. I often will include another parameter to my completion block which is the data being retrieved, but given that you updated your model object and can access it that way, perhaps that's not necessary. I simply don't have enough details about your implementation to know what is right here, so I kept my implementation as minimalist as possible.

But hopefully this illustrates the idea. Give your retrieveAtEndpoint method a completion block parameter, which lets the view controller specify what it wants to do upon completion (or failure) of the communication with the server.

Store a block object in objective C

Give your block a name as:

typedef void (^ YOUR_BLOCK_NAME)(BOOL success, id responseObject, NSError * error);

And then store as any other property:

@property (nonatomic, copy, readwrite) YOUR_BLOCK_NAME block;

You can create block as:

YOUR_BLOCK_NAME block = ^(BOOL success, id responseObject, NSError * error) {};

Why use copy for block properties?

Because a block literal is created on the stack and will be destroyed when the function exits. When keeping a block around it's necessary to copy it.



Related Topics



Leave a reply



Submit