I'm trying to create a getter for a property - for the time being I'm just using the method to build up an NSMutableObject from static arrays but eventually these will be dynamic config settings. In a previous app and from getter and setters not working objective c I did this:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic) NSMutableDictionary *questions;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[self questions] setValue:#"FOO" forKey:#"bar"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
+ (NSMutableDictionary *)questions
{
static NSMutableDictionary *_questions;
if (_questions== nil)
{
NSArray *genders = #[#"male", #"female"];
NSArray *ages = #[#"<10", #">60", #"Other"];
_questions = [[NSMutableDictionary alloc] init];
[_questions setValue:genders forKey:#"gender"];
[_questions setValue:ages forKey:#"age"];
}
return _questions;
}
When I get to the line in viewDidLoad where I try to use the 'questions' property, it doesn't use the custom getter (it just assigns bar:FOO to a nil dictionary). What am I missing?
Try changing the
+ (NSMutableDictionary *)questions
To
- (NSMutableDictionary *)questions
You may also want to change the return type to NSDictionary * to prevent the static variable from being mutated, or return _questions.copy or _questions.mutableCopy
The reason your custom questions method is not being called through setValue:forKey: is that it is a class method:
+ (NSMutableDictionary *)questions is a method defined on the ViewController class, while the property accessor (which setValue:forKey: is looking for) is defined on the instance.
To access your custom method as it is currently defined call the class method:
[[[self class] questions] setValue:#"FOO" forKey:#"bar"];
This may not have the effect you intend, as this value will be shared across all instances of the class.
Related
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.
I'm running into some trouble implementing a simple super/sub class scheme. I declare an NSMutableDictionary in the superclass, and am trying to access it in a subclass, but it only returns null. Any help would be appreciated.
#interface RootModel : NSObject <Updatable, TouchDelegate>
#property (nonatomic) NSMutableDictionary *gameValues;
#end
#interface SubclassModel : RootModel
#end
#implementation RootModel
- (id)initWithController:(id)controller {
if ((self = [super init])) {
_controller = controller;
_gameValues = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:300.0f], KEY_JUMP_VELOCITY,
[NSNumber numberWithFloat:49.47f], KEY_GRAVITY,
[NSNumber numberWithFloat:0.25f], KEY_JUMP_TIME_LIMIT,
nil];
NSLog(#"%#", _gameValues);
}
return self;
}
#implementation SubclassModel
- (id)init{
if ((self = [super init])) {
// This NSLog prints "(null)" if using super.gameValues or self.gameValues, why?
NSLog(#"subclass: %#", super.gameValues);
}
return self;
}
#end
What am I doing wrong?
As Catfish_Man answered, your init method needs to call [super initWithController:]. However you seem to show a misunderstanding of the class/super class inheritance model with your comment:
My superclass is initialized by another controller class. Any calls to the super's properties (which were initialized in initWithController:) are valid (they return values, not null).
When you create an instance of your SubclassModel then that instance has as part of itself a RootModel instance. That RootModel instance is not shared with any other instance of SubclassModel or RootModel.
So if "another controller class" creates and initialises and instance of RootModel, which in turn displays your NSLog output, then that is a totally different object to your SubclassModel instance - and the RootModel that is part of your SubclassModel instance is not initialised, as you don't call [super initWithController:], hence you NSLog in SubclassModel shows nil.
Your subclass init method needs to call [super initWithController:], since that's where the actual initialization happens.
(or the superclass initWithController: needs to call [self init], and you need to move the initialization work you're relying on to init)
I have a subclass of UIViewController with an NSMutableArray as a property to use as the data source for a UITableView. I create an instance of the class in my storyboard.
I want to populate the array with the addObject: method but if I try, the array always returns (null).
I read that #synthesize doesn't init the array and I might need to override -init and init the NSMutableArray there but -init never gets called.
How is this supposed to work?
You need to create an instance of NSMutableArray and assign it to the property.
Since the object with the property is a UIViewController created in a storyboard, you can do it in a few different places. You can override initWithCoder:, or awakeFromNib, or viewDidLoad.
If you override initWithCoder:, it is imperative that you call the super method.
If you do it in viewDidLoad, the array won't be created until the view is loaded, which doesn't have to happen right away.
I recommend doing it in awakeFromNib:
#synthesize myArray = _myArray;
- (void)awakeFromNib {
_myArray = [[NSMutableArray alloc] init];
}
Another option is to just create the array lazily by overriding the getter method of the property:
#synthesize myArray = _myArray;
- (NSMutableArray *)myArray {
if (!_myArray)
_myArray = [[NSMutableArray alloc] init];
return _myArray;
}
If you do this, it is very important that you always access the array using the getter method (self.myArray or [self myArray]) and never by accessing the instance variable (_myArray) directly.
Here's what your code will need:
#interface BlahBlah : UIViewController
#property (...) NSMutableArray *myArray;
#end
At first, *myArray is just a pointer that's equal to nil. You can't use this as an array yet. It needs to be set to a valid NSMutableArray instance. You need to do this in the designated initializer:
#implementation BlahBlah
#synthesize myArray;
// -initWithNibName:bundle: is the designated initializer for UIViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self) {
myArray = [[NSMutableArray alloc] init];
// now you can add objects to myArray
}
return self;
}
- (void)dealloc
{
[myArray release];
[super dealloc];
}
Note that you can't just override -init; you must override the designated initializer for your class. Figure out what subclass you are implementing, find out what its designated initializer is, override that (but call the superclass implementation as is often necessary), then init your properties.
Here's Apple documentation regarding multiple/designated initializers.
#synthesis neither do alloc. So alloc and init your array. Then it should work.
I'm trying to add a convenience constructor to my custom object.
Similar to [NSArray arrayWithArray:]
I know it involves a class method that returns an auto released object. I've been googling around but all I can seem to find is the definition of a convenience constructor but not how to write one.
Let's say you have the following:
#class PotatoPeeler : NSObject
- (instancetype)initWithWidget: (Widget *)w;
#end
Then to add a factory method, you'd change it to this:
#class PotatoPeeler : NSObject
+ (instancetype)potatoPeelerWithWidget: (Widget *)w;
- (instancetype)initWithWidget: (Widget *)w;
#end
And your implementation would simply be:
+ (instancetype)potatoPeelerWithWidget: (Widget *)w {
return [[[self alloc] initWithWidget: w] autorelease];
}
Edit: replaced id with instancetype. They are functionally identical, but the latter provides better hints to the compiler about the method's return type.
Generally my approach is the following: first I create a normal initializer method (instance method), then I create a class method that calls the normal initializer. It seems to me Apple uses the same approach most of the time. An example:
#implementation SomeObject
#synthesize string = _string; // assuming there's an 'string' property in the header
- (id)initWithString:(NSString *)string
{
self = [super init];
if (self)
{
self.string = string;
}
return self;
}
+ (SomeObject *)someObjectWithString:(NSString *)string
{
return [[[SomeObject alloc] initWithString:string] autorelease];
}
- (void)dealloc
{
self.string = nil;
[super dealloc];
}
#end
I'm having trouble creating a nice way of passing a collection around to different view controllers. For example, I created a custom class called Message with a bunch of attributes. I want to have a global NSMutableArray of those stored in a global variable of sorts called messages that I can add to or get from anywhere. Everyone on Stackoverflow says not to use your delagate class to store global variables so I created a singleton class called Shared. In there I created a property for the NSMutableArray called messages like this:
#interface Shared : NSObject {
}
#property (nonatomic, retain) NSMutableArray *messages;
+(Shared *) sharedInstance;
#end
And my .h file is (the important part):
#import "Shared.h"
static Shared* sharedInstance;
#implementation Shared
#synthesize messages;
static Shared *sharedInstance = nil;
-(id) init {
self = [super init];
if (self != nil){
}
return self;
}
-(void) initializeSharedInstance {
}
+ (Shared *) sharedInstance{
#synchronized(self) {
if (sharedInstance == nil){
sharedInstance = [[self alloc] init];
[sharedInstance initializeSharedInstance];
}
return (sharedInstance);
}
}
In my other view controller, I first import "Shared.h", then try this:
[[Shared sharedInstance].messages addObject:m];
NSLog([NSString stringWithFormat:#"Shared messages = %#", [Shared sharedInstance].messages]);
It keeps printing null instead of the the collection of m objects. Any thoughts?
You need to have a static variable.
In .h:
#interface Shared : NSObject
{
NSMutableArray *messages;
}
#property (nonatomic, retain) NSMutableArray *messages;
+ (Shared*)sharedInstance;
#end
in .m:
static Shared* sharedInstance;
#implementation Shared
#synthesize messages;
+ (Shared*)sharedInstance
{
if ( !sharedInstance)
{
sharedInstance = [[Shared alloc] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if ( self )
{
messages = [[NSMutableArray alloc] init];
}
return self;
}
A thought:
#synthesize generates setter and getter methods, it doesn't init your variable. Where do you do that? I can't see it in the excerpts you posted.
The following is not an answer to your issue, but instead a suggestion to an alternative approach that (in my opinion) is 'cleaner' in use.
An alternative to using a Singleton to store app-wide could be to define a class with class methods that retrieves values from the NSUserDefaults. This class could be imported into the prefix header (*.pch) so you can access it from every other class in the project.
Methods inside this class could look like this:
inside Settings.h:
// for this example I'll use the prefix for a fictional company called SimpleSoft (SS)
extern NSString *kSSUserLoginNameKey;
+ (NSString *)userLoginName;
+ (void)setUserLoginName:(NSString *)userLoginName;
inside Settings.m:
kSSUserLoginNameKey = #"SSUserLoginNameKey";
+ (NSString *)userLoginName
{
return [[NSUserDefaults standardUserDefaults] valueForKey:kSSUserLoginNameKey];
}
+ (void)setUserLoginName:(NSString *)userLoginName
{
[[NSUserDefaults standardUserDefaults] setValue:userLoginName forKey:kSSUserLoginNameKey];
[[NSUserDefaults standardUserDefaults] synthesize];
}
Of course in a setup like this NSUserDefaults is the singleton that is being accessed through a convenience class. This class acts as a wrapper around the NSUserDefaults singleton. Values can be accessed like this:
NSString userLoginName = [Settings userLoginName];
[Settings setUserLoginName:#"Bob"];
Other objects -like Arrays- could be accessed in much the same way. One thing to be careful with (much the same as with your current approach) is to be careful not to access a class like this from every other class. Components that are intended to be reusable should pass values, so the components of the application don't become too tightly coupled to the settings class.