PDA

View Full Version : I want an array of ALL the Objects in a Window




BadWolf13
Dec 5, 2010, 12:28 AM
I've got an app, where I'm trying to get an array of all the objects within a window. I tried subViews, because technically, all objects in a window are views, but unfortunately, that only returns the immediate objects, and some of my buttons and fields are within boxes and other subviews.

Anyone got any ideas on this?



robbieduncan
Dec 5, 2010, 03:24 AM
There is no one-line answer. You need to get all the subviews, then all the subviews of them and so on until there are no more subviews.

jared_kipe
Dec 5, 2010, 01:41 PM
- (void) allSubviews: (NSMutableArray *) storage {
NSArray *subviews = [self subviews];
[storage addObjectsFromArray: subviews];

for (NSView *view in subviews) {
[view allSubviews: storage];
}
}

Make this a category method on NSView, then make a new empty mutable array and pass it into this method.

BadWolf13
Dec 5, 2010, 09:58 PM
Thanks Jared, but I already got it.

I am curious though, can you actually do recursive methods? Seems like the computer would get stuck in an infinite loop if it had to process that code.

lee1210
Dec 5, 2010, 10:02 PM
Thanks Jared, but I already got it.

I am curious though, can you actually do recursive methods? Seems like the computer would get stuck in an infinite loop if it had to process that code.

You need one or more base cases as well as one or more recursive cases. When subviews returns nil you get an implicit base case in this example. Also, this example does not return anything, so you don't see a return nil, etc. for the base case like you might with other recursive functions.

You can definitely write recursive functions that work.

-Lee

BadWolf13
Dec 5, 2010, 10:13 PM
Sorry Lee, but you kinda went right over my head with that one. What is a base case, and would this code work, or not?

lee1210
Dec 5, 2010, 10:23 PM
Sorry Lee, but you kinda went right over my head with that one. What is a base case, and would this code work, or not?

The base case of a recursive function is the case in which the function does not call itself and instead returns to its caller immediately. Generally this happens when you're "done" for whatever value of done is for your recursion. In this specific example the base case is when allSubviews is passed a view with no subviews. The for...in will run 0 times, and allSubviews will return when control reaches the }. This will happen each time the bottom of a view hierarchy is reached and there are no more subviews to explore for a given view. On return the calling instance will call itself again for the next subview in its list or it will also return if it has explored all of its subviews.

I have not run the given code but it's a short function that's logically sound, so as long as there are no cycles (a view being a subview of itself directly or indirectly) it should work correctly.

-Lee

BadWolf13
Dec 5, 2010, 10:33 PM
Ok, I see what you mean. I used a do...while loop to achieve the effect of stopping at the bottom of the hierarchy. I also prefer returning a value as opposed to modifying the argument.

lee1210
Dec 5, 2010, 10:59 PM
The code you posted is not good. There are memory leaks, and I am not sure that it will ever finish. The solution jared_kipe posted is much easier to follow, will terminate excepting the case of a cycle which may be impossible, and doesn't leak.

-Lee

Edit: it looks like it might terminate, but it might not pull in the whole hierarchy, and some views will be duplicated. I like jared_kipe's solution much more, doing this without recursion would require a lot of logic to track what you've already looked at, etc. I would not want to write a non-recursive solution, especially when the recursive solution is a good fit (and when someone already wrote it for me =] ).

whooleytoo
Dec 6, 2010, 11:04 AM
If you're worried about recursion, you could simply add a test to ensure the view you're adding isn't already in the array - if it is, ignore it and move on.

That said, I don't see how the subview hierarchy could be recursive, but people are always doing weird things, so it's good to programme defensively. :)

gnasher729
Dec 6, 2010, 12:35 PM
If you're worried about recursion, you could simply add a test to ensure the view you're adding isn't already in the array - if it is, ignore it and move on.

That said, I don't see how the subview hierarchy could be recursive, but people are always doing weird things, so it's good to programme defensively. :)

If you had loops in your view hierarchy, lots of things would crash immediately, like the first responder chain, the whole view drawing code would crash, nothing would work at all. Anything going up the chain of superviews would crash. And a view can only have one superview, so it cannot be subview of two different views either.

