PDA

View Full Version : insert subview to a UIView




naphatkrit
Jul 28, 2011, 10:35 PM
Hi,
I'm writing an iPad app for my school, and I would like to make a custom view controller that controls the navigation of the user. I made a SwitchViewController class and added it to self.window in the app delegate. Then, in the Interface Builder, I added a view controller. I then added a view with an UIImage as the background. I then added a smaller view (583 x 964). I made an IBOutlet for that UIView, named it mainView. In the SwitchViewController file, I added these lines to viewDidLoad:

Grades_iPhone *tempGrades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];
grades = tempGrades;
[tempGrades release];
[self.mainView insertSubview:grades.view atIndex:0];

When I tried to run it, Xcode gave me "EXC_BAD_ACCESS" on the line [self.mainView insertSubview:grades.view atIndex:0]; . However, when I use one of apple's provided template for navigation (for example, tabBarController), there is no problem. Please help me out!



RonC
Jul 28, 2011, 11:21 PM
(Use CODE tags on your code)

EXC_BAD_ACCESS means something is an invalid pointer (nil, already released, or otherwise bogus).

The code snippet you posted begs a bunch of questions:
a) What is grades and where is it defined?
b) What is mainView and where is it defined?
c) What are the values of self.mainView and grades when the exception is raised?

The last answer is best determined by using the debugger and setting a breakpoint at the line of code that is going to fail.

Shawnpk
Jul 28, 2011, 11:22 PM
EXEC_BAD_ACCESS has to do with memory. It's hard to tell what's going on without seeing more of your code.

naphatkrit
Jul 28, 2011, 11:42 PM
I defined grades in the SwitchViewController header file from the class Grades_iPhone.

my header file has the following codes:

@class Grades_iPhone;

@interface SwitchViewController : UIViewController {
UIView *mainView;
Grades_iPhone *grades;
}
@property (nonatomic, retain) IBOutlet UIView *mainView;
@property (nonatomic, retain) IBOutlet Grades_iPhone *grades;

For some reason, just now when I tried another method, by making another view controller in the Interface Builder and pointing that view controller to the Grades_iPhone class and the appropriate xib file (that way, I don't have to initialize grades), it works now. my current implementation file is like this:

#import "SwitchViewController.h"
#import "Grades_iPhone.h"

- (void)viewDidLoad
{
[self.mainView insertSubview:grades.view atIndex:0];
[super viewDidLoad];
}

I'm not sure what's the difference between this method and my previous one, but it seems to solve the problem. I hope this helps anyone else with similar problem.

jiminaus
Jul 29, 2011, 02:42 AM
Grades_iPhone *tempGrades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];
grades = tempGrades;
[tempGrades release];
[self.mainView insertSubview:grades.view atIndex:0];

I don't know if this is root-cause of your problem, but the line above in red is problematic. It's bypassing the property and assigning directly to the ivar. This means the new Grades_iPhone object is not being retained, the send of release on the next line is deallocating the object, and therefore grades now points to a dead object.

The line should have been:
self.grades = tempGrades;

It's to avoid this very confusion that I either don't declare explicit ivars for my properties, or I name my ivars differently to my properties.

Sykte
Jul 29, 2011, 08:47 PM
I don't know if this is root-cause of your problem, but the line above in red is problematic. It's bypassing the property and assigning directly to the ivar. This means the new Grades_iPhone object is not being retained, the send of release on the next line is deallocating the object, and therefore grades now points to a dead object.

The line should have been:
self.grades = tempGrades;

It's to avoid this very confusion that I either don't declare explicit ivars for my properties, or I name my ivars differently to my properties.

How did you come to the conclusion this was improper?



Grades_iPhone *tempGrades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];
grades = tempGrades;
[tempGrades release];
[self.mainView insertSubview:grades.view atIndex:0];

Let's break this down.

Line 1.
You first create an object called tempGrades, with a retain count of 1.

Line 2.
You assign the pointer of tempGrades to grades, retain count still 1.

Line 3.
You then release tempGrades dropping the retain count to 0.

Line 4.
You then try to add grades as a subView.


What happens when a retain count drops to 0?

