Objective-C - Storing block with parameters for callback - objective-c

I have a general routine, which takes a few parameters.
Something like:
-(id) doStuff:(int)A:(int)B:(int)C {
//doStuff
return object;
}
I have a UITableViewController, which houses a number of custom cells, each with their own ID. When 'Save' is hit, these cells are iterated and some cells need 'additional behaviour' when they are being saved.
Up to now, I've created a 'Callback' object, which stores an NSString * and a delegate in the custom cell. Upon being 'Saved', the cell looks, whether it has any callbacks to apply and uses
SEL sel = NSSelectorFromString(Sel);
if([Del respondsToSelector:sel])
[Del performSelector:sel withObject:Cell];
Now that works somewhat well..., however, it requires the method I pass to do a switch/case on the ID of the Cell that's passed, and I'd like to avoid that.
That's why I'd like to use blocks instead, but I don't really know how to store a parameterized block in a variable.
What I'm trying to do:
Declare a function block doStuff.
id (^doStuff) (int, int, int) = ^(int A, int B, int C) {
//does Stuff
};
And add the previously created block as callback
[Cell addCallback:(^doStuff)(1, 2, 3)];
The block must NOT be called at that moment, but stored in the cell and only called it when the time is right.
How would I go about this correctly?
Thank you very much.
Edit: What I'd also like to avoid is storing the parameters for the block in the cell and pass them upon calling, because that would require me to further specialize the cells unnecessarily.

It sounds like what you want is a block that calls your block, something like this:
[cell addCallback:^{ doStuff(1, 2, 3); }];
But this is a rather odd and convoluted design. It seems like there is probably a way to write it with only one block, but it's hard to give a solution that specific without a better idea of what you're doing.