lee1210
Dec 6, 2010, 12:49 PM
If you had loops in your view hierarchy, lots of things would crash immediately, like the first responder chain, the whole view drawing code would crash, nothing would work at all. Anything going up the chain of superviews would crash. And a view can only have one superview, so it cannot be subview of two different views either.

Good to know. I wasn't 100% sure this was the case, so I said "excepting the case of a cycle which may be impossible". I was pretty confident that jared_kipe's code was fine, but a cycle would lead to infinite recursion, so I thought i'd mention it to be thorough.

-Lee

whooleytoo
Dec 6, 2010, 04:04 PM
If you had loops in your view hierarchy, lots of things would crash immediately, like the first responder chain, the whole view drawing code would crash, nothing would work at all. Anything going up the chain of superviews would crash. And a view can only have one superview, so it cannot be subview of two different views either.

That sounds perfectly logical. :)

I'd still add checks to recursive code, even if it's only out of uber-cautiousness. I'm always wary when doing any iterative/recursive code working through 'external' data over which you have little or no control ("how many subviews in a view", "how many files on a drive", "how long is a piece of string.." ;) ) .

BadWolf13
Dec 6, 2010, 05:22 PM
The code you posted is not good. There are memory leaks, and I am not sure that it will ever finish. The solution jared_kipe posted is much easier to follow, will terminate excepting the case of a cycle which may be impossible, and doesn't leak.

-Lee

Edit: it looks like it might terminate, but it might not pull in the whole hierarchy, and some views will be duplicated. I like jared_kipe's solution much more, doing this without recursion would require a lot of logic to track what you've already looked at, etc. I would not want to write a non-recursive solution, especially when the recursive solution is a good fit (and when someone already wrote it for me =] ).

The code actually worked well, and terminated after only 3 loops, but when I looked at it more closely, I realized that it only worked because of the order of the object in my test window, and could fail to capture all the objects in certain situations. Jared's recursive idea may work fine, but I still prefer something that returns a value, as opposed to modifying an argument. No offense, Jared.

lee1210
Dec 6, 2010, 07:21 PM
The code actually worked well, and terminated after only 3 loops, but when I looked at it more closely, I realized that it only worked because of the order of the object in my test window, and could fail to capture all the objects in certain situations. Jared's recursive idea may work fine, but I still prefer something that returns a value, as opposed to modifying an argument. No offense, Jared.

Something leaking memory and failing to capture the values desired while duplicating others is not "working well" in my book =).

Anyhow, Jared_kipe's solution could be modified in-place to return an NS(Mutable)Array or a very small wrapper could be written that allocates an NSMutableArray, pass it in, then autoreleases if the name dictates it should, and returns the array.

-Lee

BadWolf13
Dec 6, 2010, 07:58 PM
I'm curious how it was leaking memory. I admittedly don't know much about memory and how to spot memory leakage.

By saying "working well," it did capture all the objects in the window, without duplicating any. The reason I changed it, as stated above, is that I realized it was only working due to the specific setup of the test window. In case you're curious, here's the working version I ended up with. If you can spot any memory leaks, please point them out, as I'm still unclear with a lot of this memory stuff.

-(NSArray *)allSubviews{
// This method returns an array that contains all of the subviews in the window
NSMutableArray *fields = [NSMutableArray arrayWithArray:[[[self window] contentView] subviews]];
NSMutableArray *views = [NSMutableArray arrayWithArray:[[[self window] contentView] subviews]];
NSMutableArray *newViews = [[NSMutableArray alloc] init];

do {
[newViews removeAllObjects]; // clears the array to start the new cycle
for (NSView *view in views){
NSLog(@"Object:%@", [view description]);
[newViews addObjectsFromArray:[view subviews]];
}
[fields addObjectsFromArray:newViews];
[views removeAllObjects]; // clears the views before replacing them with the new views
[views addObjectsFromArray:newViews];
} while ([newViews count] > 0);

return fields;
}

