ARC Memory Management with IOS 6 - objective-c

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]

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.

Problems with subclassing NSString

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.

NSMutableDictionary Singleton issue

I am coding Objective-C using the Cocos2D framework, and I have a singleton used for multiple purposes. One new purposes is to get and set character's "states" which are strings. I've recently made an NSDictionary for this purpose, but I have issues with the program freezing up when a method inside the singleton is called.
Here's the singleton code. I'm just leaving in the character state stuff:
.h
#interface ExGlobal : NSObject {
NSArray *charStates_keys;
NSArray *charStates_objects;
NSMutableDictionary *charStates;
}
#property(nonatomic, retain) NSMutableDictionary *charStates;
+(ExGlobal*)sharedSingleton;
- (NSString *)charState:(NSString *)charName;
- (void)set_charState:(NSString *)value forCharName:(NSString *)charName;
#end
.m
#import "ExGlobal.h"
#implementation ExGlobal
#synthesize charStates;
static ExGlobal* _sharedSingleton = nil;
+(ExGlobal*)sharedSingleton {
#synchronized([ExGlobal class]) {
if (!_sharedSingleton) {
[[self alloc] init];
}
return _sharedSingleton;
}
return nil;
}
+(id)alloc {
#synchronized([ExGlobal class]) {
NSAssert(_sharedSingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedSingleton = [super alloc];
return _sharedSingleton;
}
return nil;
}
-(id)init {
self = [super init];
if (self != nil) {
// initialize stuff here
exitName = #"ruinsSkyMid";
sceneChangeKind = #"reborn";
charStates = [[NSMutableDictionary alloc] init];
charStates_keys = [NSArray arrayWithObjects:#"Feathers", #"Hummus", nil];
charStates_objects = [NSArray arrayWithObjects:#"at wall", #"with Feathers", nil];
charStates = [NSMutableDictionary dictionaryWithObjects:charStates_objects forKeys:charStates_keys];
}
return self;
}
- (NSString *)charState:(NSString *)charName{
NSString *value = [charStates objectForKey:charName];
return value;
}
- (void)set_charState:(NSString *)charState forCharName:(NSString *)charName{
[charStates setObject:charState forKey:charName];
}
- (void)dealloc {
//I know it doesn't get called, but just in case
[charStates release];
[super dealloc];
}
#end
It's unclear to me what exactly the issue is when it freezes. When this happens, all I get in the console is:
Program received signal: “EXC_BAD_ACCESS”.
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3.5 (8L1)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
Previous frame inner to this frame (gdb could not unwind past this frame)
Previous frame inner to this frame (gdb could not unwind past this frame)
Which I'm sure doesn't help finding the issue. I found if I redefine charStates_keys, charStates_objects and charStates inside both the charState and set_charState methods, it seems to work without freezing, except set_charState does not change the state.
It isn't freezing, it is crashing. Hence the EXC_BAD_ACCESS. It looks like your Xcode installation is borked, too, as the two messages following should not happen.
Note that methods should not have _s in the name; not a cause of the problem, but a comment on following convention.
You aren't retaining charStates and that is likely the cause of the crash.
Not an answer as such but I didn't have enough space in the comments field above to post this, but it might be useful.
As bbum already said, your lack of retaining charStates is likely the problem.
If you are confused about when to retain and not retain objects there's a really good book called "Learn Objective-C on the Mac" and I know it's a Mac book but most of it applies to iPhone too. On page 171 of chapter 9 (Memory Management) it talks about the "Memory Management Rules" and how if you are confused about when to retain or not then you don't understand the simple rules of Objective C memory management.
Essentially if you create an object using new, alloc or copy, then the retain count is automatically set to 1 so the object is retained and does not require you to retain it and will require a subsequent release to deallocate.
If you create the object any other way then the object will be an autoreleased object.
Obviously these rules only apply within the standard iOS libraries and can't necessarily be applied to third party libraries.
I recommend anyone who doesn't fully understand memory management in Objective C read this book. I found highly enlightening even for my iPhone work.
Hope that helps/.

Objective C Reassignment/Memory Management Crash

As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.

Singleton EXC_BAD_ACCESS

so I have a class that I declare as a singleton and in that class I have a NSMutableArray that contains some NSDictionaries with some key/value pairs in them. The trouble is it doesn't work and I don't know why... I mean it crashes with EXC_BAD_ACCESS but i don't know where. I followed the code and it did create a new array on first add, made it to the end of the function ..and crashed ...
#interface dataBase : NSObject {
NSMutableArray *inregistrari;
}
#property (nonatomic,retain) NSMutableArray *inregistrari;
-(void)adaugaInregistrareCuData:(NSDate *)data siValoare:(NSNumber *)suma caVenit:(BOOL)venit cuDetaliu:(NSString *)detaliu;
-(NSDictionary *)raportIntreData:(NSDate *)dataInitiala siData:(NSDate *)dataFinala;
-(NSArray *)luniDisponibileIntreData:(NSDate *)dataInitiala siData:(NSDate *)dataFinala;
-(NSArray *)aniDisponibiliIntreData:(NSDate *)dataInitiala siData:(NSDate *)dataFinala;
-(NSArray *)vectorDateIntreData:(NSDate *)dataI siData:(NSDate *)dataF;
-(void)salveazaInFisier;
-(void)incarcaDinFisier;
+ (dataBase *)shareddataBase;
#end
And here is the .m file
#import "dataBase.h"
#import "SynthesizeSingleton.h"
#implementation dataBase
#synthesize inregistrari;
SYNTHESIZE_SINGLETON_FOR_CLASS(dataBase);
-(void)adaugaInregistrareCuData:(NSDate *)data siValoare:(NSNumber *)suma caVenit:(BOOL)venit cuDetaliu:(NSString *)detaliu{
NSNumber *v=[NSNumber numberWithBool:venit];
NSArray *input=[NSArray arrayWithObjects:data,suma,v,detaliu,nil];
NSArray *keys=[NSArray arrayWithObjects:#"data",#"suma",#"venit",#"detaliu",nil];
NSDictionary *inreg=[NSDictionary dictionaryWithObjects:input forKeys:keys];
if(inregistrari == nil) {
inregistrari=[[NSMutableArray alloc ] initWithObjects:inreg,nil];
}else {
[inregistrari addObject:inreg];
}
[inreg release];
[input release];
[keys release];
}
It made it to the end of that adaugaInregistrareCuData ... ok . said the array had one object ... and then crashed
Try adding "NSZombieEnabled" with value "YES" to your arguments on your executeable:
Right click your executeable, select get info and add that entry to the variables in the bottom list.
This will tell you what datatype has crashed.
Using build & analyze it tells me that you are releasing inreg, input and keys twice.
All three variables will be autoreleased, your manual release will cause the later autorelease to fail and give you your BAD_ACCESS.
Don't manually release them, remove these three lines from your code:
[inreg release];
[input release];
[keys release];