Im trying to create a simple callback using blocks.
I have a MainViewController which addSubView another DatePickerViewController.view i created a block like this
typedef void(^DateChangedBlock)(NSDate*);
And i have a method on my DatePickerViewController called
setOnDateChangedCallback:(DateChangedBlock)callback
I store the callback in a property of the DatePickerViewController. The DatePickerViewController's view is a UIDatePicker instance, ive bound an IBAction to the value changed to a method that does this.
- (IBAction)dateChanged:(id)sender {
if (dateChangedCallback != nil)
{
dateChangedCallback(nil);
}
}
Here is how i register the block in the MainViewController
DatePickerViewController *dateController = [[DatePickerViewController alloc] initWithNibName:#"DatePickerView" bundle:nil];
self.datePicker = dateController;
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)];
[self.view addSubview:textView];
DateChangedBlock myBlock = ^(NSDate *newDate) {
textView.text = #"testing";
};
[self.datePicker setOnDateChanged: myBlock];
[self.datePicker dateChanged:self]; // force trigger (this works).
When i force trigger the dateChanged method on the DatePickerViewController it works no problem. But when the datepicker itself triggers the method thru the IBAction i get a EXC_BAD_ACCESS error. The error occurs in this method on the "int retVal" line.
#import <UIKit/UIKit.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil); // THREAD 1: program received EXC_BAD_ACCCESS.**
[pool release];
return retVal;
}
What am i doing wrong?
You should copy your block when passing it to other method (such as attribute setter in your situation). So when setting the callback block do this:
[self.datePicker setOnDateChanged:[[myBlock copy] autorelease]];
You get the EXC_BAD_ACCESS cause block's variables used when creating the block don't get retained by the block itself. So when calling the block - variables do not exist anymore.
The accepted answer is wrong. You don't need to copy it in your MainViewController. Rather, the setOnDateChangedCallback: method which takes a block argument is responsible for copying it if it needs to store it in an instance variable (which I see it is doing, in the variable dateChangedCallback that is later used by another method). If dateChangedCallback is a synthesized property, you can accomplish this by declaring the property as copy instead of retain.
You can only update the UI from the UI thread. Since your block is running on another thread, you're getting the exception when updating the text of the textView. You can run code on the UI thread from within a block by using the main queue (dispatch_get_main_queue()).
Related
I have two class files hudlayer.m and actionlayer.m
I have a method named jump in hudlayer.m
And i have a method named jumpone in actionlayer.m
-(void) jumpone {
_heroBody->ApplyLinearImpulse(b2Vec2(_playerVelX/[_lhelper pixelsToMeterRatio], 1.25), _heroBody->GetWorldCenter());
}
and another method called jump in hudlayer.m
-(void)jump {
ActionLayer *aob = [[ActionLayer alloc] init];
[aob jumpone];
}
The problem is when i call the Jumpone method from actionlayer.m my sprite jumps (i.e method called)
My init method of action layer
- (id)initWithHUD:(HUDLayer *)hud
{
if ((self = [super init])) {
[self setupWorld];
}
return self;
}
But when i call jumpone via jump method in from hudlayer.m it fails and my app crashed.
Any help will be appreciated .thanks
the best solution for your problem is to add a tag to hudlayer & action layer
ex: hudlayer.tag=1;
actionlayer.tag=2;
and then just use getChildByTag like this:
[[[[CCDirector sharedDirector]runningScene] getChildByTag:1]jumpone];
Everytime you call jump it creates a new instance of you ActionLayer. And following that, you setup a new world and everything get tangled up. Furthermore its a memory leak.
Make you ActionLayer to an iVar of HUDLayer and call
aob = [[ActionLayer alloc] init];
in the HUDs init method.
Dont forget to release aob in dealloc of the HUDLayer
I have object with .delegate property which i manipulate in method 'doJob'. I assign this property with 'self' and my function is being called when this object finishes his job. Till now everything is fine.
Now i want to manipulate this object in a separate thread.
I'm using [NSThread detachNewThreadSelector...] to run the 'doJob' function.
In this case my delegate method not being called. I guess this is because 'self' points to new thread instead of main one. Ok. I'm passing self as argument to function while creating the thread and it still not working. What do i miss?
my current code is as follows:
- (void)mainFunction
{
[NSThread detachNewThreadSelector:#selector(doJob:) toTarget:self witObject:self];
}
- (void)doJob:(MyObject*)parentThread
{
ManipulatedObject *obj = [[ManipulatedObject alloc] init];
obj.delegate = parentThread;
[object startJob];
}
GCD will make most of your multi-threading troubles trivial. You can do something like this:
- (void)mainFunction
{
// Runs your task on a background thread with default priority.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ManipulatedObject * obj = [[ManipulatedObject alloc] init];
[obj startJob]; // I'm assuming this is sychronous.
// The callback is explicitly run on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Your callback here.
[obj release];
});
});
}
That's all you have to do, it's that simple. All the relevant code is inline and together.
If you want the ManipulatedObject to explicitly invoke the block, then you could add that ability to ManipulatedObject. To do so, you should:
Define the block type for convenience typedef void(^MyCallback)();
Add #property (nonatomic, copy) MyCallback block; and #synthesize block. Don't forget the copy.
Invoke the block when you need to dispatch_async(dispatch_get_main_queue(), [self block]);.
If your delegate needs to make more than one kind of callback, then you will need a block for each callback. It's a minor inconvenience, but it's worth it for all the conveniences you gain.
For a more thorough explanation of blocks and GCD, check out WWDC 2011 session 308.
Well firstly you do not need to pass self as the witObject: parameter, (which is spelt wrong) because - (void)doJob:(MyObject*)parentThread is still in the same object (self is the same in both threads), self has nothing to do with your main thread its MyObject presumable, you also have a problem were you are not creating a new autorelease pool for your doJob:, doJob: should look like
- (void)doJob:(MyObject*)parentThread
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
ManipulatedObject *obj = [[ManipulatedObject alloc] init];
obj.delegate = parentThread;
[object startJob];
[pool release];
}
you have to give us some information about how you're delegate method is being called, if it is tying to use timers or something like that then you are going to have problems because there is no runloop to add your timer to.
I am hoping to clarify the processes going on here.
I have created a subclass of UIButton whose init method looks like this:
- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
self = [UIButton buttonWithType:UIButtonTypeCustom];
[self setTitle:title forState:UIControlStateNormal];
self.frame = btnFrame;
return self;
}
In my view controller I am creating one of these buttons and adding it as a subview:
myButton = [[CustomButton alloc] initWithTitle:#"Title" frame:someFrame];
[self.view addSubview:myButton];
In the view controller's dealloc method I log the retain count of my button:
- (void)dealloc {
NSLog(#"RC: %d", [myButton retainCount]); //RC = 2
[super dealloc];
NSLog(#"RC: %d", [myButton retainCount]); //RC = 1
}
The way I understand it, myButton is not actually retained, even though I invoked it using alloc, because in my subclass I created an autorelease button (using buttonWithType:).
In dealloc, does this mean that, when dealloc is called the superview releases the button and its retain count goes down to 1? The button has not yet been autoreleased?
Or do I need to get that retain count down to zero after calling [super dealloc]?
Cheers.
This deserves two answers.... one for the specific question and one for how memory is managed when the instance is replaced in -init (this one).
Initializers are an odd bird in the Objective-C memory management world. In effect, you are managing self. On entry, self is retained. On exit, you are expected to return either a retained object -- doesn't have to be the same object as self -- or nil.
So, breaking the standard idiom of [[[Foo alloc] init] autorelease] down:
id x = [Foo alloc]; // allocates instance with RC +1
x = [x init]; // RC is preserved, but x may differ
[x autorelease]; // RC -1 (sometime in future)
Note that all retain counts [RC] are expressed as deltas.
Thus, in the init method, you typically don't change the retain count of self at all!
However, if you want to return some other object, you need to release self and retain whatever you are going to return (whether allocated then or previously allocated somewhere else, say when an object is retrieved from a cache).
Specifically, with everything blown out into individual expressions because this answer is being overly pedantic:
- init {
[self release];
self = nil;
id newObject = [SomeClass alloc];
newObject = [newObject init];
if (newObject) {
self = newObject;
... initialize self here, if that is your fancy ...
}
return self;
}
This is more than a little bit tricky. I have summarized my answer in 5 parts:
Creating a custom init method that returns a different object
WARNING: beware of illegal memory access!
How to properly transfer ownership of the button to its parent view
Specific answers to specific questions
A suggestion for improvement
Part 1 : Creating a custom init method that returns a different object:
This is an example of a very special case, namely that the object returned from -initWithTitle:frame: is not the same "object" that was sent the message in the first place.
Normally speaking, the process goes like this:
instance = [Class alloc];
[instance init];
...
[instance release];
Of course, the alloc and init calls are usually grouped together into one line of code. The key thing to notice here is that the "object" (nothing more than an allocated block of memory at this point) which receives the call to init has already been allocated. If you return a different object (as in your example), you are responsible for releasing that original block of memory.
The next step would be to return a new object that has the proper retain count. Since you are using a factory method (+buttonWithType:), the resulting object has been autoreleased, and you must retain it to set the proper retain count.
Edit: A proper -init method should explicitly test to make sure that it is working with a properly initialized object before it does anything else with that object. This test was missing from my answer, but present in bbum's answer.
Here is how your init method should look:
- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
[self release]; // discard the original "self"
self = [UIButton buttonWithType:UIButtonTypeCustom];
if (self == nil) { return nil; }
[self retain]; // set the proper retain count
[self setTitle:title forState:UIControlStateNormal];
self.frame = btnFrame;
return self;
}
Part 2: WARNING: beware of illegal memory access!
If you are allocating an instance of CustomButton, and then replacing it with an instance of UIButton, you could easily cause some very subtle memory errors. Let's say CustomButton has some ivars:
#class CustomButton : UIButton
{
int someVar;
int someOtherVar;
}
...
#end;
Now, when you replace the allocated CustomButton with an instance of UIButton in your custom init... method, you are returning a block of memory that is too small to hold a CustomButton, but your code will continue to treat this block of code as if it is a full-sized CustomButton. Uh oh.
For example, the following code is now very, very bad:
- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
[self release]; // discard the original "self"
self = [UIButton buttonWithType:UIButtonTypeCustom];
[self retain]; // set the proper retain count
someOtherVar = 10; // danger, Will Robinson!
return self;
}
Part 3: How to properly transfer ownership of the button to its parent view:
As for your view controller's dealloc method, you will have to call [myButton release] if you have initialized the button as shown. This is to follow the rule that you must release anything that you alloc, retain or copy. A better way to deal with this issue is to let the controller's view take ownership of that button (which it does automatically when you add the button as a subview):
myButton = [[CustomButton alloc] initWithTitle:#"Title"
frame:someFrame]; // RC = 1
[self.view addSubview:myButton]; // RC = 2
[myButton release]; // RC = 1
Now, you never have to worry about releasing that button again. The view owns it, and will release it when the view itself is deallocated.
Part 4: Specific answers to specific questions:
Q: The way I understand it, myButton is not actually retained, even though I invoked it using alloc, because in my subclass I created an autorelease button (using buttonWithType:).
Correct.
Q: In dealloc, does this mean that, when dealloc is called the superview releases the button and its retain count goes down to 1? The button has not yet been autoreleased?
Also correct.
Q: Or do I need to get that retain count down to zero after calling [super dealloc]?
Sort of :) The retain count may or may not drop down to zero at the point when you log it. Autoreleased objects may still have a retain count of one, since they effectively belong to the autorelease pool for the current run loop. For that matter, the view itself may still belong to a window which has not yet been released. The only thing you really need to worry about is balancing out your own memory management. See Apple's memory management guide for details. From the point of view of your viewController, you have allocated the button once, so you must release it exactly once. When it comes to your custom init... method, things get a little bit trickier, but the principle is the same. A block of memory has been allocated, so it must be released (part 1), and, (part 2) init should return an object with a retain count of one (to be properly released later on).
Part 5: A suggestion for improvement:
You could avoid most of the custom initializer mess by simply creating your own factory method in the same spirit as the one provided by UIButton:
+ (id)buttonWithTitle:(NSString *)title frame:(CGRect)btnFrame {
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:title forState:UIControlStateNormal];
button.frame = btnFrame;
return button;
}
Note that this approach can still result in memory access errors as identified in part 2
First:
Do not call retainCount
The absolute retain count of an object is next to useless. There are always better ways to reason about memory management in your application.
Next:
Your initWithTitle:frame: method is allocating and returning an instance of UIButton, not an instance of the subclass. If that is what you want, there is no need for a subclass at all.
If you really want an instance of a subclass of UIButton, that is going to be more difficult. A quick google search and a read of the documentation indicates that UIButton really isn't intended to be subclassed.
I just tried:
#interface FooButton:UIButton
#end
#implementation FooButton
#end
FooButton *f = [FooButton buttonWithType: UIButtonTypeDetailDisclosure];
NSLog(#"%#", f);
And it printed:
<UIButton: 0x9d03fa0; frame = (0 0; 29 31); opaque = NO; layer = <CALayer: 0x9d040a0>>
I.e. the sole method to be used to create UIButton instances quite explicitly does not allocate via [self class]. You could go down the path of trying to initialize the instance by hand ala UIView or UIControl, but that is likely a lot of trouble.
What are you trying to do?
(or even just one class with one delegate)
Say I have a class called DataGetter, which downloads a file from the web. It has a delegate method, which gets triggered when the file has been downloaded:
- (void) dataGetterFinished:(DataGetter *)dataGetter;
So somehere in my code I can set up several files to be downloaded like so:
// in AViewController.m
DataGetter *blueFile = [[DataGetter alloc] init];
blueFile.delegate = self;
[blueFile getData:#"http://example.com/blue-file"];
DataGetter *redFile = [[DataGetter alloc] init];
redFile.delegate = self;
[redFile getData:#"http://example.com/red-file"];
Using clang static analyzer, each alloc line above gets a 'potential leak of an object allocated on line…' error. So how would I release the object. It has to hang around because it has a delegate. So is it OK to release it as the last line of the dataGetterFinished method, like so
- (void) dataGetterFinished:(DataGetter *)dataGetter
{
// code
[dateGetter release];
}
...or should I be using autorelease somehow?
Technically, that works fine, however I would suggest keeping track of the different DataGetters in an NSMutableArray.
For example:
DataGetter *blueFile = [[DataGetter alloc] init];
blueFile.delegate = self;
[blueFile getData:#"http://example.com/blue-file"];
[dataGetters addObject:blueFile]; // dataGetters is an NSMutableArray declared in the .h
[blueFile release];
// Same for red
Then in the delegate method, simple remove the getter from the array:
- (void) dataGetterFinished:(DataGetter *)dataGetter
{
// code
[dataGetters removeObject:dataGetter];
}
The array takes care of retaining the object, and you don't get the analysis warning.
Just be sure to release dataGetters in the dealloc method.
I'm trying to implament a callback mechanism where I pass in a block to the init of a class, and after some work that class calls me back. The block gets called and most everything works, except when I call anything on "self" within the block. I get Program received signal: “EXC_BAD_ACCESS” unless I comment out any reference to self within the block.
Am I wrong to think that I can access self within the block?
any advice would be greatly appreciated. This code is in a brand new "Universal app" and I'm currently working on the IPad portion, so running under the IPad simulator.
Some code:
__block LoginViewController *blockSelf = self;
LoginAlertView *alert = [[LoginAlertView alloc]
intWithPinPrompt:NO
title:#"Mobile Login"
myCallback:^(LoginAlertView *v){
DLog(#"self %#", blockSelf);
NSString *u = v.userNameText;
NSString *p = v.passwordText;
NSString *i = v.pinText;
[self authenticateUser:u
password:p
pin:i];
}];
and here is the some code from the LoginAlertView
- (id) intWithPinPrompt:(BOOL)pinPromptEnabled title:(NSString*)aTitle myCallback:(loginClicked)aCallback{
if (self = [self initWithTitle:aTitle
message:nil
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Login", nil]) {
hasPinPrompt = pinPromptEnabled;
theCallback = aCallback;
}
return self;
}
and the callback call
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (theCallback) {
theCallback(self);
}
}
I changed to following line of code
theCallback = aCallback;
to
theCallback = [aCallback copy];
which is presenting me with the following error
Program received signal: “EXC_BAD_ACCESS”.
(gdb) bt
#0 0x029c8c97 in objc_msgSend ()
#1 0xbfffe560 in ?? ()
#2 0x00026f66 in -[LoginViewController show] (self=0x4a38450, _cmd=0x209c36c) at LoginViewController.m:18
#3 0x00026d3b in -[AuthenticatedViewController viewWillAppear:] (self=0x4c1fac0, _cmd=0x20b59ac, animated=0 '\0') at AuthenticatedViewController.m:17
one other thing, the definition of my block looks like this
typedef void(^loginClicked)(LoginAlertView*);
and the member variable is this
loginClicked theCallback;
also tried moving the declaration of the block up to a variable, and passing that in. this had the same results Anytime I use the "copy" on the bock I get the dreaded Program received signal: “EXC_BAD_ACCESS”. I thought maybe this was a IOS3.2 thing, so I tried running it under the IPhone 4.0 simulator, same results. Is there possibly a compiler setting that needs to be made in order to "Copy" the block onto the heap? I'm using LLVM1.5
First, what is blockSelf in your code?
Secondly, no, there is no reason why you can't use self and this is indicative of a bug in your code.
Specifically, you aren't copying the block. Blocks start out on the stack. Thus, when you say theCallback = aCallback;, you are storing a reference to an on-stack data structure. As soon as the defining stack frame goes away, that structure is no longer valid.
Change theCallback = aCallback; to theCallback = [aCallback copy]; (and add [theCallback release]; to your -dealloc method).