jared_kipe
Dec 6, 2010, 09:32 PM
- (NSArray *) allSubviews{
static NSMutableArray *storage = nil;
BOOL isFirstCall = NO;

if (!storage) {
isFirstCall = YES;
storage = [[NSMutableArray alloc] init];
}

NSArray *subviews = [self subviews];
[storage addObjectsFromArray: subviews];

for (NSView *view in subviews) {
[view allSubviews];
}

if (!isFirstCall) {
return nil;
}

NSMutableArray *arrayToReturn = storage;
storage = nil;
return [arrayToReturn autorelease];
}


Sheesh people can be picky. I checked this thread a couple times with no response then there are all kinds of debate here.

As with the first one, this should be a category on NSView. I didn't type this in a compiler, just in the little MR reply box, so YMMV but I was pretty careful. You MIGHT want to tear down storage and make a strict NSArray instead of returning the NSMutableArray if you think stylistically it matters.

BadWolf13
Dec 6, 2010, 10:04 PM
It's not a matter of being picky, it's a matter of being stubborn. I can't stand someone else doing the work for me, so even after you posted your solution, I still had to come up with my own.

jared_kipe
Dec 6, 2010, 10:07 PM
It's not a matter of being picky, it's a matter of being stubborn. I can't stand someone else doing the work for me, so even after you posted your solution, I still had to come up with my own.

Well then I hope you LOVED the new one ;) ;)

lee1210
Dec 6, 2010, 10:48 PM
I'm curious how it was leaking memory. I admittedly don't know much about memory and how to spot memory leakage.

By saying "working well," it did capture all the objects in the window, without duplicating any. The reason I changed it, as stated above, is that I realized it was only working due to the specific setup of the test window. In case you're curious, here's the working version I ended up with. If you can spot any memory leaks, please point them out, as I'm still unclear with a lot of this memory stuff.

-(NSArray *)allSubviews{
// This method returns an array that contains all of the subviews in the window
NSMutableArray *fields = [NSMutableArray arrayWithArray:[[[self window] contentView] subviews]];
NSMutableArray *views = [NSMutableArray arrayWithArray:[[[self window] contentView] subviews]];
NSMutableArray *newViews = [[NSMutableArray alloc] init];

do {
[newViews removeAllObjects]; // clears the array to start the new cycle
for (NSView *view in views){
NSLog(@"Object:%@", [view description]);
[newViews addObjectsFromArray:[view subviews]];
}
[fields addObjectsFromArray:newViews];
[views removeAllObjects]; // clears the views before replacing them with the new views
[views addObjectsFromArray:newViews];
} while ([newViews count] > 0);

return fields;
}

This does look a lot better than the last one. There were no duplicates and all of the items were captured with the simple layout I looked at that didn't work with your original code. There is still a memory leak, but it's worlds better than the first go round. You alloc/init and assign to newViews, so you own it. You never release it, so there's a leak. Otherwise a lot of the shenanigans with assigning over pointers to things you owned, copy, etc. are cleaned up.

Now that you've come up with a more involved solution that bit you on the first try, it might behoove you to learn more about recursion. Then the next time a problem comes along that is very well suited to it you can write your own recursive solution with confidence that it will work and that the computer won't get caught in an infinite loop.

-Lee

BadWolf13
Dec 7, 2010, 12:28 PM
Thanks. As I understand, I don't need to bother with releasing because the garbage collector does that for me. Also, when I declare and initialize within a method, isnt't the scope limited to within that method?

whooleytoo
Dec 7, 2010, 12:53 PM
Thanks. As I understand, I don't need to bother with releasing because the garbage collector does that for me. Also, when I declare and initialize within a method, isnt't the scope limited to within that method?

You're right, you don't need to release if you have garbage collection on. There are times when that's not an option (iOS development, anywhere you're allocating memory in a tight loop and you need more 'manual' memory management), so it's useful to understand Cocoa memory management, but you're right.

As for the scope - that applies to basic types (Boolean, int, char, struct) which are allocated on the stack. These are freed when the method returns; no need to worry about them.

