Problems with subclassing NSString - objective-c

I have spent the last day hunting down a dynamic storage issue and at the end of the trail I have no idea what is going on other than I must have misunderstood/missed something about subclassing NSString. Here is a much cut down and much instrumented sample that has the problem:
IDStringBug.h contains:
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
/*==================================*/
#interface IDStringBug:NSString {
NSString *_backingStore;
NSArray *path;
}
- (NSArray*) path;
- (void) dealloc;
- (NSUInteger) length;
-(id) initWithString: (NSString*) string;
-(unichar) characterAtIndex:(NSUInteger) index;
#end
IDStringBug.m contains:
#include <stdio.h>
#import "IDStringBug.h"
#implementation IDStringBug
- (NSArray*) path {
printf ("Return ptr to IDString: %s\n", [_backingStore cString]);
return path;}
- (void) dealloc {
printf ("Release IDString: %s\n", [_backingStore cString]);
printf ("Path count is %d\n", (int) [path retainCount]);
[_backingStore release];
printf ("Apres _backinstore\n");
printf ("Path count is %d\n", (int) [path retainCount]);
[path release];
printf ("After path release, done but for super\n");
[super dealloc];
}
-(id)initWithString:(NSString*)string {
if ((self = [self init])) {
_backingStore = [[NSString stringWithString:string] copy];
}
path = [_backingStore componentsSeparatedByString: #"."];
printf ("Path count is %d\n", (int) [path retainCount]);
return self;
}
-(NSUInteger) length {
return [_backingStore length];
}
-(unichar)characterAtIndex:(NSUInteger)index {
return [_backingStore characterAtIndex:index];
}
#end
bug.m contains:
#include <stdio.h>
#include <Foundation/NSAutoreleasePool.h>
#import "IDStringBug.h"
int main(int argc, char* argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
IDStringBug *myids = [IDStringBug stringWithString: #"a.b.c"];
printf ("Path count is %d\n", (int) [[myids path] retainCount]);
printf ("pool=%d\n", (int) [pool autoreleaseCount]);
[pool release];
}
The output is:
$ ./bug
Path count is 1
Return ptr to IDString: a.b.c
Path count is 1
pool=7
Release IDString: a.b.c
Segmentation fault (core dumped)

This answer doesn't directly address your problem, but it will indirectly fix it and lead to a much more maintainable design pattern.
Don't subclass NSString.
Instead, use composition.
#interface PathString:NSObject
#property(copy) NSString *stringValue;
#property(strong) NSArray *pathValue;
... etc ...
#end
The actual crash is this:
path = [_backingStore componentsSeparatedByString: #"."];
That method returns an autoreleased object and it is deallocated when the pool is drained, leaving a dangling reference.
As others have mentioned, retainCount is utterly useless.
Note that this is odd:
_backingStore = [[NSString stringWithString:string] copy];
That should simply be:
_backingStore = [string copy];
Your code is technically copying the string twice. I say technically, because -- due to an implementation detail -- _backingStore will end up pointing to string (assuming string is an NSString and not an NSMutableString.
I was very much an insider back in the NeXT days, but have been mostly
away or only used Objc base.
Aha! So was I, having started ObjC programming in 1989.
That'd explain where you are coming from a bit!
Instead of retainCount, an issue like this is quite easy to debug using zombies. You can turn it on in the options pane of the scheme in Xcode.
The "whentouseretaincount.com" site links to an article I wrote about retain count. You might find it interesting in that it also illuminates some details of memory management, in general.
Apple document to the Linux GnuStep world
That is also critical to note in your questions. GNUStep is mostly just like Apple stuff, but is a little closer to the OpenStep world. I can't remember if GNUStep has zombie detection, but I'd suspect it would. Linux also has other memory debugging tools that are quite powerful.
retainCount is still going to be rife with fragility, but it is somewhat more stable when dealing with a single threaded, command line, tool. You'll still need to watch out for autoreleased stuff, though.

The call to componentsSeparatedByString: returns an NSArray that has been autoreleased. That means it will have a retain count of 1, but that count will be decremented as soon as the autorelease pool is drained. Combine that with the release call in the IDStringBug dealloc, and you'll see that the array is being released one time too many.
In fact by the time the IDStringBug dealloc is called, the path array has already been deallocated. So when you try to determine the retain count (with the call to [path retainCount] you are attempting to access an object that no longer exists.

Related

Clarifications needed for a crash using NSArray, blocks and Manual Reference Counting

I need some clarifications on a crash I'm encountering using NSArray, blocks and Manual Reference Counting. My goal is to store blocks on a collection (NSArray in this case) in order to reuse them in the future.
I've setup a small sample to replicate the issue. In particular, I have a class Item that looks like the following:
#import <Foundation/Foundation.h>
typedef void(^MyBlock)();
#interface Item : NSObject
- (instancetype)initWithBlocks:(NSArray*)blocks;
#end
#import "Item.h"
#interface Item ()
#property (nonatomic, strong) NSArray *blocks;
#end
#implementation Item
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
NSMutableArray *temp = [NSMutableArray array];
for (MyBlock block in blocks) {
[temp addObject:[[block copy] autorelease]];
}
_blocks = [temp copy];
}
return self;
}
The usage is described below (I'm using in the app delegate).
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = ^() {
[weakSelf doSomething1];
};
MyBlock myBlock2 = ^() {
[weakSelf doSomething1];
};
NSArray *blocks = #[myBlock1, myBlock2];
// As MartinR suggested the code crashes even
// if the following line is commented
Item *item = [[Item alloc] initWithBlocks:blocks];
}
If I run the app, it crashes with an EXC_BAD_INSTRUCTION (note that I've already enabled All Exceptions breakpoints). In particular, the app stops in the main.
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
Note: As suggested by Ken Thomases, if you use bt command on llvm console, you are to see the back trace. In this case it shows the following:
-[__NSArrayI dealloc]
If I comment the [weakSelf doSomethingX]; it works without crashes (it does not mean that is correct).
Modifying the code a little bit like the following, all runs ok.
// Item does not do anymore the copy/autorelease dance
// since used in the declaration of the blocks
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
_blocks = [blocks retain];
}
return self;
}
and
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
MyBlock myBlock2 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
NSArray *blocks = #[myBlock1, myBlock2];
Item *item = [[Item alloc] initWithBlocks:blocks];
What is the point here? I think I'm missing something but I don't know what.
Update 1
Ok. I'll try to recap my thoughts based on the comments with #Martin R and #Ken Thomases.
A block, by default, is created on stack if a copy message is not sent to it (ARC does this for us) in order to move it on the heap. So, the situation in this case is the following. I create an autorelease array and I add two blocks where retain is called in a implicit manner. When the applicationDidFinishLaunching method finishes is execution, the blocks, since created on the stack (they are automatic variables) disappear. In a later moment, the array called blocks will be released since has been marked as autorelease. So, it will crash since it will send a release object to blocks that do not exist anymore.
So, my question is the following: What does it mean to send a retain message to a block that is on the stack? Why the array is the source of the crash (see the back trace)? In other words, since a block is on the stack, will it bump the retain count of it? And when it goes out of scope? In addiction, why if I comment the [weakSelf doSomething1] line the code works without problems? Not very clear to me this part.
You are sticking an object from the stack into an autoreleased array. BOOM ensues.
Consider:
typedef void(^MyBlock)();
int main(int argc, char *argv[]) {
#autoreleasepool {
NSObject *o = [NSObject new];
MyBlock myBlock1 = ^() {
[o doSomething1];
};
NSLog(#"o %p", o);
NSLog(#"b %p", myBlock1);
NSLog(#"b retain %p", [myBlock1 retain]);
NSLog(#"b copy %p", [myBlock1 copy]);
NSLog(#"s %p", ^{});
sleep(1000000);
}
}
Compiled/run as -i386 (because the #s are smaller and more obvious):
a.out[11729:555819] o 0x7b6510f0
a.out[11729:555819] b 0xbff2dc30
a.out[11729:555819] b retain 0xbff2dc30
a.out[11729:555819] b copy 0x7b6511a0
a.out[11748:572916] s 0x67048
Since the object is at 0x7b, we can assume that is the heap. 0xb is really high memory and, thus, the stack.
The retain doesn't cause a copy (because doing so would have invariably led to leaks) and retain on a stack based object is meaningless.
If you change the [o doSomething1]; to [nil doSomething1]; then that becomes a static block and that lives in readonly mapped memory (readonly-executable pages from the mach-o's TEXT segment) and, thus, there is no allocation to deallocate and retain/release/autorelease are no-ops.
As you can see, the static block ended up around 0x67048 (this number may change from run to run, btw, for a variety of reasons. Low in memory.
In fact, because of the sleep(), we can run vmmap against the a.out process and see:
==== Writable regions for process 11772
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
__DATA 00067000-00068000 [ 4K] rw-/rwx SM=ZER /tmp/a.out
That is, the static block was in the first 4K segment of mapped writable regions from the mach-o file. Note that this doesn't mean the code is in that writable region (SECURITY HOLE if it were). The code is in the TEXT segment mapped into the readable regions.

ARC Memory Management with IOS 6

I'm going through the Big Nerd Ranch for IOS (3rd edition). And I'm on the ARC Memory Management chapter. It's trying to explain retain cycles and it has us modify a short console application like so:
Header for BNRItem:
#interface BNRItem : NSObject
{
NSString *itemName;
NSString *serialNumber;
int valueInDollars;
NSDate *dateCreated;
BNRItem *containedItem;
BNRItem *container;
}
+ (id)randomItem;
- (void)setItemName:(NSString *)str;
- (NSString *)itemName;
- (void)setSerialNumber:(NSString *)str;
- (NSString *)serialNumber;
- (void)setValueInDollars:(int) i;
- (int)valueInDollars;
- (void)setContainedItem:(BNRItem *)i;
- (BNRItem *)containedItem;
-(void)setContainer:(BNRItem *)i;
- (BNRItem *)container;
- (NSDate *)dateCreated;
- (id)initWithItemName:(NSString*)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;
- (id)initWithItemName:(NSString *)name andSerialNumber:(NSString *)sNumber;
#end
main file:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSMutableArray *items = [[NSMutableArray alloc]init];
BNRItem *backpack = [[BNRItem alloc] init];
[backpack setItemName:#"Backpack"];
[items addObject:backpack];
BNRItem *calculator = [[BNRItem alloc]init];
[calculator setItemName:#"Calculator"];
[items addObject:calculator];
[backpack setContainer:calculator];
NSLog(#"Setting items to nil");
items = nil;
}
return 0;
}
Now after this it says: "Per our understanding of memory management so far, both BNRItems should be destroyed along with their instance variables when items is set to nil". It had prior to this have us override (void) dealloc to print out when our BNRItem gets destroyed.
So I run it and I'm suppose to see because backpack now has a strong reference to calculator neither get destroyed. Now in the console I see both getting destroyed but I think it's because they get destroyed when the application ends. When I do a break point after setting items to nil nothing is getting destroyed. Which is what the book says should happen... but then it makes me set container to
__weak BNRItem *container
and then when I run it still nothing gets destroyed. I'm assuming because there are still pointers to it that I did not set to nil? Even though the book does not mention to do so at this point. So I understand the books explanation (I think), but in practice it's not happening.
I trusted the auto completion.
[backpack setContainer:calculator]
should have been
[backpack setContainedItem:calculator]

Why does my object still work after countless releases?

I can never seem to deallocate my NSMutableString as shown below. The initial retain count should be 1, but after releasing several times, the string is still usable like nothing happened!
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSMutableString* s = [[NSString alloc]initWithString:#"AAA"];
[s release];
[s release];
[s release];
[s release];
[s release];
NSLog(#"%#",s);
[pool drain];
return 0;
}
Of course, if I use Analyze it still tells me that I release a released object on the second release.
Scott's answer is the correct general one, but in this particular case, the reason is that NSString literals (i.e. #"") are uniqued compile-time constants and do not actually do anything at all when retained and released. Your assignment of it to an NSMutableString* does not actually make it an NSMutableString, so what you've written is equivalent to
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
[#"AAA" release];
Releasing an object tells the runtime that it can destroy the object, at least as far as you're concerned, but it doesn't require that the object be destroyed immediately: After your first [s release], Cocoa is free to do whatever it pleases with the memory formerly used by s. It might give that memory to the next object that does an alloc, in which case your later attempts to access s will result in a fiery runtime crash… or it might not need that memory right away, in which case you might get away with accessing a released object.
The rule of thumb is less "I've released this object, which means it no longer exists" and more "I've released this object, which means it's no longer guaranteed to exist."

Retain Count & Copy In Setter?

This is a followup question from a previous question, which is hopefully a little clearer. I am just curious how the code presented below is working, specifically is the variable myString getting released. It does not look like it is from the output?
CODE
// IMPLEMENT
#implementation CelestialBody
- (void)setName:(NSString *)newName{
if(name != newName) {
[name release];
name = [newName copy];
}
}
- (void)dealloc{
[name release];
name = nil;
[super dealloc];
}
#end
.
// ------------------------------------------------------------------- **
// MAIN: 30th September 2009
// ------------------------------------------------------------------- **
#import <Foundation/Foundation.h>
#import "CelestialBody.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
CelestialBody *newPlanet = [[CelestialBody alloc] init];
NSString *myString = [[NSString alloc]initWithFormat:#"go home"];
NSLog(#"RetainCount_1: %d",[myString retainCount]);
[newPlanet setName: myString];
NSLog(#"RetainCount_2: Incremented by copy in setName");
// Clean up
NSLog(#"RetainCount_2: %d -Before Release",[myString retainCount]);
[newPlanet release];
[myString release];
[pool drain];
NSLog(#"RetainCount_1: %d -After Release",[myString retainCount]);
return 0;
}
// ------------------------------------------------------------------- **
OUTPUT
Running…
2009-10-01 09:28:50.395 RetainCount_1: 1
2009-10-01 09:28:50.399 RetainCount_2: Incremented by copy in setName
2009-10-01 09:28:50.399 RetainCount_2: 2 -Before Release
2009-10-01 09:28:50.400 RetainCount_1: 1 -After Release
Debugger stopped.
I am currently re-reading the Memeory Management Guide to try and see what I have missed.
many thanks
EDIT
Just added a the release to the dealloc, It looks like that was what I was missing.
- (void)dealloc{
[name release];
name = nil;
[super dealloc];
}
gary
is the variable myString getting released.
[myString release];
All signs point to yes.
It does not look like it is from the output?
NSLog(#"RetainCount_2: %d",[myString retainCount]);
[myString release];
Your NSLog statement's output doesn't reflect the release message because the release message hasn't happened yet.
Also, don't worry about retain counts. They can be very misleading. As long as you follow Cocoa's rules and don't create any ownership cycles (A owns B owns C owns A), you'll rarely have a problem.
This is not an answer to your question, per se, but an explanation of what you're seeing: The last call to retainCount is sent to a deallocated object, which is undefined behavior. The object happens not to have been overwritten yet, so it still kindasorta "works" in the sense that the method dispatch can still see the old data that's there and doesn't realize it's invalid. You will never get back 0 from calling retainCount, because such an object can't exist.
I guess [newName copy] doesn't actually copy the NSString, because NSString is immutable? I never thought about this, but it makes sense to me.
At the end of your program you're releasing newPlanet and myString. My question is, are you releasing the instance variable name in the -dealloc method of CelestialBody? If you're not, then I believe you're leaking memory there.
Create myString: the NSString's retainCount is 1
[newPlanet setName:]: the NSString's retainCount is 2
[newPlanet release]: the NSString's retainCount is 2 ? I guess you're not releasing it in -dealloc.
[myString release]: the NSString's retainCount is 1

Why does NSSet objectEnumerator increment the retain count?

After getting the objectEnumerator in the following code, the set1 retain count goes to 3. I was surprised to see that because I didn't expect it to change. I searched the documentation and can't find where this effect is explained.
I assume the extra retains are probably set to autorelease by the Cocoa enumeration logic and won't really have any effect in the current event loop. It makes sense the objectEnumerator logic would need a reference to set1 but I'd like to know why they were made. Here is the reason: if I assume set1 has retain count zero after the release in the code then I could try to reuse it another new set. Wouldn't that cause problems since set1 is now pointing at a completely different object/address?
For "bonus" points, is there a way of enumerating the autorelease pool see what it actually contains? TIA
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSString.h>
#import <stdio.h>;
// macro to create an integer number:
#define INTOBJ(v) [NSNumber numberWithInt: v]
int main (int argc, char *argv[])
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//Make set
NSMutableSet *set1 = [[NSMutableSet alloc] initWithObjects:
INTOBJ(1), INTOBJ(2), INTOBJ(5), INTOBJ(10), nil];
printf("set1 #%lu\n", [set1 retainCount]);
//Get enumerator of the set. This is where the retain count goes to 3:
NSEnumerator *setEnum = [set1 objectEnumerator];
printf("setEnum #%lu\n", [setEnum retainCount]);
printf("set1 #%lu\n", [set1 retainCount]);
//Iterate through the collection:
printf("[");
NSNumber *element;
while ((element = [setEnum nextObject]) != nil)
//do some this with item. printf is just for debugging:
printf(" %i ", [element intValue]);
printf("]\n");
printf("set1 #%lu\n", [set1 retainCount]);
[set1 release];
printf("set1 after release #%lu\n", [set1 retainCount]);
//More logic could go here reusing variable set1 since I assumed retain count = 0
[pool release];
return 0;
}
It's generally not a good idea to rely on the retain count of objects, as it's an internal detail of the framework. Instead make sure your code adheres to the memory management principles, particularly ensuring that retain/new/copy and release/autorelease are balanced.
Presumably, the enumerator is retaining the collection so that it doesn't get deallocated during enumeration. An enumerator without a valid collection to enumerate wouldn't work very well. In fact, the only way for the enumerator to be sure that it will work is to retain the collection it enumerates.
That said, there's really no reason to ever look at the retain count of any object except for debugging a memory leak/double-release problem. As long as you follow the memory management conventions, you should never need to worry about an object's retain count.
Reusing set1 after the release won't cause problems, because the retain count is on the Object referenced by the variable set1, not on the variable itself.