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
When Does a Uitableview's Contentsize Get Set
Convert Mkcoordinateregion to Mkmaprect
Take a Picture on iPhone Without Showing Controls
Ios7 Uiswitch Its Event Valuechanged: Calling Continuously Is This Bug or What..
How to Fix the "Uipopovercontroller Is Deprecated" Warning
Barcode Generation from Within iOS App
How to Save Nsmutablearray or Nsdictionary Data as File in iOS
How to Implement Two Inits with Same Content Without Code Duplication in Swift
How to Draw Radial Gradients in a Calayer
Xcode 4: Build Failed, No Issues
Add Left/Right Horizontal Padding to Uilabel
Automatic Otp Verification in iOS
How to Test My Xcode 7.2-Compiled App with iOS 9.3
iOS Calendar Access Permission Dialog, Force It to Appear
How to Fill Uitableview with a Data from Dictionary. Swift