If you're dealing with pointers which you alloc (or malloc) memory for, they are allocated on the heap, and are not freed when the method returns. If garbage collection is off and you don't free them, they leak.

lee1210
Dec 7, 2010, 01:14 PM
This is only my opinion, but GC seems like a dangerous trap, especially when Apple put together an elegant memory management mechanism with retain/release/autorelease. The concept of ownership takes a pretty small amount of time to figure out, and once you get it it's pretty easy to apply. With GC you give up a lot of control, and "stop caring" about memory management. The GC is likely pretty good in Objective-C 2.0, but with the compatibility limitations (i guess 10.4 support is going to be dwindling soon, anyway, but iOS is a huge deal) it seems best to just "do it right" without GC so you can easily move your code to iOS.

Even with GC having your app bloat up unnecessarily just to have the GC clean up after you seems undesirable to me.

In any event, with GC you don't have to retain/release/autorelease so that code should work. Just a word of warning, people will point out what they perceive to be leaks in your code because they won't know if you're using GC and once you are familiar with memory management when you see it being done "wrong" it sticks out like a sore thumb.

Since you don't have to worry about memory management right now because of GC that means you've got the extra time to learn recursion now =).

-Lee

chown33
Dec 7, 2010, 01:51 PM
Just a word of warning, people will point out what they perceive to be leaks in your code because they won't know if you're using GC and once you are familiar with memory management when you see it being done "wrong" it sticks out like a sore thumb.

Solution: if you're using GC, always say so in the first post.

jared_kipe
Dec 7, 2010, 01:59 PM
As for the scope - that applies to basic types (Boolean, int, char, struct) which are allocated on the stack. These are freed when the method returns; no need to worry about them.

If you're dealing with pointers which you alloc (or malloc) memory for, they are allocated on the heap, and are not freed when the method returns. If garbage collection is off and you don't free them, they leak.

Obviously you are correct but I want to address this a little more because I believe the question is mostly related to my new method of doing this.

First thing to remember, all objects in Obj-C are allocated on the heap meaning if you make it and its retain count is greater than zero it will still be in the same place with the same data inside it for the life of your application.

Second thing to know is that the keyword static makes a variable into what is basically an instance variable for that single method*. Every time the method is called it has access to those static declared pieces of memory.

So the way the new method works is EXTREMELY similar to the way the old method worked. The only major change is that the very first call to the method CREATES the storage the method will use to store all the subviews.

It creates it and stores the pointer to that storage inside its own little static space.

Then essentially all that needs to happen is that all subsequent calls store all their subviews int that storage as before, and then they all return, the very first call returns the storage.

The key important ideas to keep in mind is how does each subsequent call to allSubviews know not to make the storage again, and not to return anything.**

1: Checking if storage is nil, makes it possible to make the storage if it isn't there, and use the storage if it is.

2: Each function call will get its own BOOL isFirstCall with the initial value set to NO. IF storage is nil, then it gets toggled to YES, but ONLY for the funcion call where storage is nil. For all the others it stays NO.

3: After adding all the subviews and recursively calling allSubviews on those subviews, the method checks that isFirstCall and simply returns nil right then if it is NO. This stops that particular function from continuing at that moment.

3: After doing the exact same thing, and all of them returned, the initially called allSubviews basically wants to return the storage and then set storage to nil to prime the next call of the allSubviews method. This is not possible in this order so it makes a stack copy of that pointer, sets the static pointer storage to nil, and returns the stack copy instead.

*works for C functions too
** they could return something as long as they don't set the storage pointer back to nil

chown33
Dec 7, 2010, 02:15 PM
Second thing to know is that the keyword static makes a variable into what is basically an instance variable for that single method.

I disagree with calling this an "instance variable". It implies this is stored in an object, which it definitely isn't. It's stored in a single static variable that is shared by all callers of this function, across all threads. It's exactly the same as this:

