I am currently populating an NSTableView through the controller (MVC design pattern) where I initialise one entry of a NSMutableArray in the controller's init method.
How would I:
Populate my NSMutableArray which is an array of Person objects
Should I populate the NSMutableArray in my mainViewDidLoad method of my base class instead? I have not found any examples or resources for this.
Model (Person.m)
#import "Person.h"
#implementation Person
#synthesize name;
#synthesize gender;
- (id)init
{
self = [super init];
if (self) {
name = #"Bob";
gender = #"Unknown";
}
return self;
}
- (void)dealloc
{
self.name = nil;
self.gender = nil;
[super dealloc];
}
#end
Controller (PersonController.m)
#import "PersonController.h"
#import "Person.h"
#implementation PersonController
- (id)init
{
self = [super init];
if (self) {
PersonList = [[NSMutableArray alloc] init];
// [personList addObject:[[Person alloc] init]];
//
// [personTable reloadData];
}
return self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [personList count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
Person *person = [personList objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
return [person valueForKey:identifier];
}
- (void)dealloc
{
[super dealloc];
}
#end
Base file (Main.h):
#import "Main.h"
#implementation Main
- (void)mainViewDidLoad
{
}
#end
How would I:
Populate my NSMutableArray which is an array of Person objects
Step 1: Create Person objects.
Step 2: Add them to the array.
Your commented-out code does exactly this, although you should probably create the Person separately in case you want to configure it (e.g., set its name).
Should I populate the NSMutableArray in my mainViewDidLoad method of my base class instead?
It doesn't really matter how far in advance of the user seeing your model you create it, but conceptually, it kind of smells to me. It doesn't have anything to do with the view, so I say it belongs in init.
Of course, if the main view—and every view in it—has already loaded, you'll need to tell the table view to reload your data to get it to show any changes you made to the array. Conversely, if you create the model before loading the view, you don't need to reload initially, because the table view will have already asked you for your model once.
Offhand, your commented out code in PersonController looks right. I am assuming that the NSTableRow has the right identifier. The only issue is that your person object is blank, so there are no strings to display. I bet what was happening is that your row was trying to display nil in the first row, and displayed empty strings instead. Does creating your person object, setting the name and gender fields, and then putting it into the NSMutableArray and calling reloadData work (basically, what your commented code does, except now you are providing some actual data to display)?
Well, I am assuming that your controller has an IBOutlet NSTableView *personTable property, which is bound in the interface builder?
Also, in the controller's interface the protocol should be declared, but again, since the controller's implementation has the appropriate methods, I am assuming you have this set up correctly too.
Another detail, have the table columns' identifiers been set correctly in the interface builder? From this example, it is not clear to me how the column identifiers relate to the Person's properties (name and gender). Shouldn't the array personList hold dictionary objects, where a dictionary's object is the person, and the dictionary's key maps to the column identifier that you set in interface builder?
Yet another technicality, a property's name (PersonList) should not start with a capital letter. Just a typo I think, the compiler should at least protest when you try to get the personList, using a lower-case p.
Related
Now I am developing an iOS application which works like this:
User scans QR code,
App searches for a specific key - > value,
it gives out a value to the user.
Currently I have two ViewControllers - the main and "value" ViewController, which is inherited from main. The problem is that if I create NSDictionary in main VC it is not visible in "value" VC. Main VC gives only the string (QR code, the key) through the segue. So, the value VC has to search for key and display the value.
What I ask is some kind of global variable or one DataSource visible across the whole app. Of course, I can implement NSDictionary initialisation inside value ViewDidLoad method and it will work, but this is not the point. New modules are to be added there and the variable has to be global. I googled a lot and got the idea that singleton pattern can be helpful here. I tried to implement it, but no idea how to do. Do I need it, or it is too complex for this kind of DataSource?
Thank you!
The basic idea is, you will still need to #include the header file of the place where this dictionary will be. The solution that Naveen proposes means that you will be including the header for the app delegate wherever you want to access it. Whether to use the app delegate for this purpose or not is kinda grayish. Some people often do this, some say its a bad use of it.
The singleton approach means that you will create a class, that will always contain the same information since the init method will return object that was previously created.
For the singleton aproach, imagine I have a database manager class. So in the header of this class (the DatabaseManagerSingleton.h) ill have this:
#interface DatabaseManager : NSObject
+ (DatabaseManager*)sharedInstance;
// Your dictionary
#property (nonatomic,strong) NSMutableDictionary* someDictionary;
The implementation will look like this: (check how "sharedInstance" initializes the object)
#implementation DatabaseManager
#pragma mark - Singleton Methods
+ (DatabaseManager*)sharedInstance {
static DatabaseManager *_sharedInstance;
if(!_sharedInstance) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[super allocWithZone:nil] init];
});
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)init
{
self = [super init];
if (self != nil)
{
// Custom initialization
_someDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
Now, a VERY important thing is that, any place you want to use this object should first include the header:
EDIT: To use it in your code:
1) add the header
#import "DatabaseManager.h"
2) initialize the object
DatabaseManager *databaseManager = [DatabaseManager sharedInstance];
3) do whatever you need
// Initialize the dictionary
databaseManager.someDictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:#"OBJECT",#"someKey", nil]; // In this case the object is just a NSString.
// Access
[databaseManager.someDictionary objectForKey:#"someKey"];
Put as a property on Appdelegate
#property (nonatomic,strong) NSDictionary * sharedData;
Access anywhere like
NSDictionary *sharedData= ((APPDelegate *) [UIApplication sharedApplication].delegate).sharedData;
After many hours wasted, I officially turn to the experts for help!
My problem lies with using a NSMutableArray as an instance variable, and trying to both add objects and return the array in a method in my class. I am obviously doing something fundamentally wrong and would be grateful for help...I have already tried all the suggestions from other similar questions on stackoverflow, read apples documentation, and basically all combinations of trial and error coding I can think of. The mutable array just alway returns (null). I've even tried creating properties for them, but still the array returns (null) and then I also am running into memory management problems due to the retain while setting the property, and the init in the init method for the class.
Here is what I am trying to do:
1) Loop through a series of UISwitches and if they are 'switched on', add a string to the NSMutableArray
2) Assign this mutable array to another array in another method
Any help much appreciated,
Andy
And for some code...
fruitsViewController.h
#import <UIKit/UIKit.h>
#interface fruitsViewController : UIViewController
{
NSMutableArray *fruitsArr;
UISwitch *appleSwitch;
UISwitch *orangeSwitch;
}
#property (nonatomic,retain) NSMutableArray *fruitsArr; // ADDED ON EDIT
#property (nonatomic,retain) IBOutlet UISwitch *appleSwitch;
#property (nonatomic,retain) IBOutlet UISwitch *orangeSwitch;
- (IBAction)submitButtonPressed:(id)sender;
#end
fruitsViewController.m
#import "fruitsViewController.h"
#implementation fruitsViewController
#synthesize fruitsArr; // ADDED ON EDIT
#synthesize appleSwitch, orangeSwitch;
/* COMMENTED OUT ON EDIT
-(id)init
{
if (self = [super init]) {
// Allocate memory and initialize the fruits mutable array
fruitsArr = [[NSMutableArray alloc] init];
}
return self;
}
*/
// VIEW DID LOAD ADDED ON EDIT
- (void)viewDidLoad
{
self.fruitsArr = [[NSMutableArray alloc] init];
}
- (void)viewDidUnload
{
self.fruitsArr = nil;
self.appleSwitch = nil;
self.orangeSwitch = nil;
}
- (void)dealloc
{
[fruitsArr release];
[appleSwitch release];
[orangeSwitch release];
[super dealloc];
}
- (IBAction)submitButtonPressed:(id)sender
{
if ([self.appleSwitch isOn]) {
[self.fruitsArr addObject:#"Apple"; // 'self.' ADDED ON EDIT
}
if ([self.orangeSwitch isOn]) {
[self.fruitsArr addObject:#"Orange"; // 'self.' ADDED ON EDIT
}
NSLog(#"%#",self.fruitsArr); // Why is this returning (null) even if the switches are on?!
[fruitsArr addObject:#"Hello World";
NSLog(#"%#",self.fruitsArr); // Even trying to add an object outside the if statement returns (null)
}
#end
It seems like your init function is never called. If you're initializing this view controller from a NIB, you need to use initWithCoder. If not, just declare your fruitsArr in viewDidLoad.
Use view did load instead of init...
- (void)viewDidLoad
{
fruitsArr = [[NSMutableArray alloc] init];
}
Change that init for viewDidLoad and see what happens
Is your init method ever being called (in complicationsViewController). Add a NSLog to check this, you might be calling initWithNib: maybe.
At viewDidUnload you should remove self.fruitsArr = nil;, or, if you want to keep it, then initialize the fruitsArr in viewDidLoad (and remove it from init).
because fruitsArr don't be init.
you should do this first:
fruitsArr = [[NSMutableArray alloc] init];
so, I think you don't run - (id)init before you use fruitsArr.
I'm trying to do the right thing by not using global variables in my Xcode project. I've successfully created a singleton. I have a simple data store which I named "myContactsStore" that contains an array, with each object in the array having only 3 instance variables (name, phoneNum, and eMail). I have no problem creating, modifying, saving etc. the data in the array when I'm executing the view controller that created the array.
My problem is trying to access the data store from another view controller. I'm halfway there, as proven by my ability to print the contents of the entire test array from another view controller by using the following code in a for loop:
NSLog(#"%#", myContactsStore.description);
Here's the output:
"Mary, 0938420839, PaulDoe#Mac.com",
"John, 9932097372, PaulDoe#Mac.com",
"Mary, 0726756893, RedCat#iwon.com",
"Mary, 8556327199, xxxbct#mac.com",
"John, 0640848317, xxxbct#mac.com"
How do I access just one instance variables? For example, I want to create a read-only array in another view controller that contains just the email addresses of every contact in the "myContactsStore" array. I've tried several things, but I'm new at this and I must be missing something very basic.
Thanks for you help and any code example you might have the time to include.
While Singletons are an easy way to share data across classes they cause cohesion problems in your overall design where your classes start to "import the world". The issue is coming up with exactly the right dependency for a given class and this is very easily missed when you try to design from classes instead of designing from use cases. You want to ask yourself, "what data does this class use?" You then create a protocol (abstraction) that gives the class the view of the data it wants. In your case one class writes to the data store. It doesn't need to create the data store nor does either class need to know that the data is maintained in an array. Follow these steps exactly in order and follow directly or you'll miss my point. Try a protocol in a separate .h file like the following:
#protocol MyDataStore <NSObject> {
}
Import this header in your 1st class that creates the contacts and declare a property of the protocol's type.
#import "MyDataStore.h"
#interface MyContactCreator : UIViewController
#property (nonatomic, retain) id<MyDataStore> dataStore;
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil dataStore:(id<MyDataStore>)aDataStore;
#end
I threw in a custom init method in case you are currently instantiating your view controllers programmatically instead of via InterfaceBuilder. In your implementation you would do something like this:
#implementation MyContactCreator
//other methods...
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil dataStore:(id<MyDataStore>)aDataStore
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.dataStore = aDataStore;
}
return self;
}
//other methods...
#end
If you are using Interface Builder to create your view controller you can drag a custom object into play and call it something like "MyDataStoreImpl". The idea here is that you are giving the datastore to the view controller instead of it creating it directly and knowing about it. Also you want to defer worrying about how the data store works until you really need to. Later in your view controller where you create the contacts you would use the data store to create them. Assuming the info comes from standard screen elements you would write code like this:
-(void) addContactTapped:(id)sender
{
[self.datastore createContactWithName:txtNameField.text phoneNumber:txtPhoneField.text email:txtEmailField.text];
}
Your editor would scream at you (with little red marks like what your 2nd grade teacher would use on your spelling homework) because the datastore doesn't respond to the message you are sending. You go back and add that method to the protocol:
#protocol MyDataStore <NSObject> {
-(void) createContactWithName:(NSString*)aName phoneNumber:(NSString*)aPhoneNumber email:(NSString*)anEmail;
}
In your other view controller class that wants the list of email addresses you would import the same data store protocol. You would also declare a datasource property identical as what we did above using a complimentary custom init method or Interface Builder to pass the datasource in. This view controller (assuming it's a table view controller) would probably have some methods like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.datasource numberOfContacts];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *email = [self.datasource emailForContactNumber:indexPath.row];
UITableViewCell *cell = //create tableview cell with the email string
return cell;
}
Your editor will start screaming with the little red lines and all. This is where you go and add more methods to the protocol.
#protocol MyDataStore <NSObject> {
-(void) createContactWithName:(NSString*)aName phoneNumber:(NSString*)aPhoneNumber email:(NSString*)anEmail;
-(NSInteger) numberOfContacts;
-(NSString*) emailForContactNumber:(NSInteger)index;
}
The little red lines go away and finally you can begin thinking about how the contacts are stored and retrieved. Create a separate class called MyDataStoreImpl which extends NSObject and imports and follows the "MyDataStore" protocol. Fill out implementations of all of the methods and you should be up and running. It could be as simple as storing NSDisctionary objects containing the contact info in an internal NSMutableArray property.
- (id)init {
self = [super init];
if (self) {
self.allContacts = [NSMutableArray array];
}
return self;
}
-(void) createContactWithName:(NSString*)aName phoneNumber:(NSString*)aPhoneNumber email:(NSString*)anEmail;
{
NSDictionary *newContact = [NSDictionary dictionaryWithObjectsAndKeys:
aName, #"name", aPhoneNumber, #"phone", anEmail, #"email",
nil];
[self.allConstacts addObject:newContact];
}
-(NSInteger) numberOfContacts;
{
return [self.allContacts count];
}
-(NSString*) emailForContactNumber:(NSInteger)index;
{
[[self.allContacts objectAtIndex:index] valueForKey:#"email"];
}
The advantages here are many. You can later re-implement the datasource to read/write from a plist file, network server, or database without touching any of your controllers. Also, your app will be easier to optimize for performance because you can design the read write methods to pull directly from a source instead of naively copying data from one array to another as you would if you were worrying about how it is managed too early. All of the above thrown together without testing and likely has errors but given to illustrate a point of how to properly share data between controllers without Singletons while maintaining a testable and easily maintainable codebase.
You can stick this to any class to make it a singleton:
+(MySingleton *)singleton {
static dispatch_once_t pred;
static MySingleton *shared = nil;
dispatch_once(&pred, ^{
shared = [[MySingleton alloc] init];
shared.someVar = someValue;
});
return shared;
}
-(void) dealloc {
abort();
[someVar release];
[super dealloc];
}
Better yet, you can add the class as an ivar of the application delegate, which is a singleton you already have. You can get a reference to it like this:
// AppDelegate or whatever name
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Then you access the datastore as an ivar on that delegate, and optionally implement one of the several persistence technologies available on iOS, basically plist files through direct file access or NSCoding or even NSUserDefaults (which you shouldn't but it's handy for small tasks), Core Data, or SQLite.
If you are using this data store class only to pass data between controllers, you can do so directly instead. Example:
CitySelectionVC *citySel = [[CitySelectionVC alloc] initWithCities:self.cities];
[self.navigationController pushViewController:citySel animated:TRUE];
[citySel release];
I always use SynthesizeSingleton.h by Mike Gallagher. He has a really informative article relating to this topic here. You should really check it out. It makes the creation of Singleton classes really easy.
Assuming your myContactsStore object has a method contactsArray which returns the internal NSArray object, you can do this:
NSArray *emails = [myContactsStore.contentsArray valueForKey:#"email"];
NSLog(#"output: %#", emails);
Which should output:
output: (
"PaulDoe#Mac.com",
"PaulDoe#Mac.com",
"RedCat#iwon.com",
"xxxbct#mac.com",
"xxxbct#mac.com"
)
You simply have to enumerate through the array and retrieve the data you want. Like so:
NSMutableArray *emails = [[NSMutableArray alloc] initWithCapacity:myContactsStore.count];
for (ContactClassName *contact in myContactsStore) {
NSString *email = contact.eMail; // I'm assuming your ivar eMail is also a property
if (email) [emails addObject:email];
}
You now have an NSMutableArray containing just the list of emails. If you want to make this list immutable simply do:
NSArray *emailList = [emails copy];
Simple question: where do the tableView and section arguments get passed from? The actual code in the method return [self.listData count]; doesn't even mention them.
Here's my interface code:
#interface Simple_TableViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource>
{
NSArray *listData;
}
#property (nonatomic, retain) NSArray *listData;
#end
And this is all the implementation code:
#import "Simple_TableViewController.h"
#implementation Simple_TableViewController
#synthesize listData;
- (void)viewDidLoad {
NSArray *array = [[NSArray alloc] initWithObjects:#"Sleepy", #"Sneezy",
#"Bashful", #"Happy", #"Doc", #"Grumpy", #"Dopey", #"Thorin",
#"Dorin", #"Nori", #"Ori", #"Balin", #"Dwalin", #"Fili", #"Kili",
#"Oin", #"Gloin", #"Bifur", #"Bofur", #"Bombur", nil];
self.listData = array;
[array release];
[super viewDidLoad];
}
- (void)viewDidUnload {
self.listData = nil;
}
- (void)dealloc {
[listData release];
[super dealloc];
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [self.listData count];
}
I just want to know how does the method (NSInteger)tableView: (UITableView *)numberOfRowsInSection: receive those arguments? Of course this happens everywhere; I just want to understand it.
The Simple_TableViewController class is likely meant to manage a single table with a single section. Given that, the tableView and section parameters aren't important because they can only be one thing: a pointer to the table and 0, respectively.
Your view controller class is adding support for these callback methods through UITableViewDelegate and UITableViewDataSource. You are adding this support in your .h file through <UITableViewDelegate, UITableViewDataSource>. These classes are built in to the Cocoa Touch framework and you are just using them. When the table is (re)loaded, this callback methods are called if you have defined them (some are required, others are optional).
I've been using Objective-C for a while, but I've not been following Apple's guidelines very well. Recently I read Cocoa Design Patterns and the Model Object Implementation Guide, and I'm trying to do some very simple things, but do them very well.
Have I missed any major concepts? Please don't mention self = [super init]; that's been covered many times on SO already. Feel free to critique my #pragma marks though!
#import "IRTileset.h"
#import "IRTileTemplate.h"
#interface IRTileset () //No longer lists protocols because of Felixyz
#property (retain) NSMutableArray* tileTemplates; //Added because of TechZen
#end
#pragma mark -
#implementation IRTileset
#pragma mark -
#pragma mark Initialization
- (IRTileset*)init
{
if (![super init])
{
return nil;
}
tileTemplates = [NSMutableArray new];
return self;
}
- (void)dealloc
{
[tileTemplates release];
[uniqueID release]; //Added because of Felixyz (and because OOPS. Gosh.)
[super dealloc]; //Moved from beginning to end because of Abizern
}
#pragma mark -
#pragma mark Copying/Archiving
- (IRTileset*)copyWithZone:(NSZone*)zone
{
IRTileset* copy = [IRTileset new];
[copy setTileTemplates:tileTemplates]; //No longer insertTileTemplates: because of Peter Hosey
[copy setUniqueID:uniqueID];
return copy; //No longer [copy autorelease] because of Jared P
}
- (void)encodeWithCoder:(NSCoder*)encoder
{
[encoder encodeObject:uniqueID forKey:#"uniqueID"];
[encoder encodeObject:tileTemplates forKey:#"tileTemplates"];
}
- (IRTileset*)initWithCoder:(NSCoder*)decoder
{
[self init];
[self setUniqueID:[decoder decodeObjectForKey:#"uniqueID"]];
[self setTileTemplates:[decoder decodeObjectForKey:#"tileTemplates"]]; //No longer insertTileTemplates: because of Peter Hosey
return self;
}
#pragma mark -
#pragma mark Public Accessors
#synthesize uniqueID;
#synthesize tileTemplates;
- (NSUInteger)countOfTileTemplates
{
return [tileTemplates count];
}
- (void)insertTileTemplates:(NSArray*)someTileTemplates atIndexes:(NSIndexSet*)indexes
{
[tileTemplates insertObjects:someTileTemplates atIndexes:indexes];
}
- (void)removeTileTemplatesAtIndexes:(NSIndexSet*)indexes
{
[tileTemplates removeObjectsAtIndexes:indexes];
}
//These are for later.
#pragma mark -
#pragma mark Private Accessors
#pragma mark -
#pragma mark Other
#end
(Edit: I've made the changes suggested so far and commented which answers discuss them, in case anyone needs to know why.)
Please don't mention self = [super init]…
So, why aren't you doing that?
The same goes for initWithCoder:: You should be using the object returned by [self init], not assuming that it initialized the initial object.
- (void)dealloc
{
[super dealloc];
[tileTemplates release];
}
As Abizern said in his comment, [super dealloc] should come last. Otherwise, you're accessing an instance variable of a deallocated object.
- (IRTileTemplate*)copyWithZone:(NSZone*)zone
The return type here should be id, matching the return type declared by the NSCopying protocol.
{
IRTileset* copy = [IRTileset new];
[copy insertTileTemplates:tileTemplates atIndexes:[NSIndexSet indexSetWithIndex:0]];
[copy setUniqueID:uniqueID];
You're inserting zero or more objects at one index. Create your index set with a range: location = 0, length = the count of the tileTemplates array. Better yet, just assign to the whole property value:
copy.tileTemplates = self.tileTemplates;
Or access the instance variables directly:
copy->tileTemplates = [tileTemplates copy];
(Note that you must do the copy yourself when bypassing property accessors, and that you are copying the array on behalf of the copy.)
return [copy autorelease];
}
copyWithZone: should not return an autoreleased object. According to the memory management rules, the caller of copy or copyWithZone: owns the copy, which means it is the caller's job to release it, not copyWithZone:'s.
#synthesize tileTemplates;
[et al]
You may want to implement the single-object array accessors as well:
- (void) insertObjectInTileTemplates:(IRTileTemplate *)template atIndex:(NSUInteger)idx;
- (void) removeObjectFromTileTemplatesAtIndex:(NSUInteger)idx;
This is optional, of course.
//However, should I list protocols
here, even though they're already
listed in IRTileset.h?
No, you shouldn't. The class extension declared in the implementation file is an extension, so you don't have to care about which protocols the class has been declared to follow.
I would recommend to mark your instance variables' names with an underscore: _tileTemplates. (Purists will tell you to affix rather than prefix the underscore; do that if you're afraid of them.)
Don't use new to instantiate classes. It's not recommended ever, as far as I understand.
[NSMutableArray new]; // :(
[NSMutableArray arrayWithCapacity:20]; // :)
Don't call [super dealloc] before doing your own deallocation! This can cause a crash in certain circumstances.
- (void)dealloc
{
[tileTemplates release];
[super dealloc]; // Do this last
}
I'm not sure what type uniqueID has, but shouldn't it also be released in dealloc?
I would never put my #synthesize directives in the middle of a file (place them immediately below ´#implementation´).
Also, having no clear idea about the role of this class, countOfTileTemplates doesn't sound good to me. Maybe just ´count´ will do, if it's unambiguous that what this class does it to hold tile templates?
It looks pretty good except you've left your properties open to arbitrary manipulation by external objects. Ideally, the data should be manipulated directly only by the model class itself and external objects should have access only via dedicated methods.
For example what if some external code calls this:
myIRTileset.tileTemplates=someArray;
Boom, you've lost all your data.
You should define both the data properties as readonly. Then write accessors internal to the class that will managed their retention within the class implementation. This way the only way for an external object to change the tileTemplates is by calling the - insertTileTemplates:atIndexes: and removeTileTemplatesAtIndexes: methods.
Edit01:
I think I mangled it the first go, so let me try again. You should setup you data model class in the following pattern:
Interface
#interface PrivateTest : NSObject {
#private
//iVar is invisible outside the class, even its subclasses
NSString *privateString;
#public
//iVar is visible and settable to every object.
NSString *publicString;
}
#property(nonatomic, retain) NSString *publicString; //property accessors are visible, settable and getable.
//These methods control logical operations on the private iVar.
- (void) setPrivateToPublic;
- (NSString *) returnPrivateString;
#end
So in use it would look like:
Implementation
#import "PrivateTest.h"
//private class extension category defines
// the propert setters and getters
// internal to the class
#interface PrivateTest ()
#property(nonatomic, retain) NSString *privateString;
#end
#implementation PrivateTest
//normal synthesize directives
#synthesize privateString;
#synthesize publicString;
// Methods that control access to private
- (void) setPrivateToPublic{
//Here we do a contrived validation test
if (self.privateString != nil) {
self.privateString=self.publicString;
}
}
- (NSString *) returnPrivateString{
return self.privateString;
}
#end
You would use it like so:
PrivateTest *pt=[[PrivateTest alloc] init];
// If you try to set private directly as in the next line
// the complier throws and error
//pt.privateString=#"Bob"; ==> "object cannot be set - either readonly property or no setter found"
pt.publicString=#"Steve";
[pt setPrivateToPublic];
NSLog(#"private=%#",[pt returnPrivateString]); //==> "Steve"
Now the class has bullet proof data integrity. Any object in your app can set and get the publicString string property but no external object can set or get the private.
This means you can safely allow access to the instance by any object in your app without worrying that a careless line of code in some minor object or method will trash everything.
two minor nitpicks:
One is the init method, (where stylistically I'm against having 2 different return points but thats just me), however there's nothing stopping super's init from returning a different object than itself or nil, eg a different object of its class or even just another object altogether. For this reason, self = [super init] is generally a good idea, even if it probably won't do much in practice.
Second is in the copyWithZone method, you don't copy the tileTemplates, which could be intentional but is generally a bad idea (unless they're immutable). Copying an object is supposed to have the same effect as allocing a fresh one, eg. retain count of 1, so don't autorelease it. Also, it doesn't look like you do anything with the zone, so you should probably replace it with something like
- (IRTileTemplate*)copyWithZone:(NSZone*)zone {
IRTileset* copy = [[IRTileset allocWithZone:zone] init];
[copy insertTileTemplates:[tileTemplates copyWithZone:zone]
atIndexes:[NSIndexSet indexSetWithIndex:0]];
[copy setUniqueID:uniqueID];
return copy;
}
thats everything I found; with the exception of the retain count of copy (which WILL lead to bugs later on) mostly just stuff that I prefer, you can do it your way if you like it better. Good work