Crash while releasing in implementation of NSCoding Protocol methods - objective-c

Below is the code I use for unarchiving the data:
- (id)initWithCoder:(NSCoder *)decoder
{
if((self = [super init]))
{
NSString *resoureItemClassName = NSStringFromClass([ResourceItem class]);
self.selectedItemClassName=[decoder decodeObjectForKey:#"selectedItemClassName"];
Class class = NSClassFromString(selectedItemClassName);
id temp = nil;
if(class)
{
temp = [[class alloc] init];
}
if(temp)
{
if([temp isKindOfClass:[NSString class]])
{
self.selectedString = [decoder decodeObjectForKey:#"selectedString"];;
self.ID = selectedString;
}
else if([selectedItemClassName isEqualToString:resoureItemClassName])
{
DLog(#"Resource item")
}
[temp release];
}
else
{
self.ID = nil;
}
}
return self;
}
When I release the tempObject, the app crashes. If I do not do it, it is a leak. I tried using the autorelease method. Still then it crashed? What is the solution?
UPDATE:
The encoded data in selectedItemClassName is either __NSCFConstantString or __NSCFString. The crash occurs only with __NSCFString and not with __NSCFConstantString as the class name,
What could be the reason?

How about:
- (id)initWithCoder:(NSCoder *)decoder
{
if((self = [super init]))
{
NSString *resourceItemClassName = NSStringFromClass([ResourceItem class]);
self.selectedItemClassName=[decoder decodeObjectForKey:#"selectedItemClassName"];
Class class = NSClassFromString(selectedItemClassName);
if(class)
{
NSString *stringClassName = NSStringFromClass([NSString class]);
if([stringClassName isEqualToString:self.selectedItemClassName])
{
self.selectedString = [decoder decodeObjectForKey:#"selectedString"];;
self.ID = selectedString;
}
else if([self.selectedItemClassName isEqualToString:resourceItemClassName])
{
DLog(#"Resource item")
}
}
else
{
self.ID = nil;
}
}
return self;
}

Related

Can i achieve the real singleton in objective-c like java c#

init method is declared in NSObject class hence, the client code can create a new instance of my singleton class, is there any way to achieve the real singleton such that client cannot create a new instance.
Just do this:
static SingletonClass *singleton;
+ (SingletonClass *)sharedInstance
{
#synchronized(self) { //For thread safety
if (singleton == nil) {
[[self alloc] init];
}
return singleton;
}
}
-(id)init
{
if (singleton) { //This way init will always return the same instance
return singleton;
}
self = [super init];
if (self) {
singleton = self;
}
return singleton;
}
This is a proper way to do a singleton in objective c
+ (id)sharedManager
{
static dispatch_once_t onceQueue;
static SingletonObjectClass *singleton = nil;
dispatch_once(&onceQueue, ^{
singleton = [[self alloc] init];
});
return singleton;
}
- (id)init {
self = [super init];
if (self) {
//.....
}
return self;
}
init method is for initialization of instance variables. on its own will not create the object. Alloc, copy methods needs to be overridden to achieve a real single ton.
Hope this should clarify.
+ (id)alloc {
NSLog(#"%#: use +sharedInstance instead of +alloc", [[self class] name]);
return nil;
}
+ (id)new {
return [self alloc];
}
+ (SingletonClass *)sharedInstance {
static SingletonClass *myInstance = nil;
if (!myInstance)
{
myInstance = [[super alloc] init];
}
return myInstance;
}
You can return a static object of the class each time, making it a singleton.
#implementation Singleton
#synthesize testVar;
+ (Singleton*) sharedObject {
static Singleton * myInstance = nil;
if (myInstance == nil) {
myInstance = [[[self class] alloc] init];
testVar = 5;
// Set default values if needed
return myInstance;
}
To access object and its members:
[[Singleton sharedObject] testVar]

Objective-C calling instance method within Singleton

I created a Singleton class named DataManager. I've set up some caching. However, when attempting to call the cache instance methods from within another instance method of DataManager, I'm getting EXC_BAD_ACCESS error on the line:
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
DataManager.h
#interface DataManager : NSObject {
FMDatabase *db;
NSMutableDictionary *cache;
}
+ (DataManager *)sharedInstance;
// ... more methods
- (id)getCacheForKey:(NSString *)key;
- (void)setCacheForKey:(NSString *)key withValue:(id)value;
- (void)clearCache;
DataManager.m
- (NSArray *)getStops:(NSInteger)archived {
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
if (stops != nil) {
NSLog(#"Stops: %#", stops);
return stops;
}
stops = [NSMutableArray array];
// set stops...
[[DataManager sharedInstance] setCacheForKey:#"stops" withValue:stops];
return stops;
}
It seems to occur when called from another view controller. That is the first view controller no error, second view controller, error.
This is my first attempt at a Singleton, so I'm sure I am making a simple mistake. But I am failing to see it myself.
Note: I've tried [self getCache...] with the same result.
UPDATE
Here is my Singleton implementation. Adapted from http://www.galloway.me.uk/tutorials/singleton-classes/
+ (DataManager *)sharedInstance {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
return instance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX;
}
- (oneway void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
if (db == nil){
BourbonAppDelegate *appDelegate = (BourbonAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate createEditableCopyOfDatabaseIfNeeded];
db = [[FMDatabase alloc] initWithPath:[appDelegate getDBPath]];
}
if (![db open]) {
NSAssert(0, #"Failed to open database.");
[db release];
return nil;
}
[db setTraceExecution:YES];
[db setLogsErrors:TRUE];
cache = [NSMutableDictionary dictionary];
NSLog(#"cache: %#", cache);
}
return self;
}
Your cache object is autoreleased, so it's no longer in memory when you try to access it.
Use [NSMutableDictionary alloc] init] instead of [NSMutableDictionary dictionary] to get an instance that is retained.
Not a direct answer to your question, which was already answered.
However I'd just like to point out that your singleton implementation is sub-optimal. #synchronized is very expensive, and you can avoid using it every time you access the singleton:
if (!instance) {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
}
An even better way to initialize a singleton would be:
+ (DataManager *)sharedInstance {
static DataManager *instance;
static dispatch_once_t donce;
dispatch_once(&donce, ^{
instance = [[self alloc] init];
});
return instance;
}

Bail out of an object's init with arc?

How do I bail out of creating an object with ARC?
I'm looking for the ARC equivalent of this (from memory):
- (id)init
{
if (( self = [super init] )) {
if (!condition) {
[self release];
self = nil;
return self;
}
}
return self;
}
Just get rid of the call to release and you'll be fine. Since you nil self, there will be no more references to the old self so it will be deallocated.
- (id)init;
{
if ((self = [super init])) {
if (!condition) {
return nil;
}
}
return self;
}

Singleton method not getting called in Cocos2d

I am calling a Singleton method that does not get called when I try doing this.
I get no errors or anything, just that I am unable to see the CCLOG message.
Under what reasons would a compiler not give you error and not allow you to call a method?
[[GameManager sharedGameManager] openSiteWithLinkType:kLinkTypeDeveloperSite];
The method is defined as follows:
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType"); //I NEVER SEE THIS MESSAGE
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
HERE IS THE CODE TO MY SINGLETON:
#import "GameManager.h"
#import "MainMenuScene.h"
#implementation GameManager
static GameManager* _sharedGameManager = nil;
#synthesize isMusicON;
#synthesize isSoundEffectsON;
#synthesize hasPlayerDied;
+(GameManager*) sharedGameManager
{
#synchronized([GameManager class])
{
if (!_sharedGameManager)
{
[[self alloc] init];
return _sharedGameManager;
}
return nil;
}
}
+(id)alloc
{
#synchronized ([GameManager class])
{
NSAssert(_sharedGameManager == nil,#"Attempted to allocate a second instance of the Game Manager singleton");
_sharedGameManager = [super alloc];
return _sharedGameManager;
}
return nil;
}
-(id)init
{
self = [super init];
if (self != nil)
{
//Game Manager initialized
CCLOG(#"Game Manager Singleton, init");
isMusicON = YES;
isSoundEffectsON = YES;
hasPlayerDied = NO;
currentScene = kNoSceneUninitialized;
}
return self;
}
-(void) runSceneWithID:(SceneTypes)sceneID
{
SceneTypes oldScene = currentScene;
currentScene = sceneID;
id sceneToRun = nil;
switch (sceneID)
{
case kMainMenuScene:
sceneToRun = [MainMenuScene node];
break;
default:
CCLOG(#"Unknown Scene ID, cannot switch scenes");
return;
break;
}
if (sceneToRun == nil)
{
//Revert back, since no new scene was found
currentScene = oldScene;
return;
}
//Menu Scenes have a value of < 100
if (sceneID < 100)
{
if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad)
{
CGSize screenSize = [CCDirector sharedDirector].winSizeInPixels;
if (screenSize.width == 960.0f)
{
//iPhone 4 retina
[sceneToRun setScaleX:0.9375f];
[sceneToRun setScaleY:0.8333f];
CCLOG(#"GM: Scaling for iPhone 4 (retina)");
}
else
{
[sceneToRun setScaleX:0.4688f];
[sceneToRun setScaleY:0.4166f];
CCLOG(#"GM: Scaling for iPhone 3G or older (non-retina)");
}
}
}
if ([[CCDirector sharedDirector] runningScene] == nil)
{
[[CCDirector sharedDirector] runWithScene:sceneToRun];
}
else
{
[[CCDirector sharedDirector] replaceScene:sceneToRun];
}
}
-(void)openSiteWithLinkType:(LinkTypes)linkTypeToOpen
{
CCLOG(#"WE ARE IN openSiteWithLinkType");
NSURL *urlToOpen = nil;
if (linkTypeToOpen == kLinkTypeDeveloperSite)
{
urlToOpen = [NSURL URLWithString:#"http://somesite.com"];
}
if (![[UIApplication sharedApplication]openURL:urlToOpen])
{
CCLOG(#"%#%#",#"Failed to open url:",[urlToOpen description]);
[self runSceneWithID:kMainMenuScene];
}
}
-(void) test
{
CCLOG(#"this is test");
}
#end
Perhaps sharedGameManager is returning nil? This won't cause an error.
Edit: The issue is in the new code you posted:
if (!_sharedGameManager) {
[[self alloc] init];
return _sharedGameManager;
}
return nil;
If _sharedGameManager is non-nil, this will return nil. You want:
if (!_sharedGameManager) {
[[self alloc] init];
}
return _sharedGameManager;
Check this Cocoa With Love article on how to make a proper singleton.

TTModel load method not being called

Issue
Description
The following code results in load method of TTModel not being called. I've stepped it through the debugger, as well as stepping through the TTCatalog application. The only difference between the two that I can see, is that when the catalog sets it's DataSource in the createModel method of the controller, TTModel's load method gets called, whereas mine does not.
About the Code
I've commented the specific areas of code, to tell what they should be doing and what problem is happening, but I included all of it for completion's sake.
You should look at specifically
PositionsController's createModel method
PositionsList's load method
Those are the areas where the issue is, and would be the best place to start.
Code
PositionsController : TTTableViewController
- (id)initWIthNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.title = #"Positions";
self.variableHeightRows = NO;
self.navigationBarTintColor = [UIColor colorWithHexString:#"1F455E"];
}
return self;
}
//This method here should result in a call to the PositionsList load method
- (void)createModel
{
PositionsDataSource *ds = [[PositionsDataSource alloc] init];
self.dataSource = ds;
[ds release];
}
- (void)loadView
{
[super loadView];
self.view = [[[UIView alloc] initWithFrame:TTApplicationFrame()] autorelease];
self.tableView = [[[UITableView alloc] initWithFrame:TTApplicationFrame() style:UITableViewStylePlain] autorelease];
self.tableView.backgroundColor = [UIColor colorWithHexString:#"E2E7ED"];
self.tableView.separatorColor = [UIColor whiteColor];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
//Override UITableViewDelegate creation method, so we can add drag to refresh
- (id<TTTableViewDelegate>) createDelegate {
TTTableViewDragRefreshDelegate *delegate = [[TTTableViewDragRefreshDelegate alloc] initWithController:self];
return [delegate autorelease];
}
PositionsDataSource : TTListDataSource
#implementation PositionsDataSource
#synthesize positionsList = _positionsList;
-(id)init
{
if (self = [super init])
{
_positionsList = [[PositionsList alloc] init];
self.model = _positionsList;
}
return self;
}
-(void)dealloc
{
TT_RELEASE_SAFELY(_positionsList);
[super dealloc];
}
-(void)tableViewDidLoadModel:(UITableView*)tableView
{
self.items = [NSMutableArray array];
}
-(id<TTModel>)model
{
return _positionsList;
}
#end
PositionsList : NSObject <TTModel>
#implementation PositionsList
//============================================================
//NSObject
- (id)init
{
if (self = [super init])
{
_delegates = nil;
loaded = NO;
client = [[Client alloc] init];
}
return self;
}
- (void)dealloc
{
TT_RELEASE_SAFELY(_delegates);
[client dealloc];
[super dealloc];
}
//==============================================================
//TTModel
- (NSMutableArray*)delegates
{
if (!_delegates)
{
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
-(BOOL)isLoadingMore
{
return NO;
}
-(BOOL)isOutdated
{
return NO;
}
-(BOOL)isLoaded
{
return loaded;
}
-(BOOL)isEmpty
{
//return !_positions.count;
return NO;
}
-(BOOL)isLoading
{
return YES;
}
-(void)cancel
{
}
//This method is never called, why is that?
-(void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more
{
//This method is not getting called
//When the PositionsController calls self.datasource, load should be called,
//however it isn't.
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
[client writeToServer:self dataToSend:#""];
}
-(void)invalidate:(BOOL)erase
{
}
#end
Short answer: return NO instead of YES for isLoading in your PositionList.
For a longer explanation:
If you dig through the Three20 source, you'll find that setting the dataSource on a view controller sets the model, refreshing the model and potentially calling load. Here is the code called when the TTModelViewController refreshes:
- (void)refresh {
_flags.isViewInvalid = YES;
_flags.isModelDidRefreshInvalid = YES;
BOOL loading = self.model.isLoading;
BOOL loaded = self.model.isLoaded;
if (!loading && !loaded && [self shouldLoad]) {
[self.model load:TTURLRequestCachePolicyDefault more:NO];
} else if (!loading && loaded && [self shouldReload]) {
[self.model load:TTURLRequestCachePolicyNetwork more:NO];
} else if (!loading && [self shouldLoadMore]) {
[self.model load:TTURLRequestCachePolicyDefault more:YES];
} else {
_flags.isModelDidLoadInvalid = YES;
if (_isViewAppearing) {
[self updateView];
}
}
}
Your PositionList object is returning YES for isLoading and NO for isLoaded. This means that Three20 thinks your model is loading when it isn't. You may also need to implement shouldLoad if it doesn't return YES by default.