Creating a copy of an NSMutableArray with custom objects - objective-c

I've been searching around for the solution to this for quite a while, and none of the other threads have helped. Basically, what I want to accomplish is creating an array of custom objects as a singleton, load them into my level, then create a copy of them since variables assigned to these objects will be manipulated. When the level is complete (or failed) though, I want these objects to remain the same so I can reload them.
Below are some things I've tried.
- (void)spawnStartTiles {
//where _puzzleGridTilesArray and curLevel.gridTiles are NSMutableArrays
[_puzzleGridTilesArray removeAllObjects];
_puzzleGridTilesArray = [curLevel.gridTiles mutableCopy];
CCLOG(#"tile in curlevel %#", curLevel.gridTiles[0]); //want these to log DIFFERENT objects
CCLOG(#"tile in puzzle array %#", _puzzleGridTilesArray[0]);//want these to log DIFFERENT objects
}
The above logs the same object ID.
- (void)spawnStartTiles {
//where _puzzleGridTilesArray and curLevel.gridTiles are NSMutableArrays
_puzzleGridTilesArray = [self cloneArray:curLevel.gridTiles];
CCLOG(#"tile in curlevel %#", curLevel.gridTiles[0]); //want these to log DIFFERENT objects
CCLOG(#"tile in puzzle array %#", _puzzleGridTilesArray[0]);//want these to log DIFFERENT objects
}
-(NSMutableArray*)cloneArray:(NSMutableArray *)myArray {
return [[NSMutableArray alloc] initWithArray: myArray];
}
Still logs same object ID.
- (void)spawnStartTiles {
//where _puzzleGridTilesArray and curLevel.gridTiles are NSMutableArrays
_puzzleGridTilesArray = [[NSMutableArray alloc] initWithArray:curLevel.gridTiles copyItems:YES];
CCLOG(#"tile in curlevel %#", curLevel.gridTiles[0]); //want these to log DIFFERENT objects
CCLOG(#"tile in puzzle array %#", _puzzleGridTilesArray[0]);//want these to log DIFFERENT objects
}
The above gives a runtime error. I think this is because the objects I am copying are a custom class named Tile. The class is a CCNode, and the .h file is below.
#import "CCNode.h"
#interface Tile : CCNode
#property (nonatomic, assign) NSInteger value;
#property (nonatomic, assign) NSInteger gemLevel;
#property (nonatomic, assign) BOOL mergedThisRound;
- (void)updateValueDisplay:(BOOL)bannerTiles difficultyMode:(int)difficultyMode;
- (void)updateOpacity:(NSInteger)opacityVariable;
- (void)tileHasBeenSelected:(BOOL)tileHasBeenTouched;
#end
Is there a way to somehow transform this class so that is can be copied? I've looked at http://www.techotopia.com/index.php/Copying_Objects_in_Objective-C and Implementing NSCopying and I'm still confused, so further help would be greatly appreciated. Thank you!

You get an error because [[NSMutableArray alloc] initWithArray:curLevel.gridTiles copyItems:YES] calls copyWithZone: on each item in the array. Your items must conform to NSCopying for you to use this method, but this is the correct approach.
To implement NSCopying:
Update your Tile class to add the protocol, i.e. #interface Tile : CCNode <NSCopying>
Implement the method - (id)copyWithZone:(NSZone *)zone in your Tile implementation.
In that method, allocate a new instance of Tile and assign all of it's properties to that of your current instance.

Related

Objective-C class as NSMutableArray

Very simple question. Is it possible to create a class which is a list by it self? I mean:
I do
taskList *taskList1 = [[taskList alloc] init];
And than simply:
taskList1 addObject:[task1]
May seem stupid, but I'm totally new to O-C syntax
I'd need two methods:
-(instancetype) init;
which just initialize as an empty list
+(instancetype)taskList;
to allocate taskList instance
and last thing:
In interface i use:
#interface taskList : NSObject
or
#interface taskList : NSMuttableArray
I got stuck on something specific, didn't I? I'm sorry that I bother you with my programming level.
Alright, I gave up, just last question, because I have to finish it very soon.
I changed my approach I added
#property NSMutableArray *list;
Why does this:
taskList *TL1 =[taskList initTaskList];
task *task1 = [[task alloc] init];
task *task2 = [[task alloc] init];
TL1.list addObject:[task1];
doesn't work, I have "Expected identifier" Error
If you read the subclassing notes on NSArray / NSMutableArray you'll see that Apple recommend against subclassing them because they are a class cluster. (i.e. what you really get when you ask for one is an undocumented subclass, and the initialiser decides which undocumented subclass to return to you based on some undocumented qualifiers..
So just make an NSObject subclass which owns a (private) strong property of type NSMutableArray, and publish an api to access that array..
eg
#import "modelList.h"
//dont worry header is empty, its up to you to do that.. this is a subclass on NSObject
#interface modelList()
#property (strong, nonatomic) NSMutableArray *backingArray;
#end
#implementation modelList
#synthesize backingArray = _backingArray;
-(instancetype )init{
if (self = [super init]) {
[self setBackingArray:[[NSMutableArray alloc]init]];
}
return self;
}
//public API (the stuff that is prototyped in the header..)
-(id)objectAtIndex:(NSUInteger )index{
return [self.backingArray objectAtIndex:index];
}
-(BOOL )containsObject:(id)object{
return [self.backingArray containsObject:object];
}
-(void )addObject:(id)object{
//example application, qualifying object..
if ([object conformsToProtocol:#protocol(NSCoding)]) {
[self.backingArray addObject:object];
}
}
-(NSUInteger )count{
return [self.backingArray count];
}
//etc etc declare publicly the things you need to get the job done
#end
so far this is just a face for a mutable array obviously, but it gives you a place for whatever other model logic you need. good luck

Problems with an NSMutableArray

I'm trying to write an app that has two scenes in it. The first page is a UITableView that will contain a list of note entries. The second scene has 2 text fields (note summary and note description). I'm entering details on the second scene and then clicking a "Save" button which saves the data:
NoteListViewController.m
- (IBAction)saveAndGoBack:(id)sender {
NSLog(#"NoteDetailViewController.m: %#", NSStringFromSelector(_cmd));
NSString * desc = [[NSString alloc]init];
NSString * detail = [[NSString alloc]init];
desc = _noteTitle.text;
detail = _noteDesc.text;
[[NoteStore sharedStore]createNoteWithDesc:desc AndDetail:detail];
[self.navigationController popViewControllerAnimated:YES];
}
NoteStore is a static sharedStore that I am saving the data into.
NoteStore.m
-(Notes *)createNoteWithDesc:(NSString *)desc AndDetail:(NSString *)detail {
NSLog(#"NoteStore.m: %#", NSStringFromSelector(_cmd));
Notes * newNote = [[Notes alloc]initNoteWithDesc:desc AndDetail:detail];
[self.privateItems addObject:newNote];
return newNote;
}
So, the note is added to an NSMutableArray called "privateItems". I confirmed that the Note object gets added properly.
*****The problem happens when I try to retrieve the Note object (desc and detail) from the privateItems array later on using an accessor method which has a public property in the NoteStore.h file called allItems (it's an NSArray readonly, nonatomic and a copy):
NoteStore.m
-(NSArray *)allItems{
NSLog(#"NoteStore.m: %#", NSStringFromSelector(_cmd));
return [self.privateItems copy];
}
Everytime I try to retrieve it, the first property (desc) comes up as nil while the second property (detail) has the data I saved in the second text field of the second scene. Why is the first field constantly coming up as nil???
Just for clarity, a Note object is declared as follows
#interface Notes : NSObject
// What are the properties of a note?
#property (nonatomic, weak) NSString * noteDesc;
#property (nonatomic, weak) NSString * noteDetail;
#property (nonatomic, weak) NSString * test;
// Designated Initializer
-(instancetype)initNoteWithDesc:(NSString *)desc AndDetail:(NSString *)detail;
#end
Any help would be greatly appreciated.
When you call the designated initialiser you pass in two NSString objects. At this point they are owned by the method where they are created.
When they are assigned to the properties they only have a weak reference and therefor the retain count is not bumped up. Weak references are good for things like delegate objects. In this case you want your objects to stick around, so by declaring them as strong you're saying I want these properties to stick around in memory and take ownership of them.

Initializing child objects from parent

I'm having a bit of a structural dilemma with designing my app. I want to use a series of nested loops to create a large amount of custom objects. Once those objects are created, I want to store them all into an object which is collection of those objects.
Visualized:
#interface CollectionOfObjectA : NSObject
#property (nonatomic, strong) NSArray *reference;
#end
#implementation CollectionOfObjectA
-(CollectionOfObjectA *)init{
NSMutableArray *ref = [[NSMutableArray alloc] init];
for(int i=0; i < largeNumber; i++){ // There will be nested loops.
NSString *str = #"string made from each loop index";
ObjA *obj = [[ObjA alloc] initWithIndexes: str];
[ref addObject: obj];
}
self.reference = [ref copy];
}
#end
#interface ObjA : CollectionOfObjA
// several properties
#end
#implementation ObjA
-(ObjA *)initWithIndexes:(NSString *)indexes{
self = [super init];
// Use passed indexes to create several properties for this object.
return self;
}
#end
What would be the best way about creating this object which is a collection of child objects? Am I incorrect in making ObjA a child of CollectionOfObjectA -- should it be the other way around? Any help would be greatly appreciated.
Ok, my advise: I have nearly ~30 custom objects. Like events. After that I make class Factory which can create all of them. And also this class Factory have method: getAllObjects.
Like this:
#include "CustomEvent.h"
#interface EventFactory
+(NSArray*)allEvents;
#end
#implementation EventFactory
-(CustomEvent*)firstEvent{/*something here*/}
-(CustomEvent*)secondEvent{/*yes, you should init custom object here*/}
-(CustomEvent*)thirdEvent{/*and after that you can put them*/}
/*
...
*/
+(NSArray*)allEvents{
EventFactory* factory = [[EventFactory alloc]init];
return #[
[factory firstEvent],
[factory secondEvent],
/*...*/
[factory lastEvent]
];
}
#end
Here I return NSArray because I don't need, actually, know anything of them. They already have handlers and they subscribed on custom notifications. You can return NSDictionary for better access.
P.S: for better explanation you can read article in wiki about Factory pattern
But, if you want better manipulation of objects, you should use other pattern:Composite pattern.
What I mean?
#interface EventCollection{
NSMutableArray* YourArray;
}
-(void)addCustomEvent:(CustomEvent*)event atPosition:(NSInteger)position;
-(void)removeCustomEventAtPosition:(NSInteger)position;
-(void)seeAllEvents;
-(void)seeAllPositions; /*if you want*/
-(void)doesThisPositionAvailable:(NSInteger)position;
#end
#implementation EventCollection
-(void)addCustomEvent:(CustomEvent*)event atPosition:(NSInteger)position{
/*maybe you should check if this position available*/
if ([self doesThisPositionAvailable:position]){
/*add element and save position*/
}
}
-(void)removeCustomEventAtPosition:(NSInteger)position{
if (![self doesThisPositionAvailable:position]){
/*destroy element here*/
}
}
-(void)seeAllEvents{
/*yes, this method is the main method, you must store somewhere your objects.
you can use everything, what you want, but don't share your realization.
maybe, you want use array, so, put it as hidden variable. and init at the initialization of your collection
*/
for (CustomEvent* event in YourArray){
[event description];
}
}
#end

iOS - NSMutableArray shows objects out of bounds on setting property

I have implemented the following code to assign NSMutableArray to a property -
NSMutableArray * anArray = [responseDictionary valueForKeyPath:#"tags"];
NSLog(#"The array length is=%d",[anArray count]);
for (NSString *s in anArray) {
NSLog(#"you are %#", s);
}
[self setActiveTagArray:anArray];
It prints out the string values fine. But in the setter function, if I place a breakpoint I see that it shows there are two objects but they are "Out of Scope". What does this mean? What am I doing wrong? My getter also does not fetch any values. The property functions -
-(void)setActiveTagArray:(NSMutableArray *)tags
{
activeTagArray = [[NSMutableArray alloc] init];
activeTagArray = tags;
//NSLog(#"%#",[activeTagArray count]);
}
-(NSMutableArray *)getActiveTagArray
{
return activeTagArray;
}
Is activeTagArray a class variable as well as a property. Consider using _activeTagArray as the class variable name. And then in the .m file just use #synthesize activeTagArray = _activeTagArray;, and for get the second two methods completely.
Response to comment:
You said "I have implemented the following code to assign NSMutableArray to a property". I took this to mean you have "#property (nonatomic, retain) NSMutableArray *activeTagArray;" in your .h file. If this is the case then you would access it thru otherObject'sNameForYourClassHere.activeTagArray.
#synthesize create accessors & mutators for you.

Pass array from one Objective-C class to another

Im attempting to pass an array that is created in one class into another class. I can access the data but when I run count on it, it just tells me that I have 0 items inside the array.
This is where peopleArray's data is set up, it's in a different class than the code that is provided below.
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
This is how I get the array from another class, but it always prints count = 0
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
The rest of my code:
data.h
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *peopleArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
data.m(some of it at least)
#implementation data
#synthesize viewController, managedObjectContext, peopleArray;
- (void)fetchRecords {
[self setupContext];
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
[mutableFetchResults release];
[request release];
//NSLog(#"this is an array: %#", eventArray);
}
login.h
#import <UIKit/UIKit.h>
#import "data.h"
#class rootViewController, data;
#interface login : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UIButton *loginButton;
UIButton *newUser;
UITextField *entry;
data *dataClass;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, assign) data *dataClass;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField;
#end
login.m
#import "login.h"
#import "data.h"
#interface login (PrivateMethods)
- (void)setUpFromTheStart;
- (void)loadDataScreen;
-(void)login;
#end
#implementation login
#synthesize viewController, dataClass;
-(void)login{
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
}
Is it the same object? If so, what you have should work. Check to see how you are getting the dataClass instance -- if you alloc a new one, you don't get the array from the other object.
Edit: From your comments below, it appears that you are having some confusion on the difference between classes and objects. I will try to explain (I'm going to simplify it):
A class is what you write in Xcode. It's the description that lets your application know how to create and access objects at run-time. It is used to figure out how much memory to allocate (based on instance variables) and what messages can be sent, and what code to call when they are. Classes are the blueprints for creating objects at runtime.
An object only exists at run-time. For a single class, many objects of that class can be created. Each is assigned its own memory and they are distinct from each other. If you set a property in one object, other objects don't change. When you send a message to an object, only the one you send it to receives it -- not all objects of the same class.
There are exceptions to this -- for example if you create class properties (with a + instead of a - at the beginning), then they are shared between all objects -- there is only one created in memory, and they all refer to the same one.
Also, since everything declared with a * is a pointer -- you could arrange for all pointer properties to point to the same data. The pointer itself is not shared.
Edit (based on more code): dataClass is nil, [dataClass peopleArray] is therefore nil, and then so is the count message call. You can send messages to nil, and not crash, but you don't get anything useful.
I don't see how the login object is created. When it is, you need to set its dataClass property.
Try running the code in the debugger, setting breakpoints, and looking at variables.
From the code, it looks like you are passing a mutable array.
[self setPeopleArray: mutableFetchResults];
Probably the items of the array are removed somewhere in your calling class / method. Or the array is reset by the class from which you get the mutableFetchResults in the first place.