The most straight forward way is to create a typedef containing how the block parameters should look like, then use it to declare a new property/ivar. The following sample code is copied from the Sensible TableView framework SCCellActions class:
typedef void(^SCCellAction_Block)(SCTableViewCell *cell, NSIndexPath *indexPath);
#interface SCCellActions : NSObject
...
#property (nonatomic, copy) SCCellAction_Block willDisplay;
...
#end
You could then set the property as follows:
cellActions.willDisplay = ^(SCTableViewCell *cell, NSIndexPath *indexPath)
{
cell.backgroundColor = [UIColor yellowColor];
};
Similarly, you could declare a parameter as follows:
...
- (void)callActionBlock:(SCCellAction_Block actionBlock)
{
if(actionBlock)
{
actionBlock(self.cell, self.cellIndexPath);
}
}
...
In which case the method should be called like this:
[myObject callActionBlock:^(SCTableViewCell *cell, NSIndexPath *indexPath {cell.backgroundColor = [UIColor yellowColor];}];

This answer is based on Chuck's suggestion and describes the pitfalls I encountered realizing it.
Creation:
Cell = [self CreateCell];
[Cell addCallback:^{ return doStuff(Cell, 1, 2, 3, 4) } At:ON_SAVE];
doStuff is a local block, declared before the cells. I was unable to add it directly to the cell, because I also needed a reference to the calling cell within the block.
Pitfall at this point: Class variables.
A block will only retain...or rather 'copy'...local variables, but not class variables.
Assuming that 'Cell' was a class variable and set by 'CreateCell', the block would work with the value of Cell at the time the block is executed.
As such, it is important to remember to declare a local variable, which assumes the value of the class variable if necessary.
Storage:
- (void) addCallback:(CallBlock_t)B At:(int)at {
//Creates a Callback-Object and passes it the block and adds it to an Array.
}
- (id) initWithBlock:(CallBlock_t)B At:(int)at {
self = [super init];
if(self) {
Block = [B copy]; //Yes, Copy. Not retain.
When = at;
}
return self;
}
Pitfall at this point: If the block is merely retained, the local block from the calling function will go out of scope and the program will fail with 'Bad Access'. Copy resolves this problem.
Of course you need to release the Block once you're done using it (in the dealloc of the callback class), but that's a given.
I hope this little explanation will save someone some grief.

Related

Objective C - call NSNumber outside FOR loop

It seems that i didn't cover my basics enough, but I hope that You guys mabybe will be able to help here.
I need to use cateringView.status outside this loop and even in another class. This is simple BOOL value, parsed from XML with PUGIXML
- (void)dataPrepared
{
Food* food = (Food*)[[DataManager sharedInstance] dataForItem:kDataManagerItemCatering];
if (food)
{
for (CateringView* cateringView in cateringViews)
[cateringView removeFromSuperview];
[cateringViews removeAllObjects];
for (FoodItem* item in food.catering)
{
CateringView* cateringView = [CateringView new];
[cateringView.imageView loadURL:[NSURL URLWithString:item.image] session:[DataManager sharedInstance].session completion:nil];
cateringView.status = item.status;
[self addSubview: cateringView];
[cateringViews addObject: cateringView];
}
[self layoutSubviews];
}
[super dataPrepared];
}
Could You explain me how can I do that?
My header file:
(...)
#interface CateringView : UIView
#property (strong) NSNumber* status;
#end
#interface CateringPreviewCell : PreviewCell
{
NSMutableArray* cateringViews;
(...)
}
#end
Is this somebody else's code you are trying to understand? It is unclear what you are asking, but you appear to be confusing the lifetime of local variables and objects. Maybe the following will help:
The second for loop starts:
for (FoodItem* item in food.catering)
{
CateringView* cateringView = [CateringView new];
This last statements does two things:
The right hand side (RHS) creates a new object of type CateringView. The result of the RHS is a reference to the created object. The lifetime of the created object extends as long as there is a reference to it[A].
The left hand side (LHS) creates a new local variable called cateringView. The reference returned by the RHS is stored in this variable. The lifetime of the created variable is a single iteration of the for loop.
At the end of the loop the code is:
[self addSubview: cateringView];
[cateringViews addObject: cateringView];
}
These two statements take the reference, to the created CateringView object, which is stored in the local variable cateringView and add it to this object's (which is an instance of the CateringPreviewCell class) subviews and cateringViews instance variable.
After these two statements have executed you have stored the reference to the created CateringView object three times: in the local variable cateringView, in the owning object's subviews, and in the owning object's cateringViews instance variable.
Also after these statements the loop iteration ends, so the lifetime of the local variable cateringView ends and you can no longer use that variable. However the reference to the object that was stored in that local variable still exists in two locations and that object is still alive.
You are stating you need to access cateringView.status outside of the loop. That does not make sense, the variable does not exist. However the object the variable referenced when it did exist is still alive, so the status value you seek is still around - you are just looking in the wrong place.
After the loop, and after the call to dataPrepared has returned, all the CateringView objects created can be accessed either:
as subviews of the object instance of CateringPreviewCell that dataPrepared was called on, or
as members of the instance variable cateringViews of that object instance.
The first of these is accessible "outside the class", the second can be provided you have instance methods defined on CateringPreviewCell which provided access to the instance variable.
HTH
[A]: This is not exactly true, but sufficient for the purpose here. Later you may learn about things such as weak references which do not govern how long an object lives.
You wouldn't use cateringView, because you actually have many of them. They're all stored in cateringViews so that's what you'd actually use. You'd either iterate all the views in that array or you'd choose one at a specific index to interact with.
Without knowing whether your intention is to try to add .status to
existing objects inside the cateringViews array
or
new objects that you add to cateringViews array
since your question has missing information and isn't very clear, here is the solution for #2
for (CateringView* cateringView in self.cateringViews) { //needed curly braces and self. to access property var
[cateringView removeFromSuperview];
}
[self.cateringViews removeAllObjects]; //again needed self.
for (int i = 0; i < food.catering.count; i++) { //make sure food.catering is array and not nil
CateringView* cateringView = [[CateringView alloc] init];
[cateringView.imageView loadURL:[NSURL URLWithString:item.image]
session:[DataManager sharedInstance].session
completion:nil];
FoodItem *item = food.catering[i]
cateringView.status = item.status;
[self addSubview: cateringView];
[self.cateringViews addObject: cateringView];
}
If I'm wrong and you're trying to accomplish #1, (consider improving your question wording if this is the case) then you'll need to loop through self.cateringViews as well.

Objective-C block "retain cycle" warning, don't understand why

I've seen several other questions of the same form, but I either a) can't understand the provided answers, or b) don't see how those situations are similar to mine.
I'm writing a Category on UIView to recursively evaluate all the subviews of a UIView and return an Array of subviews passing a test. I've noted where my compiler warning occurs:
-(NSArray*)subviewsPassingTest:(BOOL(^)(UIView *view, BOOL *stop))test {
__block BOOL *stop = NO;
NSArray*(^__block evaluateAndRecurse)(UIView*);
evaluateAndRecurse = ^NSArray*(UIView *view) {
NSMutableArray *myPassedChildren = [[NSMutableArray alloc] init];
for (UIView *subview in [view subviews]) {
BOOL passes = test(subview, stop);
if (passes) [myPassedChildren addObject:subview];
if (stop) return myPassedChildren;
[myPassedChildren addObjectsFromArray:evaluateAndRecurse(subview)];
// ^^^^ Compiler warning here ^^^^^
// "Capturing 'evaluateAndRecurse' strongly in this block
// is likely to lead to a retrain cycle"
}
return myPassedChildren;
};
return evaluateAndRecurse(self);
}
Also, I get a bad_access failure when I don't include the __block modifier in my block's declaration (^__block evaluateAndRecurse). If someone could explain why that is, that would be very helpful too. Thanks!
The problem here is that your block evaluteAndRecurse() captures itself, which means that, if it's ever to be copied (I don't believe it will in your case, but in slightly less-trivial cases it may), then it will retain itself and therefore live forever, as there is nothing to break the retain cycle.
Edit: Ramy Al Zuhouri made a good point, using __unsafe_unretained on the only reference to the block is dangerous. As long as the block remains on the stack, this will work, but if the block needs to be copied (e.g. it needs to escape to a parent scope), then the __unsafe_unretained will cause it to be deallocated. The following paragraph has been updated with the recommended approach:
What you probably want to do here is use a separate variable marked with __unsafe_unretained that also contains the block, and capture that separate variable. This will prevent it from retaining itself. You could use __weak, but since you know that the block must be alive if it's being called, there's no need to bother with the (very slight) overhead of a weak reference. This will make your code look like
NSArray*(^__block __unsafe_unretained capturedEvaluteAndRecurse)(UIView*);
NSArray*(^evaluateAndRecurse)(UIView*) = ^NSArray*(UIView *view) {
...
[myPassedChildren addObjectsFromArray:capturedEvaluateAndRecurse(subview)];
};
capturedEvaluateAndRecurse = evaluteAndRecurse;
Alternatively, you could capture a pointer to the block, which will have the same effect but allow you to grab the pointer before the block instantiation instead of after. This is a personal preference. It also allows you to omit the __block:
NSArray*(^evaluateAndRecurse)(UIView*);
NSArray*(^*evaluteAndRecursePtr)(UIView*) = &evaluateAndRecurse;
evaluateAndRecurse = ^NSArray*(UIView*) {
...
[myPassedChildren addObjectsFromArray:(*evaluateAndRecursePtr)(subview)];
};
As for needing the __block, that's a separate issue. If you don't have __block, then the block instance will actually capture the previous value of the variable. Remember, when a block is created, any captured variables that aren't marked with __block are actually stored as a const copy of their state at the point where the block is instantiated. And since the block is created before it's assigned to the variable, that means it's capturing the state of the capturedEvaluteAndRecurse variable before the assignment, which is going to be nil (under ARC; otherwise, it would be garbage memory).
In essence, you can think of a given block instance as actually being an instance of a hidden class that has an ivar for each captured variable. So with your code, the compiler would basically treat it as something like:
// Note: this isn't an accurate portrayal of what actually happens
PrivateBlockSubclass *block = ^NSArray*(UIView *view){ ... };
block->stop = stop;
block->evaluteAndRecurse = evaluateAndRecurse;
evaluteAndRecurse = block;
Hopefully this makes it clear why it captures the previous value of evaluateAndRecurse instead of the current value.
I've done something similar, but in a different way to cut down on time allocating new arrays, and haven't had any problems. You could try adapting your method to look something like this:
- (void)addSubviewsOfKindOfClass:(id)classObject toArray:(NSMutableArray *)array {
if ([self isKindOfClass:classObject]) {
[array addObject:self];
}
NSArray *subviews = [self subviews];
for (NSView *view in subviews) {
[view addSubviewsOfKindOfClass:classObject toArray:array];
}
}

