Why is my array property being discarded after method execution? - objective-c

Edit: the first answer has guessed it is because I am not using a protocol to "pass data backwards." I am currently implementing that and we'll see if it fixes it.
I'm very new to iOS/objective-C. I have this property on my class:
#interface GameController
#property (strong, nonatomic) NSMutableArray* targets;
#end
I was then using addObject on the array in the body of one of the GameController methods, and it didn't throw any errors, so I thought it was okay until I checked [_targets count] at the end of that method and found it was zero. So I initialized it at the beginning of that method:
if(self.targets == nil){
self.targets = [[NSMutableArray alloc] init];
}
And then count was 6. But as soon as I go to read self.targets in a different method, it's back to zero. Why is that happening? What am I doing wrong with initializing this?
Edit: Interestingly, in the other method, if I check, it is no longer nil-- it has been initialized. But it is nonetheless empty. Very confused!
Edit2: Okay, I've uploaded the code to pastebin, having cut out irrelevant stuff as much as possible.
So drawMainControls gets called first, when the app loads, and it draws a bunch of TileViews and TargetViews, instantiating self.targets in the process. Each TileView has a GameController property which is assigned to be the main GameController immediately after instantiation.
The TileView detects taps and calls addPossibleLetter on its controller. addPossibleLetter is the method that checks the self.targets array and comes up empty-handed.
Here is the GameController.h file: http://pastebin.com/C30Mi2LL
Here is the GameController.m file: http://pastebin.com/cvf2WmBs
Here is the TileView.m file: http://pastebin.com/gehJGYBD

There are fair chances that you are calling the other method before the method in which this method is being initialized.
I guess when passing data backward the way you are doing you are properly aware of using protocols and delegates. You can have look at his link which explains this concept very clearly. Passing Data between View Controllers

Related

Objective-C Why use init?

I'm reading my first book on Objective-C [Programming in Objective-C 4th Edition], I'm midway through the book but one thing that bugs me, is that it didn't explain WHY we initialize objects.
I tried playing around with the with objects, for example allocating their memory but not initiating them and everything in the program works the same as before.
I'd appreciate some example explaining this, also.
The code within an init method is class specific - it performs whatever initialisation is required for that specific class. There are cases where a class does not need to perform any initialisation and thus removing this method call would have no effect.
However, by convention, you should always use init - what if someone were to add some required initialisation code to a class in the future?
See also:
alloc and init what do they actually do
To address you point of "everything works", the interesting thing about Objective-C is that alloc sets all instance variables to nil, and sending messages to nil doesn't do anything, it just returns nil, so in most of the cases you will not see a problem until you would try to do something illegal, consider a class like this
#interface MyObject : NSObject
#property (nonatomic, strong) NSString *string;
#end
#implementation MyObject
#end
Now if we'd just alloc it as:
MyObject *m = [MyObject alloc];
the instance variable _string, or property string would be nil, we could send different messages to it, like [string length] without any harm, since message to nil equals nil.
But say we then want to add this string to array, like
#[m.string]
Now you would get a exception, because NSArray cannot contain nil, only full blown objects. You can easily fix this by initializing your value inside MyObject.init.
Pretty contrived example, but hopefully shows the point of why everything doesn't break when you don't init :)
One of the main reasons why you should never use alloc's return value directly instead of using [[Class alloc] init]'s return value is that init might return a different object than alloc.
Apple's documentation mentions this:
Note: It’s possible for init to return a different object than was
created by alloc, so it’s best practice to nest the calls as shown.
Never initialize an object without reassigning any pointer to that
object. As an example, don’t do this:
NSObject *someObject = [NSObject alloc];
[someObject init];
If the call to init returns some other object, you’ll be left with a
pointer to the object that was originally allocated but never
initialized.
Source: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithObjects/WorkingwithObjects.html

Unable to access App Delegate property