static NSMutableArray *storage = nil;
- (NSArray *) allSubviews{
BOOL isFirstCall = NO;
...
except that when it's inside the method body's block, its name is only visible to that block. Outside the block, the name is inaccessible to all other code.

In the above code, the name is visible outside the block (i.e. to other blocks or initializers in the same compilation unit), but the lifetime of the variable is absolutely identical to the "static inside the block" declaration. Even the initializer to nil is identical: it's done exactly once, conceptually when the program is loaded, before main() is called. It's not done each time the method/function is entered.

At no time is 'storage' ever an instance variable. If anything, it's a shared singleton.

One consequence of static storage is in concurrency: this code is not thread-safe. Re-entrancy is another concern, although here the function/method is re-entrant (it calls itself), and the singularity of the static storage is being relied upon by the re-entrant calls. In short, the variable has to be static because that's the only way multiple calls can use the same variable without passing it in.

To eliminate the static storage entirely would make this thread-safe. I'd probably do it by making two methods, or a method and a static function. One method (or the static function) is the recursive one with void return, and modifies its arg-array. The other method is the public one identical in name and args to allSubviews.

BadWolf13
Dec 7, 2010, 04:39 PM
Obviously you are correct but I want to address this a little more because I believe the question is mostly related to my new method of doing this.

Second thing to know is that the keyword static makes a variable into what is basically an instance variable for that single method*. Every time the method is called it has access to those static declared pieces of memory.


Ironically, my question was directed in general, and not to your method, however this post was very educational. Let me just say it back to you, to see if I get it right. When you did this;

static NSMutableArray *storage = nil;

That same instance is going to be used by the instances of the method that are called from within that method?

lee1210
Dec 7, 2010, 04:52 PM
Ironically, my question was directed in general, and not to your method, however this post was very educational. Let me just say it back to you, to see if I get it right. When you did this;

static NSMutableArray *storage = nil;

That same instance is going to be used by the instances of the method that are called from within that method?

Not quite. There is exactly one storage that is available within allSubviews whenever it is called, not just in recursive cases. This is, essentially, making a global variable called storage as chown33 said. It just so happens that you can only get to it from allSubviews. The memory for storage (the actual pointer primitive, not what it points to) is on the heap and lives throughout the program. This is distinct from a "regular" (automatic) variable that lives on the stack and "goes away" when the stack frame is popped at the end of the function's execution.

-Lee

BadWolf13
Dec 7, 2010, 05:18 PM
Ok, so a quick rundown of everything I've learned in this thread.

1. Recursive methods are possible, as long as you prevent an infinite loop.

2. "Static" declarations within a method create an instance variable that will be used by every instance of that method called within the lifetime of the application.

3. Non-"static" variables declared within a method will not automatically be released upon the end of said method.

Correct?

lee1210
Dec 7, 2010, 05:30 PM
Ok, so a quick rundown of everything I've learned in this thread.

1. Recursive methods are possible, as long as you prevent an infinite loop.

2. "Static" declarations within a method create an instance variable that will be used by every instance of that method called within the lifetime of the application.

3. Non-"static" variables declared within a method will not automatically be released upon the end of said method.

Correct?

Some terminology may have gotten mixed up.
1. is correct.
2. Don't say instance variable. jared_kipe was trying to be helpful and relate this to something you may already know, but it's probably better not to mix nomenclature as chown33 pointed out. The rest of what you said is correct. Instead of saying "instance of that method" it might be clearer to think of "every time the method is called".
3. I'm not sure what you mean, but i don't think this is right. Objects live on the heap, they are not tied to any particular method. You might have a pointer to an object that is local to a method, but that is not the object. Primitives (char, int, float, double, any pointer, etc.) declared within a method that have the "automatic" storage class (that is the default if you don't specify) will be allocated on the stack when the method is called. When the method completes, the stack frame is "popped" so the space for these variables is no longer being used. If you're talking about Objects in a non-GC environment, the only time release will happen is if you send the message yourself when you own it, or if you have an autoreleased object and the autorelease pool is drained.

-Lee

BadWolf13
Dec 7, 2010, 05:46 PM
Ok, let me try again on the last one then. The pointer to the object, if declared within a method, will stop existing after the method ends. For example;


-(void)foo{
NSString *test = [[NSString alloc] initWithFormat:@"This is a test"];
NSLog (@"%@", test);
}


Would the pointer *test cease to exist at the end of this method, leaving the object with a 0 retain count, or would it still be necessary to put [test release] at the end of this code?

jared_kipe
Dec 7, 2010, 05:56 PM
I disagree with calling this an "instance variable". It implies this is stored in an object, which it definitely isn't. It's stored in a single static variable that is shared by all callers of this function, across all threads. It's exactly the same as this:

static NSMutableArray *storage = nil;
- (NSArray *) allSubviews{
BOOL isFirstCall = NO;
...
except that when it's inside the method body's block, its name is only visible to that block. Outside the block, the name is inaccessible to all other code.


I disagree with calling this "exactly the same as this: <code>...</code>", since it isn't the same at all! You're splitting hairs, I'm splitting hairs.

I said "basically an instance variable for that single method." I didn't say "exactly an instance variable" or "is an instance variable".

I was merely relating one concept the OP is probably familiar with another.


...
One consequence of static storage is in concurrency: this code is not thread-safe. Re-entrancy is another concern, although here the function/method is re-entrant (it calls itself), and the singularity of the static storage is being relied upon by the re-entrant calls. In short, the variable has to be static because that's the only way multiple calls can use the same variable without passing it in.

To eliminate the static storage entirely would make this thread-safe. I'd probably do it by making two methods, or a method and a static function. One method (or the static function) is the recursive one with void return, and modifies its arg-array. The other method is the public one identical in name and args to allSubviews.


We could argue over thread safety here.

Firstly, it wasn't a condition of the OP's question, nor did I ever promise it.

Secondly, its only a minor thread "safety" issue. Worst case scenario, subviews that aren't actually subviews get returned if two threads try to use the method at the same time (or duplicate views). It can't create an infinite loop because at no time did I actually look at what is inside the NSMutableArray. And since it is only ever an NSMutableArray OR nil, it is safe to message it if it is in the wrong state. So what would probably happen is one thread would get more than it bargained for (namely some weird subviews) and the other thread would get nil back from its initial calling. (this of course relies on NSMutableArray being thread safe which I hope it is!)

I should point out that actual ObjC instance variables are not inherently thread safe either!


If thread safety is an issue, then I'd suggest two category methods as such:


- (NSArray *) allSubviews{
NSMutableArray *storage = [[NSMutableArray alloc] init];
[self allSubviewsIntoMutableArray: storage];
return [storage autorelease];
}

- (void) allSubviewsIntoMutableArray: (NSMutableArray *)storage {
NSArray *subviews = [self subviews];
[storage addObjectsFromArray: subviews];

for (NSView *view in subviews) {
[view allSubviews: storage];
}
}


I probably would have done it this way in the first place (in actual practice), but decided to challenge myself a little and write it all in one method without needing storage passed into it as per the OP's request/the way the thread was going.



Also I would appreciate it, chown33, if you would never reply to my threads or posts. LOL JK, I hope you get the hilarious joke I make ;)

jared_kipe
Dec 7, 2010, 06:00 PM
2. "Static" declarations within a method create an instance variable that will be used by every instance of that method called within the lifetime of the application.
...

Jesus, I owe you a cookie chown.

static variables in a method or function are kinda like class variables if you think of the function itself as the class.. I should stop...

jared_kipe
Dec 7, 2010, 06:06 PM
Ok, let me try again on the last one then. The pointer to the object, if declared within a method, will stop existing after the method ends. For example;


-(void)foo{
NSString *test = [[NSString alloc] initWithFormat:@"This is a test"];
NSLog (@"%@", test);
}


Would the pointer *test cease to exist at the end of this method, leaving the object with a 0 retain count, or would it still be necessary to put [test release] at the end of this code?
the pointer to that string will cease to exist. The object itself will still be in the heap with a retain count of 1, the object doesn't know that the pointer went away.

Now technically, in a garbage collected environment it wouldn't go away right away but it would eventually. Keep in mind that if you have something... like
this and you want to indicate to the collector that you don't need it anymore you can say test = nil; this is due to the details of how garbage collection works internally (it still won't go away immediately).

jared_kipe
Dec 7, 2010, 06:35 PM
Also, in GCC it should be possible to fix the original function by changing the declaration of static NSMutableArray *storage = nil; to static __thread NSMutableArray *storage = nil;

Making it a thread specifically allocated static local variable.

chown33
Dec 7, 2010, 06:43 PM
I disagree with calling this "exactly the same as this: <code>...</code>", since it isn't the same at all! You're splitting hairs, I'm splitting hairs.

Except my example really is exactly the same except for the scope of the name. If you look at the generated code, it should be exactly the same except the variable's name is visible to the rest of the compilation unit. A static declared inside a function, however, has a unique name automatically generated for it, and that name is used exclusively inside the function. If the same static name is reused inside another function, then another unique name is generated. Other than that, there should be no differences in the code or the alloted static storage.

I said "basically an instance variable for that single method." I didn't say "exactly an instance variable" or "is an instance variable".

I was merely relating one concept the OP is probably familiar with another.

Unfortunately, there are limits to the power of analogy, and "instance variable" has a pretty specific meaning. And I think it was a poor analogy in the first place. It's like the radio in a car... No, it's like the ears on a horse... No, it's like the wings on a flying fish... No, the radio-controlled ears on a flying horse... with static.


We could argue over thread safety here.

We could, but I won't. It wasn't an original requirement.


Also I would appreciate it, chown33, if you would never reply to my threads or posts. LOL JK, I hope you get the hilarious joke I make ;)

