I have an initializer with two arguments:
-(id) initWithSourceFrame:(CGRect)sourceViewFrame mappedFrame:(CGRect)mappedViewFrame {
CGRect copy = mappedViewFrame;
self = [super init];
if (self) {
// not able to access mappedViewFrame here..
// copy is ok
// doing initialization here...
}
return self;
}
It seemed to get wrong values from the second argument (mappedViewFrame). When looking for the error, I found out that mappedViewFrame gets destroyed (overridden in memory?).
This can be easily observed in the debugger:
Debugger Screenshot on flickr (I cannot post images yet)
The copy is still holding the original values, so using the copy was a workaround in this case. But of course I want to understand why this could happen.
The class is a direct subclass of NSObject, the whole project is a OS X native app.
The first argument was never destroyed. The problem does not relate to the values passed in. I switched them and it was always the second which was corrupted.
For example, I called the initializer with these example arguments (different from those in the debugger screenshots), and the error occured in the same way:
Mapper *mapper = [[Mapper alloc] initWithSourceFrame:CGRectMake(0, 0, 100, 100) mappedFrame:CGRectMake(0,0, 200,200)];
The method declaration:
-(id) initWithSourceFrame:(CGRect) sourceViewFrame mappedFrame:(CGRect) mappedViewFrame;
I'm somewhat new to Objective-C, so I am sorry if I missed something obvious. However, it looks strange that an argument does not keep valid during method invocation.
I stumbled upon a possible solution while I tried to reproduce the error using NSLog instead of the debugger.
The funny thing was, the logged values were correct. Just before the call to NSLog the debugger shows still the garbage output as in the screenshots. But after accessing the value (by NSLog or another programmatic access) the debugger shows the correct values.
So it looks more like a problem with the debugger. The wrong display of values is more likely to appear if some objects are allocated.
Remains the question: Why did my program not work? I now removed the 'copy' variable (from line 2) and access mappedViewFrame directly and everything works as expected. So the same code which didn't work before now works, I think this can be of two reasons:
There was another error in my implementation, which caused the wrong computation results. When introducing the 'copy' variable (after seeing the weird debugger output), I fixed the other error without noticing. Afterwards, I credited the fix to my new copying solution.
An unknown factor caused the variable to be distorted when I first encountered the problem and does not do so now. Well, to be shure I left the copy there, and check if the value remains unchanged. If this will happen, I will post it here. Until then, the first theory is much more likely.
This thing teached me not to trust the debugger, and instead use logging when in doubt, which is a pain. If anybody knows what causes the strange debugger output or how to fix it I would greatly appreceate it. If I have time I will do further look into when this does appear, if it happens just in xcode or is a problem of the gnu debugger in general.
I have tried this quickly in a few minutes. I've never really tried to create a Mac OS X project, just Foundation and iPhone projects, but the idea seems the same. I created a Cocoa Application project "Stack1". I'm assuming you have done the same, because I don't think a Foundation project would support CGRect structs without manually importing CGGeometry.h or something... Anyway, I set up a new Objective C class called "Mapper" and implemented the initWithSourceFrame:mappedFrame: method you specified.
Mapper.h
#import <Cocoa/Cocoa.h>
#interface Mapper : NSObject {
}
-(id) initWithSourceFrame:(CGRect) sourceViewFrame mappedFrame:(CGRect) mappedViewFrame;
#end
Mapper.m
#import "Mapper.h"
#implementation Mapper
-(id) initWithSourceFrame:(CGRect)sourceViewFrame mappedFrame:(CGRect)mappedViewFrame {
NSLog(#"height: %f, width: %f", mappedViewFrame.size.height, mappedViewFrame.size.width);
self = [super init];
NSLog(#"height: %f, width: %f", mappedViewFrame.size.height, mappedViewFrame.size.width);
if (self) {
NSLog(#"height: %f, width: %f", mappedViewFrame.size.height, mappedViewFrame.size.width);
}
return self;
}
#end
I didn't alter Stack1AppDelegate.h.
Stack1AppDelegate.m
#import "Stack1AppDelegate.h"
#import "Mapper.h"
#implementation Stack1AppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
Mapper *mapper = [[Mapper alloc] initWithSourceFrame:CGRectMake(0, 0, 100, 100) mappedFrame:CGRectMake(0,0, 200,200)];
}
#end
All my code did was allocate a Mapper instance as yours did and also use NSLog to print the width and height before [super init], after [super init], and also within the if (self) block.
Here's the log:
2011-01-23 16:09:07.167 Stack1[935:a0f] height: 200.000000, width: 200.000000
2011-01-23 16:09:07.170 Stack1[935:a0f] height: 200.000000, width: 200.000000
2011-01-23 16:09:07.170 Stack1[935:a0f] height: 200.000000, width: 200.000000
I've also uploaded the whole project here:
http://ak.net84.net/files/temp/Stack1.zip
Related
Recently I learn that if I need to keep a block object, I should copy the block, because it's created on the stack.
Then I review some of my code. I found that I was doing something like:
#implementation MyView
...
-(void) addButton:(NSString *)title clickAction:(void ^(void)) action {
[self.buttons addObject:#[ title, action ] ];
}
-(void) createButtons { ... }
...
#end
...
// Somewhere in my project:
-(void) init {
MyView *view = [MyView new];
[view addButton:#"OK" clickAction:^{ NSLog(#"OK"); }];
[view addButton:#"Cancel" clickAction:^{ NSLog(#"Cancel"); }];
}
Basically, I add some buttons info to MyView. At some point, MyView will create the buttons. When the button is click, MyView will find out the corresponding clickAction, and invoke it.
The weird part is that this program works fine, no exception, no error.
So, why the program runs without copying the block object?
Other info :
* iphone app ( 4.3 - 6.0 )
* non-ARC
* XCode 4.5
Behind the scene, block implementation consists of two parts:
The executable code of the block, and
A data structure containing the values of the variables used in the block
Only the second part of the block is placed on the stack and copied to the heap memory; the first part is compiled to the code segment of your program, and does not get copied.
Since your block implementations do not reference any variable from the surrounding scope, the data portion of their blocks is empty. That is why your code works: there is simply nothing to copy! However, you should add block copy anyway, because otherwise a small change to the code of your block will break your application without the compiler noticing anything.
The compiler creates different types of blocks depending on what the block does. If a block doesn't capture any variables then it can be represented by an NSGlobalBlock (NSGlobalBlock is little more than a function pointer). I suspect that's what's happening.
in my project I'm managing several Drawing objects. I'm trying to add a copy of a SmartPath object to a drawing object. This works. But when the drawing gets deallocated the SmartPath does not. I have put some extra code in the dealloc of the Drawing to explicitly set clear the pointer to the SmartPath. For some reason this works (the retain count was 1). I know I can probably copy the SmartPath and assign that to a strong parameter to fix this leak. But I'm relatively new to IOS and want to know how to use the copy parameters properly in combination with ARC.
Here is the code:
Drawing.h:
#interface Drawing : NSObject{
#private
SmartPath* rawLinePath; //path that is build up from straight lines between input points
SmartPath* smoothLinePath; //smoothened version of rawLinePath
}
#property(atomic,copy)SmartPath* rawLinePath;
#property(atomic,copy)SmartPath* smoothLinePath;
Drawing.m
#implementation Drawing
#synthesize rawLinePath;
#synthesize smoothLinePath;
-(id)init
{
if (self = [super init])
{
[NSThread detachNewThreadSelector:#selector(pointMonitor)
toTarget:self
withObject:nil];
}
return self;
}
-(void)dealloc{
rawLinePath=nil;
}
SmartPath.m
/*
* Init - set all variables in the correct state
*/
-(id)init
{
if (self = [super init])
{
visible=TRUE;
color = [UIColor redColor].CGColor;
width = SMARTPATH_LINE_WIDTH;
path = CGPathCreateMutable();
lock = [[NSLock alloc]init];
}
return self;
}
/*
* dealloc - clean up after self
*/
-(void)dealloc{
CGPathRelease(path);
}
/*
* copy method to be able to pass a SmartPath to a copy property
*/
-(id)copyWithZone:(NSZone *)zone{
SmartPath *pathCopy = [[SmartPath allocWithZone: zone] init];
pathCopy.visible =visible;
pathCopy.color = color;
pathCopy.width = width;
return pathCopy;
}
I hope any of you knows the answer to this problem.
Best regards
Your problem is your call to -detachNewThreadSelector:toTarget:withObject:. This retains target, which is self, and does not release it until pointMonitor exits. I suspect this never happens, so you've effectively created a retain loop.
You should almost never use -detachNewThreadSelector:toTarget:withObject:. It can create an unbounded number of threads. Instead, you should generally use dispatch queues, NSTimer, NSOperation or other async mechanisms. NSThread objects are generally only appropriate for long-lived producer/consumer threads (and usually those are still handled better with the newer tools like dispatch queues).
I'm not certain what pointMonitor does, but is there any reason it needs its own thread at all? You can do a lot of very good Cocoa development and never fork a thread. Could you use an NSTimer here? Note that most of these techniques retain their target until they fire (just like NSThread). If they didn't, you'd crash when they fired.
Without knowing what you're trying to do, I'm not certain which approach to recommend. You may want to put that together as a new question.
By not starting instance variable names with an underscore, you end up with code where you never know whether you are using an accessor method or an instance variable. As a result, you can never be sure whether a copy is made or not.
If you do that in other places, there's a good chance that a reference to your SmartPath object gets stuck somewhere. And what are you doing creating NSLock objects? Do you need to do anything that #synchronized can't do with much less code?
And if you use a newer Xcode version, get rid of all the instance variables and #synthesize statements. Just declare the properties.
And excuse me, but detaching a thread from an init method is just sick.
Stick with me. I'm visually impaired, have never used this site before, and will probably not post this in precisely the format that you are all used to. I apologize for any unintentional faux pas's herein.
Using Objective-C in an iOS project…
I have a singleton class, set up in what appears to be the usual way for Objective-C. It is, in the main, a series of methods which accept NSString values, interprets them, and return something else. In the code below, I'm simplifying things to the barest minimum, to emphasize the problem I am having.
From the singleton class:
- (NSUInteger) assignControlState:(NSString *)state {
// excerpted for clarity...
return UIControlStateNormal; // an example of what might be returned
}
Now, an instance of another class tries to use this method like so:
- (void) buttonSetup:(UIButton*)button {
[button setTitle:#"something" forState:[[SingletonClass accessToInstance] assignControlState:#"normal"]];
}
This code actually works. HOwever, when the system goes to draw the UI which includes the button whose title was set in this way, an EXC_BAD_ACCESS error occurs.
If the assignControlState method is moved into the same class as the buttonSetup method, no error is generated.
I'm guessing this is something about Apple's memory management that I'm not fully understanding, and how things go in and out of scope, but for the life of me, I can't figure out where I'm going wrong.
HOpe someone can help. Thanks.
The problem is in your accessToInstance method. I'll bet you are under-retaining. The implementation should be more like this:
static SingletonClass *sSingletonClass = nil;
#implementation
+ (id)accessToInstance {
if (sSingletonClass == nil) {
sSingletonClass = [[[self class] alloc] init];
}
return sSingletonClass;
}
#end
Now, if your program is following normal memory management rules, the singleton will stay around. You can check by writing:
- (void)dealloc {
[super dealloc]; // <-- set a breakpoint here.
}
If the debugger ever stops at this breakpoint, you know something in your program has over-released the singleton.
You know that bit you excerpted for clarity? I think you need to show us what it is because there's probably an over release in it somewhere.
Specifically, I think you release an autoreleased object. If you do that and don't use the object again, everything will carry on normally until the autorelease pool gets drained. The autorelease pool gets drained automatically at the end of the event at about the same time as the drawing normally occurs.
That would also explain the delayed crash following the NSLogs.
I am writing a program that displays to a console-like UITextView different events generated by my AudioSession and AudioQueues. For instance, when I detect that my audio route has changed, I just want a quickie message displayed on the screen on my iPhone that this happened. Unfortunately, I believe I am getting into some race condition nastiness, and I'm not sure what the best solution to solve this is.
When I run my program, my debug console spits this out:
bool _WebTryThreadLock(bool), 0x1349a0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
This happens on a line of code:
textView.text = string;
I tried this:
[textView performSelectorOnMainThread:#selector(setText:) withObject:string waitForDone:YES];
And this seemed to have fixed it, but I'm pretty sure I shouldn't be doing something like this to get it to work. Unfortunately, this doesn't work with [UITextView scrollVisibleWithRange:] since this takes an NSRange, which isn't a descendant of NSObject. I think what I am doing is fundamentally wrong.
This code is called from an interruption listener, which runs from the audio queue's thread. Is there something that I should be doing that will make my updates to my textview blocking so I'm not getting this problem?
Thanks.
You are allowed to do anything about the view only from main thread, you did the right thing.
If it requires more parameters or primitive you may need a proxy function.
This is how I make a proxy function
// the caller should be like this
UTMainThreadOperationTextViewScroll *opr = [[UTMainThreadOperationTextViewScroll alloc] init];
opr.textView = textView;
opr.range = NSMakeRange(5, 10);
[UTMainThread performOperationInMainThread:opr];
[opr release];
// the Utility classes goes below
#interface UTMainThreadOperation : NSObject
- (void)executeOperation;
#end
#implementation UTMainThread
+ (void)performOperationInMainThread:(UTMainThreadOperation *)operaion
{
[operaion performSelectorOnMainThread:#selector(executeOperation) withObject:nil waitUntilDone:NO];
}
#end
#implementation UTMainThreadOperationTextViewScroll
#synthesize textView;
#synthesize range;
- (void)dealloc { /* I'm too lazy to post it here */ }
- (void)executeOperation
{
[textView scrollVisibleWithRange:range];
}
#end
PS. some declarations omitted
Given
#interface Canvas:NSView {
NSNumber * currentToolType;
...
}
declared in my .h file
and in the .m file
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
currentToolType=[[NSNumber alloc]initWithInt:1];
}
return self;
}
and further down
-(void)mouseUp:(NSEvent *)event
{
NSLog(#"tool value in event: %d",[currentToolType intValue]);
//rest of code
}
-(NSBezzierPath *)drawPath:(NSRect)aRect
{
NSLog(#"tool value in draw: %d",[currentToolType intValue]);
//rest of drawPath method code that uses the value of currentToolType in a switch statment
}
-(IBAction)selectToolOne:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:0];
}
-(IBAction)selectToolTwo:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:1];
}
The action methods are the only place where currentToolType is changed. But, for some reason, it seems to be a different instance of currentToolType in the mouseUp. I did not write (or synthesize) accessors for the var as it is used only by itself. I noticed that initWithFrame is called twice - I'm assuming it's for the parent window and the NSView?
What am I missing?THANKS!
This is an XCode generated Document based app using COCOA and Obj-C. I'm new at both.
You mention that initWithFrame: is called twice. Your initWithFrame: should only be called once (unless you happen to have two Canvas views).
Is it possible you have the Canvas view in your nib/xib file and are also creating another in code (with alloc/initWithFrame:)?
In which case you have two Canvas objects. You probably have one hooked up to your controls and the other one is in the window (and thus responding to the mouseUp: and it is giving you the same value every time).
If you have the Canvas view setup in IB, you can fix this problem by removing your code that is creating the second one.
You've probably run in to a special case: NSNumber could have cached instances to represent commonly-used numbers.
Two observations, though:
You're wasting a whole lot of memory using NSNumber when you could be simply using NSIntegers or maybe an old-fashioned enumerated type, completely avoiding the object overhead.
You never actually showed your code for when you look at the instances of NSNumber; without it, there's not really enough information here to answer your question.