Unable to update singleton properties - objective-c

I've encountered a stupid problem, and I've tried almost everything (bought 3 books, went through the whole google :)) but nothing helped. And it seems to me like the solution should be extremely simple...
I need to declare a singleton in Objective-C (for an iOS app, if that matters), and it should have some properties that I need to update from other classes. But I can't do that - the properties just won't update, they have the same values set in the "init" method.
I've created a simple app to test out this problem. That's what I've done:
First, I've declared a sample class and its subclass that I'm going to use as a singleton's property:
#interface Entity : NSObject
#property (nonatomic, strong, readwrite) NSMutableString * name;
#end
#implementation Entity
#synthesize name;
#end
#interface Company : Entity
#property (nonatomic, strong, readwrite) NSMutableString * boss;
#property (nonatomic) int rating;
#end
#implementation Company
#synthesize boss, rating;
#end
Then I declare the singleton itself based on the method described in the "iOS Programming Guide by Big Nerd Ranch" book. I'm using both my custom class and a standard NSMutableString as properties, just for clarity's sake:
#class Company;
#interface CompanyStore : NSObject
{
NSMutableString * someName;
}
#property (nonatomic, strong, readwrite) Company * someCompany;
#property (nonatomic, strong, readwrite) NSMutableString * someName;
+ (CompanyStore *) store;
- (void) modifyCompanyProperties;
#end
#implementation CompanyStore
#synthesize someCompany, someName;
// Declaring the shared instance
+ (CompanyStore *) store
{
static CompanyStore * storeVar = nil;
if (!storeVar) storeVar = [[super allocWithZone:nil] init];
return storeVar;
}
// Replacing the standard allocWithZone method
+ (id) allocWithZone:(NSZone *)zone
{
return [self store];
}
Then I initialize all the properties with initial values:
- (id) init
{
self = [super init];
if (self) {
someCompany = [[Company alloc] init];
[someCompany setBoss:[NSMutableString stringWithFormat:#"John Smith"]];
[someCompany setName:[NSMutableString stringWithFormat:#"Megasoft"]];
[someCompany setRating:50];
someName = [[NSMutableString alloc] initWithString:#"Bobby"];
}
return self;
}
And from another class (view controller that displays the contents in a view):
1. I get the values of the singleton's properties. Everything's okay - I get "John Smith", "Megasoft", "Bobby" and 50 for my int value. The values from my init method.
2. I change the singleton's properties from that view controller (using several ways - I'm not sure now which one is right):
- (IBAction)modify2Button:(id)sender {
CompanyStore * cst = [CompanyStore store];
NSMutableString * name = [[NSMutableString alloc] initWithString:#"Microcompany"];
NSMutableString * boss = [[NSMutableString alloc] initWithString:#"Larry"];
[[[CompanyStore store] someCompany] setName:name];
cst.someCompany.boss = boss;
NSMutableString * strng = [[NSMutableString alloc] initWithString:#"Johnny"];
[cst setSomeName:strng];
}
... and then I'm trying to get the values again. I'm still getting the old set - "John Smith", "Megasoft" etc. even though when I set a breakpoint at one of the strings, I can see that singleton's name property is "Microcompany" and not "Megasoft" at the time of the break... But it doesn't seem to be assigned.
3. Then I'm trying another thing - I'm calling from the view controller a singleton's private method, which assigns another set of values to the properties. This is that method in the singleton:
- (void) modifyCompanyProperties
{
NSMutableString * boss = [[NSMutableString alloc] initWithString:#"George"];
NSMutableString * name = [[NSMutableString alloc] initWithString:#"Georgeland"];
[someCompany setBoss:boss];
[someCompany setName:name];
[someCompany setRating:100000];
[someName setString:#"Nicholas"];
}
4. I'm trying to get the updated property values from the view controller again... and still get those "John Smith", "Megasoft"... Nothing changes.
It seems like the properties of the singleton are set only once and then I can't change them, even though their attributes are declared as "readwrite".
It looks like I don't understand something simple.
If someone could explain how to correctly declare and update properties in singletons, I would be very grateful.

First thing I noticed was that you are declaring "storeVar" in the body of the store method. And this looks like terribly wrong to me because every time you call this you'll re-initialize the singleton. You should declare the variable like this:
static CompanyStore * storeVar = nil;
#implementation CompanyStore
#synthesize someCompany, someName;
// Declaring the shared instance
+ (CompanyStore *) store
{
if (!storeVar) storeVar = [[super allocWithZone:nil] init];
return storeVar;
}
Also your init method is not exactly complete because you don't want to call init again after the singleton has been initialized so you have to check this and if it has been initialized you should simply return it:
- (id) init
{
if (storeVar!=nil) {
return storeVar;
}
self = [super init];
if (self) {
someCompany = [[Company alloc] init];
[someCompany setBoss:[NSMutableString stringWithFormat:#"John Smith"]];
[someCompany setName:[NSMutableString stringWithFormat:#"Megasoft"]];
[someCompany setRating:50];
someName = [[NSMutableString alloc] initWithString:#"Bobby"];
}
return self;
}
Also, this is not a mistake, just a mere suggestion - you can ditch #synthesize because since ios 6 because the compiler generates it automatically. But again, not a mistake to use it. Hope it helps

Related

expected ';' at end of declaration list objective c

"error: expected ';' at end of declaration list" objective c
When I am trying to declare global variable
#implementation CachingManager{
NSMutableArray*object = [[NSMutableArray alloc] init];
}
You can create instance variables here but cannot initialize those instance variables here like you do. They are all initialized to nil or zeroes. So compiler expect a semicolon when you are writing an equal sign.
You can initialize them in init method or other method where your class initialization takes place in order to make them global variables. e.g.
Interface block for instance variable inside .m file:
#interface CachingManager ()
{
// instance variables initialized to nil or zeroes
NSMutableArray *object; // global ivar
}
#end
Implementation part same .m file:
#implementation CachingManager
- (void)viewDidLoad {
[super viewDidLoad];
object = [[NSMutableArray alloc] init]; // initialization takes place
}
One way to implement global variables, and to manage their lifetime (i.e. that they are initialised) and even to provide global methods is to implement a class exposing those variables/methods and to use the singleton pattern:
yourFile.h:
#import <Foundation/Foundation.h>
#interface GlobalVars : NSObject
{
NSMutableArray *_truckBoxes;
NSMutableArray *_farmerlist;
NSString *_farmerCardNumber;
NSString *_fName;
}
+ (GlobalVars *)sharedInstance;
#property(strong, nonatomic, readwrite) NSMutableArray *truckBoxes;
#property(strong, nonatomic, readwrite) NSMutableArray *farmerList;
#property(strong, nonatomic, readwrite) NSString *farmerCardNumber;
#property(strong, nonatomic, readwrite) NSString *fName;
#end
yourFile.m:
#import "GlobalVars.h"
#implementation GlobalVars
#synthesize truckBoxes = _truckBoxes;
#synthesize farmerList = _farmerList;
#synthesize frameCardNumber = _frameCardNumber;
#synthesize fName = _fName;
+ (GlobalVars *)sharedInstance {
static dispatch_once_t onceToken;
static GlobalVars *instance = nil;
dispatch_once(&onceToken, ^{
instance = [[GlobalVars alloc] init];
});
return instance;
}
- (id)init {
self = [super init];
if (self) {
_truckBoxes = [[NSMutableArray alloc] init];
_farmerlist = [[NSMutableArray alloc] init];
// Note these aren't allocated as [[NSString alloc] init] doesn't provide a useful object
_farmerCardNumber = nil;
_fName = nil;
}
return self;
}
You can then use these global variables like this, for example:
GlobalVars *globals = [GlobalVars sharedInstance];
globals.fName = #"HelloWorld.txt";
[globals.farmerList addObject:#"Old Macdonald"];
[self processList:[globals farmerList]];
However, please consider:
You don't need to use global variables like this; you should be able to create a model object which is created as necessary and reference to it passed to the views. This is MVC.
You also posted a stack trace of an unrelated issue which is extremely common with Objective-C; only you can fix this error, once you realise what it is.
That sin't a global variable. That would be an instance variable and that particular syntax wasn't commonly used after 2005 (really, it wasn't terribly common after the mid 90s).
If you want a global variable, do:
NSMutableArray *myGlobal;
Somewhere at the top level -- at the same level with the #implementation -- of your source.
You'll have to initialize the global variable elsewhere, though. Typically, in the +initialize or +load method of the class.

Singleton set in AppDelegate loses it value when allocated in another class

I have a iPad application where I'm attempting to use a singleton. This is the code in the .h file:
//-------------------------------------------
//-- singleton: timeFormat
#interface SingletonTimeFormat : NSObject {
}
#property (nonatomic, retain) NSNumber *timeFormat;
+ (id)sharedTimeFormat;
#end
This is the code from the .m file:
//-------------------------------------------
//-- SingletonTimeFormat
#implementation SingletonTimeFormat {
}
#synthesize timeFormat;
//-- sharedColorScheme --
+ (id)sharedTimeFormat {
static dispatch_once_t dispatchOncePredicate = 0;
__strong static id _sharedObject = nil;
dispatch_once(&dispatchOncePredicate, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-id) init {
self = [super init];
if (self) {
timeFormat = [[NSNumber alloc] init];
}
return self;
}
#end
I load the value (either 12 or 24) in AppDelegate - didFinishLaunchingWithOptions, then when I want to get the value of timeFormat I use this:
SingletonTimeFormat *stf = [[SingletonTimeFormat alloc]init];
if([stf.timeFormat isEqualToNumber: [NSNumber numberWithInt:12]]) {
which returns 0 (it was set correctly in AppDelegate, but apparently when I do the alloc in another class, it loses it's value. So obviously it's not working! (I have several other singletons that have the same pattern, but so far they appear to be working.
What's wrong here and how do I fix it?
You don't want to call your singleton using alloc init. With this singleton, all references to it should be through its sharedTimeFormat method, which will init the object if necessary, and will return the singleton instance otherwise.
In other words, it doesn't appear that you're referencing the instance of the object stored in the static sharedObject variable, which means that it's stored value will not necessarily be the same.

Trouble with NSString and copy attribute

I'm learning Objective C with the book "Programming with Objective C", but I'm stuck with the exercises 3 on page 66.
I have the following properties:
#property (copy, readonly) NSString *lastName;
#property (copy, readonly) NSString *firstName;
the following methods:
+ (id) personWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
return [[self alloc] initWithLastName:aLastName andFirstName:aFirstName;
}
- (id) initWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
self = [super init];
if (self) {
_lastName = aLastName;
_firstName = aFirstName;
}
return self;
}
- (void) sayHello {
NSString *greeting = [NSString stringWithFormat:#"Hello, %# %#!", [self firstName], [self lastName]];
[self saySomething:greeting];
}
and the following code in the main:
NSMutableString *firstName = [NSMutableString stringWithString:#"Steve"];
NSString *lastName = #"Jobs";
XYZPerson* firstPerson = [XYZPerson personWithLastName:lastName andFirstName:firstName];
[firstPerson sayHello];
which correctly print "Hello, Steve Jobs!".
Later, I added the following lines:
[firstName setString:#"John"];
[firstPerson sayHello];
which edit the property firstName in the object firstPerson, and print "Hello, John Jobs!".
However, firstName have the copy attribute, so why this happen?
Well let's break this down...
This property says that we want only an accessor to be generated (the readonly part) plus we take ownership of the object via copy upon assignment (which in this case is never gonna happen because of the readonly)
#property (copy, readonly) NSString *lastName;
So a better form would be (unless you're interested in redeclaration of the property as readwrite in a class extension, like in this answer):
#property (readonly) NSString *lastName;
and now we copy inside our init method before assignment to the backing ivars:
- (id) initWithLastName:(NSString *)aLastName andFirstName:(NSString *)aFirstName {
self = [super init];
if (self) {
_lastName = [aLastName copy];
_firstName = [aFirstName copy];
}
return self;
}
and of course the same applies to your other property also.
The firstName and Lastname you are using in your main method are not the properties you declared, but instead new instance variables. To access the properties you want you can use _firstName to use the ivar or self.firstName to use the property's setter and getter methods.
EDIT: Disregard the above. Because you are using the ivar directly instead of calling the accessor method, the copy is not being made -- you have to call copy yourself.

How to init objects in Objective-C [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I'm unsure how I should initialise the various properties in an objective-C class. Please assume I'm a very new user to Objective-C in your answers...
I have the following classes:
Test class
#interface Test : NSObject
#property (nonatomic, strong) NSString *name;
#end
TestManager class
#interface TestManager : NSObject
#property (nonatomic, strong) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
Controller class
#interface TestController : NSObject
#property (nonatomic, strong) TestManager *aManager;
-(void)initManager;
-(void)doSomething;
#end
I want to have an method like initManager called:
-(void)initManager
{
// how can I init the aManager which will have an array of Test objects
}
which will automatically allocate an array of objects to be stored inside the manager class so I can do things like:
-(void)doSomething
{
NSString *name = ((Test *)[self.aManager.tests objectAtIndex:0]).name;
}
I'm not even sure that initManager is the correct method to use - is there something built in that always gets called?
Firstly, let's look at the way we can initialize your Test class objects.
You can also write some initialization method for your Test class so instead of this:
Test example = [[Test alloc] init];
example.name = #"s";
you can write something like this:
Test example = [[Test alloc] initWithName:#"s"];
Please note that this is very common for initialization method to return newly created object, hence the initialization method usually returns 'id' type (not void).
This is the implementation for your test class which will be used in examples below.
.h file:
- (id)initWithName:(NSString *)aName;
.m file:
- (id)initWithName:(NSString *)aName
{
self = [super init];
if (self) {
_name = aName;
}
return self;
}
You can initialize your TestController class this way:
.h file:
- (id)initManager;
.m file:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSMutableArray array];
[_tests addObject:[[Test alloc] initWithName:#"s"]];
[_tests addObject:[[Test alloc] initWithName:#"l"]];
}
return self;
}
Or something like this:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSArray arrayWithObjects:[[Test alloc] initWithName:#"s"], [[Test alloc] initWithName:#"l"]];
}
return self;
}
Like the #Andrew said it is better to use alloc + init. Here are some examples of this syntax:
CGRect rect = CGRectMake(0, 0, 100, 100);
[[UIView alloc] initWithFrame:rect];
[[NSArray alloc] init]
This is the common way to initialize objects. Despite having this mechanism there are also some additional methods (which are in fact static functions) which give the programmer the nice way to initialize objects. Using them u don't have to write keyword 'alloc' so that the code is shorter and easier to read.
[NSArray array] //creates and returns empty array
[NSMutableArray array] //creates and return empty mutable array
[UIButton buttonWithType:UIButtonTypeContactAdd]; //creates and return button
first import header files of test, and test manager class, into controller class
#import Test.h
#import TestManager.h
then in controller class
-(void)initManager
{
TestManager *aTestManager = [TestManager new];
Test *test1 = [Test new];
Test *test2 = [Test new];
[aTestManager.tests addObject:test1];
[aTestManager.tests addObject:test2];
}
Let's start at the top. You probably can and should make the name readonly.
(Demos assume ARC is enabled)
#interface Test : NSObject
#property (nonatomic, readonly) NSString *name;
// and then simply initialize name:
- (instancetype)initWithName:(NSString *)pName;
#end
NSString properties should be copied:
#implementation Test
- (instancetype)initWithName:(NSString *)pName
{
self = [super init];
if (nil == self) return nil;
// copy the NSString:
// don't use getters/setters in initializers or -dealloc
_name = pName.copy;
return self;
}
#end
Similarly readonly:
#interface TestManager : NSObject
#property (nonatomic, strong, readonly) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
#implementation TestManager
- (id)init
{
self = [super init];
if (nil == self) return nil;
// just initialize readonly tests:
_tests = NSMutableArray.new;
return self;
}
#end
Then TestController could probably use a readonly TestManager and borrow the form used above. Otherwise, it can be readwrite, if needed.
// don't declare/implement an instance method
// which has the prefix -init*, like initManager. renamed.
- (void)resetManager
{
// if readonly is ok, then just create it in the initializer.
// otherwise, if you need the ability to set the manager in the controller,
// then declare the property readwrite and:
self.testManager = TestManager.new;
// note: aManager is not a good name. renamed to testManager.
}
- (void)doSomething
{
assert(self.testManager && "did you forget to init the manager?");
Test * test = [self.testManager.tests objectAtIndex:0];
NSString * name = test.name;
...
}
This is far from covering all initialization cases in ObjC, but it is a start.

Why need I add self when assign attibute for one model?

I have one model.
I want to update one attribute each 3 seconds after init, and I need to change the attribute in one other view controller later.
the code is:
MODEL
#interface Ap : NSObject
#property (nonatomic, retain) NSString *address;
#property (nonatomic, retain) NSString *sessionId;
+ (id)sharedInstance;
#end
#implementation Ap
#synthesize sessionId, address;
-(id) init {
if (self = [super init]) {
self.address = nil;
self.sessionId = nil;
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
return self;
}
-(void) updateSession{
sessionId = #"some string";
}
- (void) update{
self.sessionId = #"some value from network";
}
+ (Ap *)sharedInstance {
if (!sharedInstance) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
#end
CONTROLLER
- (void) viewDidLoad {
[super viewDidLoad];
[[Ap sharedInstance] updateSession];
}
The error is: (lldb) bad access when model update
And I change the updateSession method to
-(void) updateSession{
self.sessionId = #"some string";
}
The error is gone, can anyone tell me why?
You should use self.ap = [[Ap alloc] init]; in your app delegate. Your model object might be getting released when you try to call update on that which might cause a crash.
Your code will look like this,
#interface SomeAppDelegate : UIResponder{
#property(nonatomic, retain) Ap *ap; //declare property here
#end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if(!self.ap){
self.ap = [[Ap alloc] init]; //use property here as self.ap which will retain ap. Otherwise it will not retain it.
}
}
Update:
Looks like you have removed the previous question and added a completely new question to the previous one. Anyways I will try to answer this one as well.
The error is gone, can anyone tell me why?
The reason is same as what I mentioned above. If you use self.sessionId = #"some string";, you are retaining the object. Because it is a property and a property will internally retain since you have declared that property as retain. But if you use sessionId = #"some string";, its scope is only inside that method since you are not using the property. You are directly setting the value without calling property and it will autoreleased after that method. And hence you will get a bad access.
I would suggest you to go through the apple documentation to understand more about properties and its working.