And I would appreciate it, jared_kipe, if I could refrain from having to respond to any of your threads or posts. I find that doing so often makes things go off-track. (I could put a smiley there, but this week's shipment is overdue and I'm over my quota elsewhere.)

jared_kipe
Dec 7, 2010, 07:33 PM
.. my example really is exactly the same except for the scope of the name. ...

It isn't exact if you have to say except.

lee1210
Dec 7, 2010, 07:56 PM
jared_kipe covered it earlier, but I thought I'd try to hammer it home:
With retain/release a pointer going out of scope has nothing to do with your ownership of an object. If you own an object, and you no longer have a pointer to it, you can no longer relinquish ownership, so the object will live forever and you have leaked the memory associated with the object. No magic happens IRT an object when a variable that points to it changes. The only time you claim ownership or relinquish ownership is by sending retain/release/autorelease messages.

Don't think about retain count or what changes it. Think about ownership. If you are done with an object you own but fail to relinquish ownership, that's bad. If you already own something and claim ownership again, bad. Relinquish ownership of something you don't own, bad. Use something you don't own and hope it works, bad.

-Lee

BadWolf13
Dec 7, 2010, 10:35 PM
Ok, I never really thought of it as ownership. None of the educational material I've read so far covers it in terms of ownership. Do you know if there's any Apple reference docs that explain it that way?

