Reputation: 2402
After searching the web without any insight, I decided to post my problem here hoping someone can explain what's wrong with the following piece of code. I just couldn't implement the singleton design pattern in Objective-C. I found some code that implements the pattern on the internet, and modified it a little bit to add a dictionary that is initiated from a plist file (where each entry in the property list is itself a dictionary):
@interface UnitManager : NSObject {
NSString *someProperty;
NSDictionary *factors;
}
@property (nonatomic, retain) NSString *someProperty;
@property (nonatomic, retain) NSDictionary *factors;
+ (id) sharedUnitManager;
@end
// implementation file
#import "Unit.h"
#define MARGIN 20
static UnitManager *unitManager = nil;
@implementation UnitManager
@synthesize someProperty;
@synthesize factors;
#pragma mark Singleton Methods
+ (id)sharedUnitManager {
@synchronized(self) {
if(unitManager == nil)
unitManager = [[super allocWithZone:NULL] init];
}
return unitManager;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedUnitManager] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
someProperty = [[NSString alloc] initWithString:@"Default Property Value"];
// get path to file that stores category order
NSString *path = [[NSBundle mainBundle] pathForResource:@"factors" ofType:@"plist"];
factors = [NSDictionary dictionaryWithContentsOfFile:path];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
[someProperty release];
NSLog(@"should NEVER GET HERE!");
[factors release];
[super dealloc];
}
@end
Then, some code later I try to access the dictionary in the singleton object, and the first time it's called works fine, but the second time it accesses deallocated memory!
- (void) someFunction {
...
NSString *str = [[[[UnitManager sharedUnitManager] factors] objectForKey:category] objectForKey:name];
...
...
}
The message I get is
2011-09-12 17:39:44.567 Test[7837:b603] -[CFDictionary objectForKey:]: message sent to deallocated instance 0x4d565e0*
And the memory trace:
(gdb) info malloc-history 0x5c73300 Alloc: Block address: 0x05c73300 length: 48 Stack - pthread: 0xac2bd2c0 number of frames: 44
0. 0x92573993 in malloc_zone_malloc
1. 0xebe87d in _CFRuntimeCreateInstance
2. 0xebe47a in CFBasicHashCreate
3. 0xf81e44 in __CFDictionaryCreateTransfer
4. 0xeed3ff in __CFBinaryPlistCreateObject2
5. 0xee2009 in __CFTryParseBinaryPlist
6. 0xee1a48 in _CFPropertyListCreateWithData
7. 0xee19ba in CFPropertyListCreateWithData
8. 0xee194f in CFPropertyListCreateFromXMLData
9. 0x791e34 in _NSParseObjectFromASCIIPropertyListOrSerialization
10. 0x7913d5 in +[NSDictionary(NSDictionary) newWithContentsOf:immutable:]
11. 0xf067bf in -[__NSPlaceholderDictionary initWithContentsOfFile:]
12. 0x791308 in +[NSDictionary(NSDictionary) dictionaryWithContentsOfFile:]
13. 0xcb38 in -[UnitManager init] at Unit.m:54
14. 0xc833 in +[UnitManager sharedUnitManager] at Unit.m:25
15. 0xe03a in -[Unit factor] at Unit.m:226
16. 0x39c9 in -[MainViewController updateTextFields] at MainViewController.m:113
17. 0x4037 in -[MainViewController refresh] at MainViewController.m:229
18. 0x79c669 in _nsnote_callback
19. 0xf879f9 in __CFXNotificationPost_old
20. 0xf0693a in _CFXNotificationPostNotification
21. 0x79220e in -[NSNotificationCenter postNotificationName:object:userInfo:]
22. 0x79e551 in -[NSNotificationCenter postNotificationName:object:]
23. 0x62f7 in -[MainViewController changeUnit:] at MainViewController.m:447
24. 0x2a4fd in -[UIApplication sendAction:to:from:forEvent:]
25. 0xba799 in -[UIControl sendAction:to:forEvent:]
26. 0xbcc2b in -[UIControl(Internal) _sendActionsForEvents:withEvent:]
27. 0xba750 in -[UIControl sendActionsForControlEvents:]
28. 0xfa59b in -[UISegmentedControl setSelectedSegmentIndex:]
29. 0xff39d in -[UISegmentedControl touchesBegan:withEvent:]
30. 0x4ed41 in -[UIWindow _sendTouchesForEvent:]
31. 0x2fc37 in -[UIApplication sendEvent:]
32. 0x34f2e in _UIApplicationHandleEvent
33. 0x11e8992 in PurpleEventCallback
34. 0xf90944 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
35. 0xef0cf7 in __CFRunLoopDoSource1
36. 0xeedf83 in __CFRunLoopRun
37. 0xeed840 in CFRunLoopRunSpecific
38. 0xeed761 in CFRunLoopRunInMode
39. 0x11e71c4 in GSEventRunModal
40. 0x11e7289 in GSEventRun
41. 0x38c93 in UIApplicationMain
42. 0x2639 in main at main.m:14
43. 0x25b5 in start
Can someone explain what is happening here? I really don't understand why the second time I can't access the dictionary. I don't see anything deallocating the dictionary for the second time it's used.
Thank you all,
aa
Upvotes: 0
Views: 1086
Reputation: 162722
factors = [NSDictionary dictionaryWithContentsOfFile:path];
You are setting an instance variable to an autoreleased object. As soon as the pool is drained, the next message to factors
will likely go BOOM.
The method follows the standard rules for every method in the framework APIs. See the policies guide.
The documentation goes to great lengths to not repeat the common rules on a per method basis; i.e. if you don't read the guides, you won't understand how each individual method necessarily works.
It is a "give a man a fish, he'll eat for a day... teach a man to fish, and he'll feed himself for a lifetime" approach.
Upvotes: 5