I'm trying to access a property in my app delegate from another class (something I thought would be rather simply) but I'm having troubles in doing so. My files currently look like this:
LTAppDelegate.h
#import <Cocoa/Cocoa.h>
#import "Subject.h"
#interface LTAppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate> {
}
#property Subject *selectedSubject;
#end
LTAppDelegate.m
#synthesize selectedSubject;
The value for selectedSubject is then set inside applicationDidFinishLaunchingin LTAppDelegate.m. Now I'm wanting to get access to this from another class that I have, which is called LTTableViewController and is setup like so:
LTTableViewController.h
#import <Foundation/Foundation.h>
#import "LTAppDelegate.h"
#import "Subject.h"
#import "Note.h"
#interface LTTableViewController : NSObject{
NSMutableArray *notesArray;
LTAppDelegate *appDelegate;
Subject *s;
}
-(IBAction)currentSubjectDetails:(id)sender;
#end
LTTableViewController.m
#import "LTTableViewController.h"
#implementation LTTableViewController
- (id)init
{
self = [super init];
if (self) {
appDelegate = ((LTAppDelegate *)[[NSApplication sharedApplication] delegate]);
s = [appDelegate selectedSubject];
NSLog(#"Test Subject: %#", [s title]);
}
return self;
}
-(IBAction)currentSubjectDetails:(id)sender{
NSLog(#"Selected Subject: %#", [s title]);
}
After inserting various NSLog() messages it would appear that the init method of LTTableViewController is called before applicationDidFinishLaunchingis called in LTAppDelegate. Based on that it makes sense that the "Test Subject" NSLog() in LTTableViewController.m init displays null; however, the 'currentSubjectDetails' method is linked to a button on the interface and when that is pressed after the app is finished loading, the NSLog() message still returns null.
Is there anything obvious I'm missing here. I feel like I'm being a little stupid and missing something really basic.
Similar issue is described here http://iphonedevsdk.com/forum/iphone-sdk-development/11537-viewcontroller-called-before-applicationdidfinishlaunching.html Adding this kind of functionality in the constructor is usually not recommended. Generally, I'd suggest using parameters and not relying on hidden dependencies as those will necessarily depend on the order of execution and you lose the help of the compiler to avoid invalid values. View controller initializers should not be used to store mutable references since view controllers are initialized automatically by predefined constructors, and you cannot pass parameters to them this way.
If you need to access the app delegate, then obtain it, perform operations on it and drop the reference. Try not to cache it, you'll very likely introduce hidden issues. I suggest you hook into the appear-disappear cycle if the viewed contents depend on any kind of current state.
Well, s does not exist, since it is set to null in init, so -currentSubjectDetails prints null. It is not a good idea to set your private variables in the constructor if they depend on other objects.
Rather, let the other objects explicitly tell your controller that it should use that Subject (e.g., treat s as a property).
Or, just query ((LTAppDelegate *)[[NSApplication sharedApplication] delegate]); every time.
-applicationDidFinishLaunching called when e.g. all nib's object initialized, so launching will be ended after construction of views related stuff. This means that constructors of nib's objects wouldn't use any other nib's objects (your delegate and controller initializing with nib, right?).
Try to use -awakeFromNib instead of constructors, I think it will called after construction of both objects.
If you are trying to avoid often calls of ((LTAppDelegate *)[[NSApplication sharedApplication] delegate]) I'll recommend to pass it as method parameter, in function stack. Cyclic references defense and some flexibility.

Lazy instantiation in Objective-C/ iPhone development

Quick question... Well I understand that all properties start out as nil in Objective-C and that sending a message to nil does nothing, therefore you must initialize using [[Class alloc] init]; before sending a message to a newly created property. However, what about if I'm not sending messages to this property or if I set the property using self.property = something? Do I need to alloc init in these cases as well? Also, do UI properties start out as nil as well, such as a UILabel property that you drag out from your storyboard? Do these need alloc init?
Thanks to all who answer
Stunner did a good job of explaining not needing to alloc init objects that have already been created.
But if it is an object that doesn't exist, where are you going to create it? A very common pattern, which I mention because you mentioned it in your post, is lazy instantiation.
So you want an NSMutableArray property. You could alloc init it in some method before you use it, but then you have to worry about "does that method get called before I need my array?" or "am I going to call it again accidentally and re-initialize it."
So a failsafe place to do it is in the property's getter. It gets called every time you access the property.
.h
#property (nonatomic, strong) NSMutableArray* myArray;
.m
#synthesize myArray = _myArray;
- (NSMutableArray*)myArray
{
if (!_myArray) {
_myArray = [[NSMutableArray alloc] initWithCapacity:2];
}
return _myArray;
}
Every time you access that property, it says, "Does myArray exist? If not, create it. If it does, just return what I have."
Plus an added benefit with this design pattern is you aren't creating resources until you need them, versus creating them all at once, say, when your view controller loads or your app launches, which, depending on the requirements, could take a couple seconds.
The reality is when you do self.myProperty = [[Class alloc] init], you're not initializing your property. Rather, you're initializing an object that you tell your property (which is in fact a pointer) to point to. So if you already have an object that's allocated and initialized, you don't have to alloc/init again and you can do self.myProperty = object;
UI Properties do no start as nil, this is because when you add elements in the interface builder, the view owns the elements that you add and these objects are initialized automatically for you. This means if you're creating IBOutlets and connecting them to some properties, you don't have to alloc/init.
I hope this was helpful.
I don't have experience with Storyboards but I know that when you create objects via a xib file all objects are properly instantiated when you tell a view controller to use a xib file. So you need not worry about alloc/initing those objects in code.
Regarding using self.property = <something>, it depends on what something is. If something is any sort of existing object you need not do the alloc init on that object as the self.property = ... syntax calls the property's setter method which will retain, copy, assign, etc. the new value to the property appropriately.
Now any sort of existing object can be an alloc/init'ed object, or an autoreleased object obtained from a convenience method (NSString's stringWithFormat: for example).
As Kaan Dedeoglu pointed out, the self.property = ... syntax points (and retains) the ivar to the object in memory, and it is up to you to initialize that object if it isn't already instantiated.
No you do not need to [[Class alloc]init the properties in your init method.
However, I would encourage you to explicitly set them to Nil in your init method for clarity.

Load custom class properly

I have a custom class which I want to "load" inside the firstViewController and then access it from other classes by segues. My Problem is, I can't even access and change the instance variable inside the firstViewController. Somehow I'm "loading" it wrong. Here is the code I used until now:
inside viewController.h
#property (strong, nonatomic) myClass *newClass;
inside viewController.m
#synthesize newClass;
I then try to access it by:
self.newClass.string = #"myString";
if(newClass.string == #"myString"){
NSLog(#"didn't work");
}
Well, I get "didn't work". Why is that?
When I write
myClass *newClass = [myClass new];
It does work. But the class and its properties gets overwritten every time the ViewController loads again.
What would you recommend? Thank you very much.
Like Kaan said, you forgot to initialize your class, You have only declared and created a pointer for it but not the actual object, on your ViewDidLoad add
self.newClass = [[myClass alloc] init];
It does work. But the class and its properties gets overwritten every
time the ViewController loads again.
That's because every time that specific Viewcontroller loads you are reinitializing the class.
If you want a persistent class through all your program look for the singleton pattern.
This is used in the case when you want to have only 1 instance of a certain object, if you try to initialize another instance of that object you will just receive the one you already have.
PD: newClass.string == #"myString" is wrong.
Use the isEqualToString method when comparing strings.

Singleton NSMutableArray accessed by NSArrayController in multiple NIB's

Early warning - code sample a little long...
I have a singleton NSMutableArray that can be accessed from anywhere within my application. I want to be able to reference the NSMutableArray from multiple NIB files but bind to UI elements via NSArrayController objects. Initial creation is not a problem. I can reference the singleton NSMutableArray when the NIB gets loaded and everything appears fine.
However, changing the NSMutableArray by adding or removing objects does not kick off KVO to update the NSArrayController instances. I realize that "changing behind the controller's back" is considered a no-go part of Cocoa-land, but I don't see any other way of programmatically updating the NSMutableArray and letting every NSArrayController be notified (except it doesn't work of course...).
I have simplified classes below to explain.
Simplified singleton class header:
#interface MyGlobals : NSObject {
NSMutableArray * globalArray;
}
#property (nonatomic, retain) NSMutableArray * globalArray;
Simplified singleton method:
static MyGlobals *sharedMyGlobals = nil;
#implementation MyGlobals
#synthesize globalArray;
+(MyGlobals*)sharedDataManager {
#synchronized(self) {
if (sharedMyGlobals == nil)
[[[self alloc] init] autorelease];
}
return sharedMyGlobals;
}
-(id) init {
if(self = [super init]) {
self.globals = [[NSMutableArray alloc] init];
}
return self
}
// ---- allocWithZone, copyWithZone etc clipped from example ----
In this simplified example the header and model for objects in the array:
Header file:
#interface MyModel : NSObject {
NSInteger myId;
NSString * myName;
}
#property (readwrite) NSInteger myId;
#property (readwrite, copy) NSString * myName;
-(id)initWithObjectId:(NSInteger)newId objectName:(NSString *)newName;
#end
Method file:
#implementation MyModel
#synthesize myId;
#synthesize myName;
-(id)init {
[super init];
myName = #"New Object Name";
myId = 0;
return self;
}
#end
Now imagine two NIB files with appropriate NSArrayController instances. We'll call them myArrayControllerInNibOne and myArrayControllerInNib2. Each array controller in the init of the NIB controller sets the content of the array:
// In NIB one init
[myArrayControllerInNibOne setContent: [[MyGlobals sharedMyGlobals].globalArray];
// In NIB two init
[myArrayControllerInNibTwo setContent: [[MyGlobals sharedMyGlobals].globalArray];
When each NIB initializes the NSArrayController binds correctly to the shared array and I can see the array content in the UI as you would expect. I have a separate background thread that updates the global array when content changes based on an external event. When objects need to be added in this background thread, I simply add them to the array as follows:
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
This is where things fall apart. I can't call a willChangeValueForKey and didChangeValueForKey on the global array because the shared instance doesn't have a key value (should I be adding this in the singleton class?)
I could fire off an NSNotification and catch that in the NIB controller and either do a [myArrayControllerInNibOne rearrangeObjects]; or set the content to nil and reassign the content to the array - but both of these seems like hacks and. moreover, setting the NSArrayController to nil and then back to the global array causes a visual flash within the UI as the content is cleared and re-populated.
I know I could add directly to the NSArrayController and the array gets updated, but I don't see a) how the other NSArrayController instances would be updated and b) I don't want to tie my background thread class explicitly to a NIB instance (nor should I have to).
I think the correct approach is to either fire off the KVO notification somehow around the addObject in the background thread, or add something to the object that is being stored in the global array. But I'm at a loss.
As a point of note I am NOT using Core Data.
Any help or assistance would be very much appreciated.
Early warning - answer a little long…
Use objects that model your domain. You have no need for singletons or globals, you need a regular instance of a regular class. What Objects are your storing in your global array? Create a class that represents that part of your model.
If you use an NSMutableArray as storage it should be internal to your class and not visible to outside objects. eg if you are modelling a zoo, don't do
[[[MyGlobals sharedMyGlobals].globalArray] addObject:tomTheZebra];
do do
[doc addAnimal:tomTheZebra];
Dont try to observe a mutable array - you want to observe a to-many property of your object. eg. instead of
[[[MyGlobals sharedMyGlobals].globalArray] addObserver:_controller]
you want
[doc addObserver:_controller forKeyPath:#"animals" options:0 context:nil];
where doc is kvo compliant for the to-many property 'anaimals'.
To make doc kvo compliant you would need to implement these methods (Note - you don't need all these. Some are optional but better for performance)
- (NSArray *)animals;
- (NSUInteger)countOfAnimals;
- (id)objectInAnimalsAtIndex:(NSUInteger)i;
- (id)AnimalsAtIndexes:(NSIndexSet *)ix;
- (void)insertObject:(id)val inAnimalsAtIndex:(NSUInteger)i;
- (void)insertAnimals:atIndexes:(NSIndexSet *)ix;
- (void)removeObjectFromAnimalsAtIndex:(NSUInteger)i;
- (void)removeAnimalsAtIndexes:(NSIndexSet *)ix;
- (void)replaceObjectInAnimalsAtIndex:(NSUInteger)i withObject:(id)val;
- (void)replaceAnimalsAtIndexes:(NSIndexSet *)ix withAnimals:(NSArray *)vals;
Ok, that looks pretty scary but it's not that bad, like i said you don't need them all. See here. These methods dont need to be part of the interface to your model, you could just add:-
- (void)addAnimal:(id)val;
- (void)removeAnimal:(id)val;
and write them in terms of the kvc accessors. The key point is it's not the array that sends notifications when it is changed, the array is just the storage behind the scenes, it is your model class that send the notifications that objects have been added or removed.
You may need to restructure your app. You may need to forget about NSArrayController altogether.
Aaaaaannnnnyyywaaayyy… all this gets you nothing if you do this
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
or this
[doc addAnimal:tomTheZebra];
from a background thread. You can't do this. NSMutableArray isn't thread safe. If it seems to work then the best that will happen is that the kvo/binding notification is delivered on the background as well, meaning that you will try to update your GUI on the background, which you absolutely cannot do. Making the array static does not help in any way i'm afraid - you must come up with a strategy for this.. the simplest way is performSelectorOnMainThread but beyond that is another question entirely. Threading is hard.
And about that static array - just stop using static, you don't need it. Not because you have 2 nibs, 2 windows or anything. You have an instance that represents your model and pass a pointer to that to you viewControllers, windowControllers, whatever. Not having singletons/static variables helps enormously with testing, which of course you should be doing.