lee1210
Dec 7, 2010, 10:46 PM
Ok, I never really thought of it as ownership. None of the educational material I've read so far covers it in terms of ownership. Do you know if there's any Apple reference docs that explain it that way?

That's too bad. The Memory Management Programming Guide:
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
explains it that way, and is the resource for this topic. The second section is Object Ownership and Disposal:
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-BEHDEDDB

Gaming retain counts is no way to live. Has the material you've read so far dealt with memory management or does it all assume GC? What are you reading?

-Lee

jared_kipe
Dec 7, 2010, 10:50 PM
Ok, I never really thought of it as ownership. None of the educational material I've read so far covers it in terms of ownership. Do you know if there's any Apple reference docs that explain it that way?

It is sometimes described as a dog and leash metaphor.

The dog will only stay around if there is at least one leash on it. When you buy the dog you automatically leash up to it. You can have friends come along and walk with you and leash to the dog. And those friends -and you- can leave the dog alone at any point by unleashing yourself from the dog.

Where there are no more leashes the dog will run away. There can be any number of leashes on a dog at a time, but there needs to be at least one to keep the dog there.

The number of leashes is the retain count, leashing and unleashing is retaining and releasing.

lee1210
Dec 7, 2010, 10:56 PM
It is sometimes described as a dog and leash metaphor.