jiminaus
Jul 29, 2011, 11:17 PM
How did you come to the conclusion this was improper?


Let's break this down.

Line 1.
You first create an object called tempGrades, with a retain count of 1.

Line 2.
You assign the pointer of tempGrades to grades, retain count still 1.

Line 3.
You then release tempGrades dropping the retain count to 0.

Line 4.
You then try to add grades as a subView.


What happens when a retain count drops to 0?

I'm confused by your post(s). Are you asking a genuine question? Being rhetorical? Perhaps asking the question of the OP?

Sykte
Jul 30, 2011, 09:05 AM
I'm confused by your post(s). Are you asking a genuine question? Being rhetorical? Perhaps asking the question of the OP?


No I was asking you how you came to the conclusion what he did was improper?

I'm confused by your post(s).
I originally had the post separated for this exact reason however a demi god joined them together. Oops guess consecutive posting is against forums rules.

jiminaus
Jul 30, 2011, 10:15 AM
No I was asking you how you came to the conclusion what he did was improper?

Because it is like you said. The NSView is being over-released and a dead NSView object is being added to the view hierarchy. NSView is going to retain its subviews, so worse, not only is a dead object being used, it's also being retained and being resurrected.

What happens when the retain count goes to zero is that the runtime will send a dealloc message to the object. What happens when you use or resurrect a dead object, depends on how teared down the object is by the end of dealloc, and that depends on the class. But anyone coding to use or resurrect a dead object is asking for a massive amount hurt and for spending many hours in the debugger trying to diagnose suitable crashes.

I assumed the OP had intended to set the grades ivar via the retained grades property, because then the code makes sense. But had either forgotten the self. before grades to make that happen, or had mistakenly believed the code was setting via the property.

Sykte
Jul 30, 2011, 11:15 AM
Because it is like you said. The NSView is being over-released and a dead NSView object is being added to the view hierarchy. NSView is going to retain its subviews, so worse, not only is a dead object being used, it's also being retained and being resurrected.

What happens when the retain count goes to zero is that the runtime will send a dealloc message to the object. What happens when you use or resurrect a dead object, depends on how teared down the object is by the end of dealloc, and that depends on the class. But anyone coding to use or resurrect a dead object is asking for a massive amount hurt and for spending many hours in the debugger trying to diagnose suitable crashes.

I assumed the OP had intended to set the grades ivar via the retained grades property, because then the code makes sense. But had either forgotten the self. before grades to make that happen, or had mistakenly believed the code was setting via the property.


Not sure what you mean by resurrected however bypassing the property isn't necessarily improper.


Original code:

Grades_iPhone *tempGrades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];
grades = tempGrades;
[tempGrades release];
[self.mainView insertSubview:grades.view atIndex:0];


The original code can be rewritten as follows.


grades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];

[self.mainView insertSubview:grades.view atIndex:0];


or, which is probaly better for beginners.


self.grades = [[[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil] autorelease];

[self.mainView insertSubview:grades.view atIndex:0];


Are you saying he should write the code this way?


self.grades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];

[self.grades release];

[self.mainView insertSubview:grades.view atIndex:0];



Bypassing a property to access it's own member variable in the proper scope can have it's benefits.

dejo
Jul 30, 2011, 05:52 PM
Bypassing a property to access it's own member variable in the proper scope can have it's benefits.
Such as?

jiminaus
Jul 30, 2011, 06:56 PM
Not sure what you mean by resurrected however bypassing the property isn't necessarily improper.

What I mean by resurrecting is retaining an object after it's retain count has reached zero and dealloc has been called. This results in a zombie object, an object that should be dead but isn't.


The original code can be rewritten as follows.

grades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];
[self.mainView insertSubview:grades.view atIndex:0];


This leaks memory. I'll leave it to you to figure out why.

I thought grades was being added directly, not grades.view. So this is okay, assuming the OP releases grades in dealloc.


or, which is probaly better for beginners.

