In objective C lazy instantiation, why don't we touch the setter? - objective-c

In objective C, its common practice to instantiate internal class arrays (and the like) in a lazy manner.
So if you call on the getter, it first checks if the array isn't nil, and allocates memory for it if needed.
But what about the setter?
If you are trying to insert some value into one of the array cells, since we did not allocate memory for it yet - where does it go?
I'm missing something here, clearly. Would be happy for a clarification.

I'm not sure I understand your question, but if you do this:
#property (nonatomic, strong) NSMutableArray* myArray;
...
- (NSMutableArray *) myArray {
if(!_myArray) {
NSLog(#"created");
_myArray = [[NSMutableArray alloc] init];
}
return _myArray;
}
...
[self.myArray addObject:#"test"];
The getter is actually getting called when you call addObject:, so you'll see "created" being logged.

So #property declarations are syntactic sugar for declaring, in the case of objects, pointers to instance variables. The "nonatomic" refers to the type of getter and setter automatically created (in this case "non thread safe.") And the "strong" is an indicator to ARC to increase the retain count of the variable.
So when you declare:
#property (nonatomic, strong) NSMutableArray* myArray;
This is what really gets created in your class - just a pointer to your hidden instance variable.
#implementation MyClass {
NSMutableArray *_myArray;
}
As you can see in the getter, you are initializing the _myArray pointer to point to a new NSMutableArray:
- (NSMutableArray *) myArray {
if(!_myArray) {
NSLog(#"created");
_myArray = [[NSMutableArray alloc] init];
}
return _myArray;
}
However in the setter, you are just updating the pointer to a variable you have already created.
self.myArray = [[NSMutableArray alloc] init];
This sends your class the following message:
- (void) myArray: (NSMutableArray *) myArray {
_myArray = myArray;
}
As you can, see the setter doesn't need any special initialization most of the time. The only time you want to create a custom setter is when you want to validate the incoming object has special properties. A contrived example is checking that the NSMutableArray is no larger than 10 objects:
- (void) myArray: (NSMutableArray *) myArray {
if (myArray.count < 10) {
_myArray = myArray;
}
}
Finally, I would like to point out that you can actually lazy instantiate objects using the short ternary operator and parenthetical return values. For example, the following statement:
- (NSMutableArray *) myArray {
return (_myArray = _myArray ?: #{}.mutableCopy);
}
Is equal to:
- (NSMutableArray *) myArray {
if(!_myArray) {
_myArray = [[NSMutableArray alloc] init];
}
return _myArray;
}
You can even macro this pattern into (WSM is my class prefix):
#define WSM_LAZY(object, assignment) (object = object ?: assignment)
So you can write statements like this:
- (NSMutableArray *) myArray {
return WSM_LAZY(_myArray, #{}.mutableCopy);
}
Or even use compound statement syntax to rewrite the original setter you presented as an example:
- (NSMutableArray *) myArray {
return WSM_LAZY(_myArray, ({
NSLog(#"created");
#{}.mutableCopy;
}));
}

Related

NSArray error: initializer element is not constant

I declared this array in my view controller implementation file:
NSMutableArray *images = [NSMutableArray array];
I wanted an empty, mutable array that I would later add UIImageViews to. It always return the error:
initializer element is not constant
The proper solution is to make images an instance variable and then you initialize it in your init method.
#implementation SomeClass {
NSMutableArray *images; // instance variable
}
- (id)init {
self = [super init];
if (self) {
images = [[NSMutableArray alloc] init];
}
return self;
}
This is an example. If you have a specific init... method, use that instead.
As an instance variable, other methods in the class now have access to images and each instance of the class gets its own copy of images.
You need to show more code, but the problem is pretty obvious if that really is the line that is erroring out.
You can only dynamically initialization variables at the time of declaration in very specific spots. Dynamically includes calling a method.
NSMutableArray *a = [NSMutableArray array]; // this will error.
static NSMutableArray *a = [NSMutableArray array]; // this will error.
#implementation Booger
{
NSMutableArray *a = [NSMutableArray array]; // this will error
}
NSMutableArray *a = [NSMutableArray array]; // this will error.
- (void)bar
{
NSMutableArray *a = [NSMutableArray array]; // this is fine
}
Sounds like you need to dive a bit more deeply on the whole object-oriented thing. A class is a collection of functions called methods that either operate on the class (class methods) or a single instance of the class (instance methods). An instance can store state that is accessible to all methods when any method is invoked on that instance. In traditional OO, such state is stored in instance variables. Typically, you would define a pair instance methods that set and get that instance variable's value. These are called accessors or setter/getter. In modern Objective-C, we use properties to declare both the instance variables and the methods that access the instance variable.
Thus:
#interface MyClass:NSObject
#property(strong) NSMutableArray *myArray;
#end
#implementation MyClass
// the #property will automatically create an instance variable called _myArray,
// a getter method called -myArray and a setter called -setMyArray:
- init
{
self = [super init];
if (self) {
_myArray = [NSMutableArray array]; // set the ivar directly in init
}
return self;
}
- (void)maybeAddThisThing:(Thing *)aThing
{
if ([aThing isCool] && ![[self myArray] containsObject:aThing]) {
[[self myArray] addObject:aThing];
}
}
- (void)nukeFromOrbit
{
[self setMyArray:[NSMutableArray array]];
// or you could do [[self myArray] removeAllObjects];
}
The return of your NSMutableArray construction does not have an address known at compile time. You can only initialise dynamically inside method scope.
Static initialisation would be fine though: For example, NSString *myString = #"Hello String"; in global scope will compile just fine.

NSArray getter method not returning right value

I'm making a program where one Class (classA) generates a random number and adds it to a mutable array. A view controller (viewControllerA) calls a method from classA and receives the array of random numbers and stores it in its own array.
I have another class (classB) that needs the same array. After viewcontrollerA is finished doing what it needs to do with the array, it calls the setter method for an array in classB. I call NSLog in the setter and getter methods for the array in classB to check to see if it loads.
-(void)setRandomNumberArray:(NSArray *)randomNumberArray{
_randomNumberArray = randomNumberArray;
NSLog(#"%# setter", _randomNumberArray);
}
-
-(NSArray *)randomNumberArray{
if (!_randomNumberArray) {
_randomNumberArray = [[NSArray alloc] init];
}
NSLog(#"%# getter", _randomNumberArray);
return _randomNumberArray;
}
When I call the setter method in viewControlerA, NSLog returns the value of the array.
When I call the getter method in viewControllerB, NSLog prints nothing from the getter method.
2012-05-29 23:57:43.589 SwipeGame[8603:f803] (
) getter
It's obviously setting the array but not retaining it for when i want to get it. What is going on? I've tried multiple other techniques and it always sets the array but doesn't retain it for when i want to "get" the array.
the property for my array is set to retain btw..
UPDATE:
Yes I am using ARC. my property declaration is:
#property (nonatomic, strong) NSArray *randomNumberArray
SOLVED:
Thanks for all your help! It was a problem with instances.
Your setter method does not mention viewControllerB. You are just setting an internal variable. How is viewControllerB going to know about the array having been set?
The easiest way is to just use #properties and #synthesize:
// in A
viewControllerB.array = _array;
As for the retain question: if you use ARC you should not worry about it.
Do you use ARC?
ARC version bellow:
#interface Foo : NSObject {
NSMutableArray *_randomNumberArray;
}
#property (nonatomic, strong) NSMutableArray *randomNumberArray;
#end
#implementation Foo
#synthesize randomNumberArray = _randomNumberArray;
- (void)setRandomNumberArray:(NSMutableArray *)randomNumberArray {
_randomNumberArray = randomNumberArray;
NSLog(#"%# setter", _randomNumberArray);
}
- (NSMutableArray *) randomNumberArray {
if ( _randomNumberArray == nil )
_randomNumberArray = [[NSMutableArray alloc] init];
NSLog(#"%# getter", _randomNumberArray);
return _randomNumberArray;
}
#end
Not ARC version bellow:
#interface Foo : NSObject {
NSMutableArray *_randomNumberArray;
}
#property (nonatomic, strong) NSMutableArray *randomNumberArray;
#end
#implementation Foo
#synthesize randomNumberArray = _randomNumberArray;
- (void)setRandomNumberArray:(NSMutableArray *)randomNumberArray {
[_randomNumber release];
_randomNumberArray = randomNumberArray;
[_randomNumberArray retain];
NSLog(#"%# setter", _randomNumberArray);
}
- (NSMutableArray *) randomNumberArray {
if ( _randomNumberArray == nil )
_randomNumberArray = [[NSMutableArray alloc] init];
NSLog(#"%# getter", _randomNumberArray);
return _randomNumberArray;
}
- (void)dealloc {
[_randomNumberArray release];
}
#end
If you are creating this random number array using an NSMutableArray, and passing that to the setter, the array could be mutated later by the caller (e.g. all items removed) and the array can change from under your feet.
For types like NSArray and NSString which have mutable subtypes, you should declare your property as copy instead of strong. This will ensure the array passed to you cannot be mutated at a later date by somebody else. Copy performance is not a problem because the regular immutable types handle copies very efficiently.

Memory management technique for Objective-C iVars/properties

Is the following code doing anything unnecessary?
#interface MyClass {
NSArray *myArray;
}
-(void)replaceArray:(NSArray *)newArray;
#implementation MyClass
-(void)replaceArray:(NSArray *)newArray {
if( myArray )
{
[myArray release];
myArray = nil;
}
myArray = [[NSArray alloc] initWithArray: newArray];
}
#end
What if I made the following changes:
1) Made myArray a property:
#property (nonatomic, retain) NSArray myArray;
2) Changed the assignment to:
self.myArray = [NSArray arrayWithArray: newArray];
Would that allow me to remove the conditional?
You don't need the conditional at all; you can message nil (including a release), and nothing will happen. You also don't need to allocate a new array; you can retain the one passed to you instead. If you're worried about actually getting an NSMutableArray, you can make a copy. I'd do this:
- (void)replaceArray:(NSArray *)newArray
{
[myArray autorelease];
myArray = [newArray copy];
}
Or, if you don't want to use autorelease, you could do:
- (void)replaceArray:(NSArray *)newArray
{
if (myArray != newArray) {
[myArray release];
myArray = [newArray copy];
}
}
You can already get rid of the conditional. If the array is nil, then you'll be sending a message to nil, which is a no-op. The assignment to nil is pointless either way as well. And if you make it a retain property, explicitly releasing the old value is wrong.
However, there is one case where that code will not work correctly: When the argument is the current value. In that case, you'll release the current value and then try to use the released object (which may already have been dealloced) to create a new array.
Imaging the following:
MyClass * myObj;
// init myObj
NSArray * array = [myObj myArray];
[myObj replaceArray:array];
In this case, myArray and newArray are the same, which means you're using it after it being released. To solve this problem, all you need to do is remove the replaceArray: method, and implement the property as #synthesize myArray. So the above code changes to
MyClass * myObj;
// init myObj
NSArray * array = [myObj myArray];
[myObj setMyArray:array];
and your problem is solved by the synthesized implementation.
Note that you are setting your value by creating a new array:
myArray = [[NSArray alloc] initWithArray: newArray];
if this is the behaviour you want, you should change your property definition to copy instead of retain:
#property (nonatomic, copy) NSArray myArray;
I've voted up mipadi because his answer is right in the context of the question you asked, but why not just use a property and do away with replaceArray: altogether:
#interface MyClass {
NSArray *myArray;
}
#property (copy) NSArray* myArray;
#end
#implementation MyClass
#synthesize myArray;
-(void) dealloc
{
[myArray release];
[super dealloc];
}
#end

Question on retain attribute with #property and #synthesize

I'm still pretty new to Objective-C coding (as evidenced by this question) and I think I'm not completely understanding how using the retain attribute in a #property declaration works.
Here is an example class:
#interface Foo : NSObject {
NSMutableArray *myArray;
}
#property (retain) NSMutableArray *myArray;
My understanding was that adding the retain attribute to the #property declaration (and using the necessary #synthesize delcaration in the implementation file) will basically do the following setter and getter for me:
- (void)setMyArray:(NSMutableArray *)newArray {
myArray = [[NSMutableArray alloc] initWithArray:newArray];
[newArray release];
}
- (NSMutableArray *)myArray {
return myArray;
}
Is this accurate or am I mistaken on how the retain attribute works?
Adding the retain attribute will actually generate this code:
- (void)setMyArray:(NSMutableArray *)newArray {
[newArray retain];
[myArray release];
myArray = newArray;
}
- (NSMutableArray *)myArray {
return myArray;
}
The reason the retain method is called on newArray before release on the old value is that if newArray and myArray are the same object, the array will be released before it is retained again.
It's really hard to do it right. Take a look at the article Memory and thread-safe custom property methods on Cocoa with Love by Matt Gallagher.
Here's one implementation that works, heavily inspired by that excellent article.
- (void)setSomeString:(NSString *)aString {
#synchronized(self)
{
if (someString != aString) // not necessary, but might improve
// performance quite a bit
{
[aString retain];
[someString release];
someString = aString;
}
}
}
- (NSString *)someString {
#synchronized(self)
{
id result = [someString retain];
}
return [result autorelease];
}
retain will not do a copy of the new value. It will retain the new value and release the old one.

Accessing collection through KVC (to protect collection and be KVO compliant)

I have a class Test which has an array of Foos. I want to provide access to the Foos without exposing the ivar directly. I'm trying to make this KVC compliant (also to pave the way for KVO compliance). I have:
Test.h
#interface Test : NSObject
{
NSMutableArray *foos;
}
#property (readonly, copy) NSMutableArray *foos;
#end
Test.m
- (id) init
{
self = [super init];
if (self != nil)
{
foos = [[NSMutableArray array] retain];
}
return self;
}
- (NSMutableArray*) foos
{
return [self mutableArrayValueForKey:#"foos"];
}
- (NSUInteger)countOfFoos
{
return [foos count];
}
- (id)objectInFoosAtIndex:(NSUInteger)index
{
return [foos objectAtIndex:index];
}
- (NSArray *)foosAtIndexes:(NSIndexSet *)indexes
{
return [foos objectsAtIndexes:indexes];
}
- (void)insertObject:(id)key inFoosAtIndex:(NSUInteger)index
{
[foos insertObject:key atIndex:index];
}
- (void)insertFoos:(NSArray *)foosArray atIndexes:(NSIndexSet *)indexes
{
[foos insertObjects:foosArray atIndexes:indexes];
}
- (void)removeObjectFromFoosAtIndex:(NSUInteger)index
{
[foos removeObjectAtIndex:index];
}
- (void)removeFoosAtIndexes:(NSIndexSet *)indexes
{
[foos removeObjectsAtIndexes:indexes];
}
This enters an infinite loop when a client tries to add a Foo:
Test *test = [[Test alloc] init];
NSMutableArray *foos = test.foos;
[foos addObject:#"adding object"]; // infinite loop here
What am I doing wrong?
- (NSMutableArray*) foos
{
return [self mutableArrayValueForKey:#"foos"];
}
An accessor should not use KVC to get the value of the property being accessed; the idea is that KVC goes through the accessors, because the accessors are closer to the value than KVC is.
The correct implementation of foos should return a copy, mutable or otherwise, of the array. Here's how I'd do it:
- (NSArray *) foos
{
return [[foos copy] autorelease];
}
I would also make all of the accessors public. Anything that wants to mutate the array or randomly access elements at specific indexes can do so that way. It's still safe and encapsulated because they're going through your accessors, not directly accessing the array.
There's not really any reason to use the KVC protocol methods yourself unless you don't know what key you'll access at the time you write the code. For example, if you were writing the nib loader or the Cocoa Bindings system, you would use KVC.
The problem is that the proxy NSMutableArray returned by mutableArrayValueForKey: first has to get the real array, which it does through the "foos" method. Since that's the one that returns a proxy NSMutableArray it enters an infinite loop. One solution is to use another name:
- (NSMutableArray*) mutableFoos
{
return [self mutableArrayValueForKey:#"foos"];
}
I spent a very long time on this problem and wanted to get this through an accessor. I wanted to clarify in the answer for those coming in. This is what I did:
#property (nonatomic,readonly,getter=getTheFoos) NSMutableArray* foos;
Then obviously implemented:
- (NSMutableArray*)getTheFoos {
return [self mutableArrayValueForKey:#"foos"];
}
Had to be careful though, getFoos appears to be an (undocumented) KVC accessor, because this sends it into the same loop.
Then onto KVO:
Test* test= [[Test alloc] init];
NSObject* obj= [[NSObject alloc] init];
NSMutableArray* arrTheData= test.foos;
[test.foos insertObject:obj atIndex:0];
[arrFoos insertObject:obj atIndex:0];
arrFoos can read the updated, mutated array (it will have two objects in it), but inserting into it will not fire KVO. Somewhere on my adventures, I saw that the return from mutableArrayValueForKey: doesn't return an NSMutableArray*, but a subclass of it, which might be the cause of it.