The dog will only stay around if there is at least one leash on it. When you buy the dog you automatically leash up to it. You can have friends come along and walk with you and leash to the dog. And those friends -and you- can leave the dog alone at any point by unleashing yourself from the dog.

Where there are no more leashes the dog will run away. There can be any number of leashes on a dog at a time, but there needs to be at least one to keep the dog there.

The number of leashes is the retain count, leashing and unleashing is retaining and releasing.

So autorelease is attaching a leash to a bench that will dissolve at an unknown time in the future?

And if you forget to attach a leash but know where you left the dog, you might find a rhino or pocketwatch when you come back?

How does your backyard fit into this metaphor? Does that count as a virtual leash?

-Lee

chown33
Dec 7, 2010, 11:08 PM
And if you forget to attach a leash but know where you left the dog, you might find a rhino or pocketwatch when you come back?

Metaphors FTW! And I thought I'd had too many beers when I wrote flying fish and radio-controlled ears.

jared_kipe
Dec 8, 2010, 11:18 AM
So autorelease is attaching a leash to a bench that will dissolve at an unknown time in the future?

And if you forget to attach a leash but know where you left the dog, you might find a rhino or pocketwatch when you come back?

How does your backyard fit into this metaphor? Does that count as a virtual leash?

-Lee

I was thinking, autorelease was like dropping your leash so that someone else could pick it up for you. If enough time goes by the dog will figure out that the leash isn't actually holding him there and run away.

If you unleash from the dog but stay in sight of it, the dog will probably disappear and be replaced by some other dog. The new dog will probably not respond to the old dog's name or know the old dog's tricks.

...
On second thought, maybe autorelease is like putting your dog into a cage and walking away. Someone else could come along and leash up the dog, but if nobody comes around soon enough, the dog will die .. I mean run away to your uncles farm upstate.

Sydde
Dec 8, 2010, 11:34 AM
On second thought, maybe autorelease is like putting your dog into a cage and walking away. Someone else could come along and leash up the dog, but if nobody comes around soon enough, the dog will die .. I mean run away to your uncles farm upstate.

Like taking the dog to the pound: they will keep the dog there for a few days, and you can check on it during that time, but eventually it will no longer be there.

Except dogs are kind of a flawed metaphor: chocolate (as in "Cocoa") is toxic to dogs.

BadWolf13
Dec 8, 2010, 12:23 PM
That's too bad. The Memory Management Programming Guide:
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
explains it that way, and is the resource for this topic. The second section is Object Ownership and Disposal:
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-BEHDEDDB

Gaming retain counts is no way to live. Has the material you've read so far dealt with memory management or does it all assume GC? What are you reading?

-Lee

I started with Hillegaas' book. It shows code with the retain and release commands, but only has a very short chapter talking about memory, and as I recall, I don't remember "ownership" coming up in it.

lee1210
Dec 8, 2010, 01:35 PM
I started with Hillegaas' book. It shows code with the retain and release commands, but only has a very short chapter talking about memory, and as I recall, I don't remember "ownership" coming up in it.

That's pretty unfortunate to hear, especially considering that book is recommended so often. In any event, the Memory Management Programming Guide should be all you need if you choose to learn retain/release memory management. If you're more interested in exploring other things right now and want to stick to GC for the time being, that's understandable. I would recommend doubling back at some point to learn proper memory management in case you ever need it.

-Lee

BadWolf13
Dec 21, 2010, 03:01 PM
Hey guys, just in case you're interested, I just realized that we all missed a biggie on this one. Apparently, an NSTabView only considers the view in the currently selected tab as a subView. I had to add an if to grab the views from all the tabs in a tabView.

jared_kipe
Dec 21, 2010, 05:09 PM
Hey guys, just in case you're interested, I just realized that we all missed a biggie on this one. Apparently, an NSTabView only considers the view in the currently selected tab as a subView. I had to add an if to grab the views from all the tabs in a tabView.

I didn't miss anything.

If only the current selected tab is a subView than only that tab is a subView and should be returned. Plain and simple. One of the reasons for this is to utilize lazy loading.