self.grades = [[[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil] autorelease];
[self.mainView insertSubview:grades.view atIndex:0];


Acceptable but I don't like the use of the property and the use of the ivar within the same method. I find it to be sloppy style.



Are you saying he should write the code this way?
[CODE]
self.grades = [[Grades_iPhone alloc] initWithNibName:@"Grades_iPad" bundle:nil];
[self.grades release];
[self.mainView insertSubview:grades.view atIndex:0];


Absolutely not. Releasing via a retain property like this is horrid!

What I suggest, and continue to suggest, is just to add the initial self. that's required to the original code.



Bypassing a property to access it's own member variable in the proper scope can have it's benefits.

I second Dejo. Please expand on this.

admanimal
Jul 30, 2011, 11:41 PM
Using the ivar directly requires less code to initialize it.

Suppose you have this property:


@property (nonatomic, retain) Grades *grades;


and you want to initialize it. The two most reasonable ways to do it are:


grades = [[Grades alloc] initWithStuff:...];


or


Grades *tempGrades = [[Grades alloc] initWithStuff:...];
self.grades = tempGrades;
[tempGrades release];


I prefer the first way of doing it.

chown33
Jul 31, 2011, 01:33 AM
Using the ivar directly requires less code to initialize it.

Suppose you have this property:

@property (nonatomic, retain) Grades *grades;


and you want to initialize it from some other class. The two most reasonable ways to do it are:


grades = [[Grades alloc] initWithStuff:...];


or


Grades *tempGrades = [[Grades alloc] initWithStuff:...];
self.grades = tempGrades;
[tempGrades release];


I prefer the first way of doing it.
I see a contradiction here.

First, you're not initializing it from another class. The use of self.grades means you're initializing it from the class that defines the ivar, or one of its subclasses, otherwise self won't work in this code. The additional fact of the unadorned ivar name, also means you're not initializing it from another class, but from the class (or subclass) that defines the ivar.

Second, I don't see why you'd be initializing an ivar from another class. If there are ownership concerns (such as use of -retain or -copy) that are addressed by the property's attributes, then the property should be used. That might even be a logical reason for making it a property. If it wasn't intended to be a property, then it would remain just an ivar, and perhaps be restricted in scope with @protected or @private. In any case, the initial value should be the responsibility of the class whose ivar it is, not some external class. And any non-initial assignment of a value by some other class should use the prescribed public API, which is presumably the property.

Third, the code was for a class that defined the ivar and the property, not an external class. I don't see why the rules for self-access would be the same as the rules for other-access. If they were, then encapsulation has no purpose.

admanimal
Jul 31, 2011, 01:59 AM
Sorry, I'm not sure why I said "from some other class." Obviously everything I was doing is supposed to be in the interface/implementation of one class.

Sykte
Aug 1, 2011, 01:18 PM
Sorry Jiminaus, I meant this.
grades = tempGrades;

However quoted this.

self.grades = tempGrades;



Such as?


I should have been more specific instead of making a blanket statement as I did. Let’s say your application is truly constrained by device resources. Within your application you create a million expensive objects and those objects create other objects. The parent objects then set the default state of its child objects during initialization. Something like this..

self.var = [[[myobject alloc] init] autorelease];

will result in millions and millions of release and autorelease messages being sent. So in a tightly controlled environment you can save precious resources by bypassing the properties and doing something like this.


_var = [[myobject alloc] init];


Let me state I would only bypass the property during object creation and only bypass the locally scoped properties. Setting anything beyond local would be nonsense.

jiminaus
Aug 1, 2011, 03:38 PM
I should have been more specific instead of making a blanket statement as I did. Letís say your application is truly constrained by device resources. Within your application you create a million expensive objects and those objects create other objects. The parent objects then set the default state of its child objects during initialization. Something like this..

self.var = [[[myobject alloc] init] autorelease];

will result in millions and millions of release and autorelease messages being sent. So in a tightly controlled environment you can save precious resources by bypassing the properties and doing something like this.


_var = [[myobject alloc] init];


Let me state I would only bypass the property during object creation and only bypass the locally scoped properties. Setting anything beyond local would be nonsense.

I agree, with one proviso (is that the word?). That is, that the program fully works albeit slowly, and that profiling shows this to be the bottleneck. Otherwise, it's case of premature optimisation.