So this is very odd. I just created a new singleton in my app, using the same pattern I have used for the numerous other singletons. This one, however, doesn't play nice with the debugger. Here's the code for getting the singleton:
- (id)init
{
self = [super init];
if (self) {
[self loadData];
}
return self;
}
+ (Settings *)sharedInstance
{
static Settings *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
Now when I try to access the object from the debugger, it gives me this:
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x20).
The process has been returned to the state before expression evaluation.
Obviously, I wasn't trying to just get a handle on the singleton from the debugger, I was trying to get a property. This gives some even more interesting output:
(lldb) po [Settings sharedInstance].jpegCompressionLevel
error: warning: got name from symbols: Settings
warning: receiver type 'void *' is not 'id' or interface pointer, consider casting it to 'id'
error: no known method '-sharedInstance'; cast the message send to the method's return type
error: 1 errors parsing expression
What in the world is going on here? Calls from within code seem to go just fine. The debugger constantly fails though. In the same context, I'm able to access other singletons (using the same pattern) just fine.
It may be worth noting that the loadData method just loads a dictionary from disk, and that the getters for the properties on the class utilise the values in that dictionary, rather than ivars.
Here's the code for loadData:
-(void)loadData
{
// Load data from persistent storage
NSString *path = [self pathToDataFile];
NSMutableDictionary *settingsDict = [NSMutableDictionary dictionaryWithDictionary:[NSKeyedUnarchiver unarchiveObjectWithFile:path]];
_settingsDict = settingsDict;
}
Now I can't explain why, but renaming the class from 'Settings' to something else fixed the issue. I can't see anything in the Symbol Navigator that would clash with this name... very weird indeed.
Related
I'm receiving a static analysis error, and I'm not sure if it can be safely ignored, or if I can improve the design to remove it without to much change, this is legacy code.
This does NOT use ARC.
-(id) initCustom{
NSString* key = #"foo";
NSData* objectData = nil;
objectData = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if( objectData != nil)
{
//If this path is taken the error occurs
self = [NSKeyedUnarchiver unarchiveObjectWithData:objectData];
}
else
{
self = [super init];
}
if (self)
{
//Static analysis warns here
m_fiz = [[NSString alloc] initWithString:#"bar"];
//Instance variable used while 'self' is not set to the result of '[(super or self) init....]'
}
}
My understanding is that [NSKeyedUnarchiver unarchiveObjectWithData:objectData] will cause the "initWithCoder" to be called. This object implements NSCoding, and has the proper methods required by NSCoding implemented.
Is this a false positive from the static analysis, or can I make it better?
The compiler is warning because that is a bizarre way of doing this. :)
Move the "read from defaults or create new" logic into a class method. That will both fix the compiler warning message and be a more consistent pattern.
+ (instancetype) defaultThingamahoover
{
... check defaults database and unarchive ...
... or return new ....
}
BTW: In general, you don't want to shove anything large into the defaults database. It is pretty atypical to archive an object and stick it in there. The defaults database is generally intended for small key/value pairs.
I'm confused about how to use singleton effectively. I want my singleton class to behave like [NSUserDefaults standardUserDefaults]. But what I've observed is each time it is creating new object and I see a different memory address each time.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MyManager *sharedManager = [MyManager sharedManager];
if(sharedManager.name.length==0) {
sharedManager.name = #"manager";
}
return YES;
}
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;}
each time at launch, if clause is executing.
What you want to achieve is not what a singleton does.
A singleton is an object for which there is only one instance while your application is running. If you call [MyManager sharedManager] 10 times in a row, you'll get the same address each time.
Once you quit the application, like any other object the singleton is destroyed. Next time you start the application, you'll get a brand new singleton being created without any of the old data.
NSUserDefaults is able to remember data even after quitting and restarting the application because it is persisting that data to disk (i.e. writing a file somewhere.) If you want to have similar behaviour, you'll also need to save you data to a file and read that file on startup.
I need to implement a singleton class that takes in a parameter. The same object will be passed as a parameter every single time so the resultant singleton object will always be the same.
I am doing something like the code below. Does this look ok? Is there a better way of achieving what I want to achieve?
- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
if (!sharedInstance) {
#synchronized(self) {
sharedInstance = [[[self class] alloc] initWithAccount:userAccount];
}
}
return sharedInstance;
}
- (id)initWithAccount:(UserAccount *)userAccount {
self = [super init];
if (self) {
_userAccount = userAccount;
}
return self;
}
- (id)init {
NSAssert(false,
#"You cannot init this class directly. It needs UserAccountDataSource as a paramter");
return nil;
}
+ (id)alloc {
#synchronized(self) {
NSAssert(sharedInstance == nil, #"Attempted to allocated a second instance of the singleton");
sharedInstance = [super alloc];
return sharedInstance;
}
return nil;
}
There are a number of problem in this design:
As recommended by Apple, should dispatch_once instead of #synchronized(self) for singleton:
static MyClass *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[MyClass alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
Refer to this question for more detail: Why does Apple recommend to use dispatch_once for implementing the singleton pattern under ARC?
Bad API design to put singleton in alloc.
As indicated by the name of the method alloc, it means that some memory will be allocated. However, in your case, it is not. This attempt to overwrite the alloc will cause confusion to other programmers in your team.
Bad idea to use NSAssert in your -init.
If you want to disable a method, disable it by putting this in your header file:
- (id)init __attribute__((unavailable));
In this case, you will get a compile error instead of crashing the app at run time.
Refer to this post for more detail: Approach to overriding a Core Data property: isDeleted
Moreover, you can even add unavailable message:
- (id)init __attribute__((unavailable("You cannot init this class directly. It needs UserAccountDataSource as a parameter")));
Sometime input parameters is ignored with no warning.
In your following code, how would the programmer who is calling this function know that the input parameter userAccount is sometimes ignored if an instance of the class is already created by someone else?
- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
if (!sharedInstance) {
#synchronized(self) {
sharedInstance = [[[self class] alloc] initWithAccount:userAccount];
}
}
return sharedInstance;
}
In short, don't think it is a good idea to create singleton with parameter. Use conventional singleton design is much cleaner.
objA = [Object sharedInstanceWithAccount:A];
objB = [Object sharedInstanceWithAccount:B];
B is ignored.
userAccount in objB is A.
if userAccount B in objB, you will change sharedInstanceWithAccount.
- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
static NSMutableDictionary *instanceByAccount = [[NSMutableDictionary alloc] init];
id instance = instanceByAccount[userAccount];
if (!instance) {
#synchronized(self) {
instance = [[[self class] alloc] initWithAccount:userAccount];
instanceByAccount[userAccount] = instance;
}
}
return instance;
}
I have a singleton that is initialized like all singletons, with something like this:
+ (MySingleton *)sharedInstance
{
static MySingleton *sharedMyInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyInstance = [[MySingleton alloc] init];
});
return sharedMyInstance;
}
In my case I want to add some code to initialize the singleton but because sharedInstance is a class method I cannot call instance methods from there.
So I always have to have this pattern:
MySingleton *sing = [MySingleton sharedInstance];
[sing initialize];
Ok, I can do this
MySingleton *sing = [[MySingleton sharedInstance] initialize];
but this will generate another problem because if initializing the singleton is the only thing I want at this point, sing is not being used and this is ugly code.
I suppose I can do simply
[[MySingleton sharedInstance] initialize];
and Xcode will not complain, but this does not sound good.
Is there another way to do this?
Check your code ;-) Specifically the line:
sharedMyInstance = [[MySingleton alloc] init];
You have to implement init and there is where you'll initialize the instance variables of your singleton (AKA shared instance). It will be called the first time the shared instance is used.
I'm basically implementing a fancier NSURLConnection class that downloads data from a server parses it into a dictionary, and returns an NSDictionary of the data. I'm trying add a completion block option (in addition to a delegate option), but it crashes anytime I try to store that data in another class.
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
I can NSLog that data just fine, and basically do whatever I want with it, but as soon as I try to save it into another variable it crashes with a really obscure message.
EDIT: the crash message is EXC_BAD_ACCESS, but the stack trace is 0x00000000 error: address doesn't contain a section that points to a section in a object file.
I'm calling this function in the init method of a singleton. It DOES let me save the data if I set this in the completion block.
[SingletonClass sharedInstance].contentDictionary = data
But then the app gets stuck forever because sharedInstance hasn't returned yet, so the singleton object is still nil, so sharedInstance in the completion block calls init again, over and over.
EDIT 2: The singleton code looks like this:
+ (SingletonClass*)sharedInstance {
static SingletonClass *instance;
if (!instance) {
instance = [[SingletonClass alloc] init];
}
return instance;
}
- (id)init {
self = [super init];
if (self) {
dataFetcher_ = [[DataFetcher alloc] init];
NSString *testURL = #"..."
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
[SingletonClass sharedInstance].contentDictionary = data;
}];
}
return self;
}
Like I said, this works fine but repeats the initialize code over and over until the app crashes. This only happens the first time I run the app on a device, because I cache the data returned and it doesn't crash once I have the data cached. I would like to be able to just say self.contentDictionary = data, but that crashes.
Specify a variable to be used in the block with the __block directive outside of the block:
__block NSDictionary *contentDictionary_;
[dataFetcher_ fetchDataWithURL:testURL completionHandler:^(NSDictionary *data, NSInteger error) {
contentDictionary_ = data;
}];
You're invoking recursion before ever setting the "instance". (which I now see you understand from OP).
In your block, you can use the ivar or an accessor instead of
[SingletonClass sharedInstance].contentDictionary
use:
_contentDictionary = [data copy]; or self.contentDictionary=data;
assuming that the ivar backing the contentDictionary property is _contentDictionary.
It sounds like you tried self.contentDictionary and it failed? I got it to work in a test, with ARC turned, so there may be something about your dataFetcher that is affecting this. In my test dataFetcher just returns a dictionary with a single element.
Turns out the issue was with a bunch of different parts. My URL was empty sometimes, and my data fetcher would just fail immediately and call the completion block. In my completion block I hadn't included any error handling, so if the singleton class hadn't initialized, it would repeat forever. With a real URL this doesn't happen.
I still would like to figure out why it crashes when I try to assign the data to an ivar, though.