Variable out of IBAction

I have problem that im trying to get solve for like week.
My goal is to get variable out of my IBAction, to use for example in -(void)viewDidLoad..
But as far as I am now I can use my variable only in my IBAction..
- (IBAction) changeLat:(NSNumber *)str {
longi = str;
double lop = longi.doubleValue;
NSLog(#"%f",lop);
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog (#"%#",lop);
}
It NSLog shows everything fine in action, but in view did load it doesn't even recorganize it.
If you create a variable inside of -IBAction, the scope of that variable is only that method, so you cannot access to that variable outside it.
If you want your variable to be global to your class, you have to create it in the declaration of your class, like this:
#interface MainViewController () {
#private
double lop;
}
Put this at the beginning of your .m file, and then lop would be accesible in all your class.
You can read more about the scope of the variables here:
http://www.techotopia.com/index.php/Objective-C_Variable_Scope_and_Storage_Class
Actually, IBAction is converted to void by the preprocessor. It's used by Interface Builder as a label that identifies this method as an action able to be related from an IB Object.
There's no way (AFAIK) to use two return types in a function (for example `(IBAction double)´, equivalent to ´(void double)´), but a good practice could be something like this:
- (IBAction)changeLatAction:(id)sender {
NSNumber *str = <get the NSNumber from a valid place>;
[self changeLat:str];
}
- (double) changeLat:(NSNumber *)str {
longi = str;
double lop = longi.doubleValue;
NSLog(#"%f",lop);
return ????;
}
Your first declaration of changeLat seems to be wrong, because as a first parameter you'll always get the "sender" or "caller" object, related from IB (when called from an action, of course), so, you need to get the str value from a valid place.
Cheers.

Using a block object instead of a selector?

I have:
[self schedule:#selector(tickhealth)];
And tickHealth method only has one line of code:
-(void)tickHealth
{
[hm decreaseBars:0.5];
}
is it possible to use block objects in place of a selector. for example something like:
[self schedule:^{
[hm decreaseBars:0.5];
}];
As Caleb & bbum correctly pointed out you cannot simply pass a block to your existing (and unchanged) - (void)schedule:(SEL)selector; method.
You can however do this:
Define block type:
typedef void(^ScheduleBlock)();
Change schedule: method to be defined similar to this:
- (void)schedule:(ScheduleBlock)block {
//blocks get created on the stack, thus we need to declare ownership explicitly:
ScheduleBlock myBlock = [[block copy] autorelease];
//...
myBlock();
}
Then call it like this:
[self schedule:^{
[hm decreaseBars:0.5];
}];
Further Objective-C block goodness compiled by Mike Ash that will get you kickstarted with blocks:
http://mikeash.com/pyblog/friday-qa-2008-12-26.html
http://mikeash.com/pyblog//friday-qa-2009-08-14-practical-blocks.html
http://mikeash.com/pyblog/friday-qa-2011-06-03-objective-c-blocks-vs-c0x-lambdas-fight.html
You can't just pass a block in place of a selector because those two things have different types. However, if you have control over the -schedule: method, you can easily modify it to accept and use a block in place of a selector.

How do I write to an NSObject from within a C function that doesn't see Obj-C variables?

I'm trying to get some code going that lets me display raw trackpad data from my macbook pro, like the app FingerMgmt. Unfortunately, no one seems to have the source for FingerMgmt. I did find some other source code that kind of works, however. I was able to NSLog the data I wanted to see like this:
int callback(int device, Finger *data, int nFingers, double timestamp, int frame) {
for (int i=0; i<nFingers; i++) {
Finger *f = &data[i];
NSLog(#"Frame %7d: Angle %6.2f, ellipse %6.3f x%6.3f; "
"position (%6.3f,%6.3f) vel (%6.3f,%6.3f) "
"ID %d, state %d [%d %d?] size %6.3f, %6.3f?\n",
f->frame,
f->angle * 90 / atan2(1,0),
f->majorAxis,
f->minorAxis,
f->normalized.pos.x,
f->normalized.pos.y,
f->normalized.vel.x,
f->normalized.vel.y,
f->identifier, f->state, f->foo3, f->foo4,
f->size, f->unk2);
//todo-get data from raw C to obj-C variable
}
return 0;
}
But whenever I try to store any of the data to an Obj-c string or variable, the C code does not see the variable as having been declared. Because of this, I cannot write to any text fields or graphical displays in Obj-C, and I cannot store the data to a variable that Obj-c can access.
Basically, I need a way to write to an Obj-C variable or object from within the callback.
On a side note, I had a very similar problem with an iPhone app a while back, and I ended up fixing it by somehow declaring the app delegate within the C code and writing to or reading from the variable like this-
me.delegate=(id <UIApplicationDelegate,UITabBarControllerDelegate>)[[UIApplication sharedApplication] delegate];//allows access to the delegate within C function
me.delegate.number0=5;//writes to this variable in the delegate
For some reason, I can not seem to adapt this to my current situation. I always get the error that "me" is undeclared.
A Objective-C method can access instance variables because it is automagically passed a hidden parameter with the public name self - any reference to an instance variable, say fred, is translated by the compiler into a field reference, say self->fred (and a similar translation for property references).
For your C function callback to access the fields of any object (or call an object's methods) you need to pass the function a reference to the object. Two simple ways:
Add an argument to the function. Many C callback protocols include a general "user defined" values which is passed around as void *, if you are calling one of these pass your object reference as this value and cast it within the C function back to the correct Objective-C type.
Pass the object via a global (or file static) variable, e.g. static NSSomeType *objectForCallback;. This method works when you're stuck with an existing C callback protocol which doesn't support a user defined value. However it is not thread or re-entrant safe as you are sharing a single static variable.
In both cases make sure the objected is retain'ed if you're not using garbage collection.
In response to comment
Case 1: You will see C functions declared which (a) take a callback function and (b) a user-defined value to pass to that function on every call. For example:
typedef T ...;
T findMatching(T *buffer, // an array of T to search
size_t count, // number of items in array
int (*matcher)(T item, void *user), // user function for match, 1st arg is item, 2nd user-supplied value
void *userValue); // user-supplied value to pass to matcher
If you are faced with C function like this you can pass a (retain'ed if needed) Objective-C object as userValue and cast it back to its Objective-C type inside matcher. For example:
int myMatcher(T item, void *user)
{
NSMutableDictionary *myDictionary = (NSMutableDictionary *)user;
...
}
- (void) someMethod
{
NSMutableDictionary *sharedWithC = ...;
...
T found = findMatching(buffer, count, myMatcher, (void *)sharedWithC);
...
}
Case 2: Objective-C is (a superset of) C. You declare a global just as you would in C. For example (little checking, not thread safe):
static NSMutableDictionary *myGlobalDictionary = nil; // "static" makes the variable only visible to code in the same file
- (void) setupTheSharedDictionary
{
myGlobalDictionary = [[[NSMutableDictionary alloc] init] retain];
}
- (void) releaseTheSharedDictionary
{
if(myGlobalDictionary != nil)
{
[myGlobalDictionary release];
myGlobalDictionary = nil;
}
}
In response to second comment
I'm guessing you are trying to use some third party (Google?) code. That code defines a callback protocol - a C function type. You cannot just redefine that C function type adding an extra argument and expect the third party code to magically cope!
So unless you intend to change the C you can use the second approach - store the reference to Objective-C object in a global. In your case this will be something like:
static MT2AppDelegate *sharedWithCAppDelegateReference;
int callback(...)
{
...
[sharedWithCAppDelegateReference->L1 setStringValue:#"Hellofff"];
...
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
sharedWithCAppDelegateReference = self; // store so C can pick it up
...
MTRegisterContactFrameCallback(dev, callback);
...
}
But remember this is not thread or re-entrant safe - you are effectively passing a function parameter via a global variable. If you need it to be thread/re-entrant safe you need to get a bit more involved.