Trouble converting on NSArray - ios

I have an NSMutableArray 'myArray' which contained some customObject 'A', Now I want to convert 'myArray' to tempArray which contained some object which subclass 'A' named as 'B'.
As a property just like follow:
NSMutableArray <B *> tempArray;
tempArray = [myArray mutableCopy];
But the object in tempArray always is kind of 'A'. I want convert 'A' to 'B', Any suggestion?

'B' has more property which i want to use.
How do you expect this to work? B is a child of A, it contains more information (the property you wish to use etc.). Where would the data for that property come from?
You need a way to construct a new instance of B using (the properties of) an instance of A and supplying the extra data a B needs. B might already have a suitable init method, or you may need to write your own code.
Once you have a way to produce a new B you can just iterate over your array building a new one, building a new B instance for each A instance.
HTH

If you have an immutable array (NSArray) that means you can't add or remove objects, or replace objects with other objects. [myArray mutableCopy] creates an NSMutableArray. It will contain exactly the same objects as the immutable array, but you are now free to add or remove or replace objects.
Your declaration NSMutableArray * tempArray does nothing but lie to the compiler. If myArray contained objects of type A*, then tempArray contains the same objects of type A*. You are just lying, so the compiler believes they are objects of type B*, but they are not.
There is no way on earth how a compiler could automatically convert an object of some class to an object of a subclass. If the subclass has additional members, for example, how is the compiler going to fill those members? This just cannot work.
If you want to create new objects of type B*, based on objects of type A*, you have to do that by hand, likely using an initialiser like
- (instancetype)initWithA:(A*)a;
in your B interface. Your code could be for example
NSMutableArray <B*> *tempArray = [myArray mutableCopy];
for (NSUInteger i = 0; i < tempArray.count; ++i) {
tempArray [i] = [[B alloc] initWithA:tempArray [i]];
}
or possibly
NSMutableArray <B*> *tempArray = [[NSMutableArray alloc] init];
for (A* a in myArray)
[tempArray addObject:[[B alloc] initWithA:a]];

Related

NSMutableArray becomes immutable when reinstantiated?

I've made a NSMutableArray a property on my view controller, which holds some core data objects for users.
When the user presses a button , I clear out the contents of the mutable array with a while loop,
while (self.mArray.count != 0){
[context deleteObject:self.mArray[0]];
[self.mArray removeObjectAtIndex:0];
}
After the while loop, I reinit the array:
self.mArray = [[NSMutableArray alloc] init];
I know it isn't necessary since there should be zero objects left in the array, but regardless, when I reinitialize the array, and then check the class of the array in the debugger, I get __NSArrayI, which is corroborated by an exception thrown when I attempt to add an object into self.mArray right afterwards.
I've looked for any other references to my array, but I've always passed around [self.mArray mutableCopy] as arguments to other methods, and I never cast it as an NSArray. I just don't understand how calling [[NSMutableArray alloc] init] would initialize the array as an immutable array.
What am I missing?
It's likely that you have declared mArray as copy in the property declaration. Change that to strong.

Check if NSMutableArray has a specific Object

I am trying to check if the NSMutableArray has a specific object, before adding the object to it, if exists then don't add.
i looked over many posts explaining how to do this, managed to implement it like this, but it always gives me that the object "doesn't exist", though i already added it !
//get row details into FieldLables Object
AllItemsFieldNames *FieldLabels = feedItems[row];
// object to hold single row detailes
AllItemsFieldNames *SelectedRowDetails = [[AllItemsFieldNames alloc] init];
SelectedRowDetails.item_name = FieldLabels.item_name;
//SelectedRowDetails.item_img = FieldLabels.item_img;
SelectedRowDetails.item_price = FieldLabels.item_price;
//NSLog(#"item has been added %#", SelectedRowDetails.item_name);
//NSLog(#"shopcartLength %lu", (unsigned long)SelectedFieldsNames.count);
if([SelectedFieldsNames containsObject:SelectedRowDetails])
{
NSLog(#"Already Exists!");
}
else
{
NSLog(#"Doesn't Exist!");
[SelectedFieldsNames addObject:SelectedRowDetails];
}
I can display all object from the NSMutableArray into a table, what i need to do in the above code is stop the addition of duplicate objects.
The first method listed on the NSArray documentation under the section "querying an array" is containsObject:. If it's not working, that suggests that your implementation of isEqual: is not correct. Make sure you follow the note in the documentation:
If two objects are equal, they must have the same hash value. This
last point is particularly important if you define isEqual: in a
subclass and intend to put instances of that subclass into a
collection. Make sure you also define hash in your subclass.
You might also consider using an NSSet since you can't add duplicates to that. Of course, this would also require a working version of isEqual:.
Sets are composed of unique elements, so this serves as a convenient way to remove all duplicates in an array.
here some sample,
NSMutableArray*array=[[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3",#"4", nil];
[array addObject:#"4"];
NSMutableSet*chk=[[NSMutableSet alloc ]initWithArray:array]; //finally initialize NSMutableArray to NSMutableSet
array= [[NSMutableArray alloc] initWithArray:[[chk allObjects] sortedArrayUsingSelector:#selector(compare:)]]; //after assign NSMutableSet to your NSMutableArray and sort your array,because sets are unordered.
NSLog(#"%#",array);//1,2,3,4

If we store a NSMutableDictionary into a NSDictionary, can we later use it again as a NSMutableDictionary?

If we replace a NSDictionary instance variable with a NSMutableDictionary that we create, can we later use it again as a NSMutableDictionary by casting it as a NSDictionary?
Example:
create and store the NSMutableDictionary into the NSDictionary slot
NSMutableDictionary *muta = [[NSMutableDictionary alloc] initWithObjects:NSArray forKeys:NSArray];
Object.nsDictionary = muta;
Get the dictionary later
NSMutableDictionary *muta2 = (NSMutableDictionary*) Object.nsDictionary;
//Do stuff like Add objects with it
[muta2 setObject:id forKey#"key"];
Do we have to recreate a NSMutableDictionary from the NSDictionary we pull from the object or does it retain it's "mutability"? Can you please tell me why a subclassed object will or will not retain its specific methods and properties when replacing a generic super class?
If your property is declared as NSDictionary then you shouldn't make any assumptions about whether it is actually mutable or not.
The proper code should be:
NSMutableDictionary *muta2 = [Object.nsDictionary mutableCopy];
This works regardless of what type of dictionary is actually stored in the property.
In your question you are confusing two different things: you refer to assigning to an instance variable but show code which shows assigning to a property. These are not the same. You are also appear to be misunderstanding assignment by referring to it as replacing an object.
In Objective-C (and many other, but not all, languages) an object is referred to by a reference. It is these references which are assigned into variables. So for example in:
NSMutableDictionary *a = [NSMutableDictionary new];
NSMutableDictionary *b = a;
The right hand side of the first assignment is an expression which creates a new object and returns a reference to that object. This reference is then stored into the variable a. The second line copies the reference, not the object, stored in a and stores into into the variable b.
After these two lines one object and two variables have been created, and both variables reference exactly the same object. Assignment of a reference to an object does not change the object it refers to. So if we now change the code to:
NSMutableDictionary *a = [NSMutableDictionary new];
NSDictionary *b = a;
We still have one object and two variables created, and both still refer to exactly the same object. The assignment in this case is allowed as NSMutableDictionary is a subclass of NSDictionary - that is an object of type NSMutableDictionary is also of type NSDictionary, it provides all the same behaviour as the latter.
From your question "Can you please tell me why a subclassed object will or will not retain its specific methods and properties when replacing a generic super class?" you need to read up on inheritance and understand how subclassing works.
Once you've stored a reference to a subclass into a superclass typed variables, a into b in the above code, while you haven't changed the referenced object in anyway you have lost the immediate knowledge that the reference is in fact to an object of the subclass - all you can immediately state about a reference stored in b above is that it refers to an object which is at least an NSDictionary, but may be of any subclass of NSDictionary.
If you are absolutely sure, or just like writing programs that break, you can tell the compiler to trust you that b contains a reference to an NSMutableDictionary by using a cast:
NSMutableDictionary *c = (NSMutableDictionary *)b;
Do this and the compiler trusts you, but if b does not contain a reference to an NSMutableDictionary then your subsequent usage of c will probably be invalid and your program will break.
What you need to do is to test whether the reference refers to an NSMutableDictionary or not, and you do this with the method isKindOfClass::
if ([b isKindOfClass:NSMutableDictionary.class])
{
// b refers to an NSMutableDictionary
NSMutableDictionary *c = (NSMutableDictionary *)b;
// do something with c
}
else
{
// b does not refer to an NSMutableDictionary
// handle this case
}
Back to properties: a property is two methods (assuming read-write, you can have read-only properties), a getter and a setter, which combine to provide an abstraction of a variable - you can "read" and "assign" to them using dot notation in expressions. However as they call a method, rather than performing direct reads or assignments to a variable, that method can change was is read or assigned. In particular an object typed property declared with the copy attribute will make a copy of the object that is reference. For example:
#property (copy) NSDictionary *c;
...
NSMutableDictionary *a = [NSMutableDictionary new];
NSDictionary *b = a;
self.c = a;
then a & b both refer to the same object which is an instance of NSMutableDictionary; while, due to the copy attribute,crefers to a *distinct* object which is an instance ofNSDictionary`.
You can now see why using instance variable and property interchangeably in your question is not right - what is assigned can be different depending on whether the assignment is to a variable or a property.
You should read up on objects, object references, inheritance and properties.
HTH.

addObjectsFromArray will not add them as new objects

My problem is when I copy an array with objects, it seems that if i change the copied array, the original array changes as well. Below is a simplified version of my code.
I have an array of objects
#interface TimesViewController (){
NSMutableArray *route1;
}
I fill these objects up in my ViewDidLoad method
route1 = [[NSMutableArray alloc] init];
while (sqlite3_step(statement) == SQLITE_ROW) {
StopsOnRoutes *stopOnRoutes = [[StopsOnRoutes alloc] init];
[stopOnRoutes setStart_time:p_time];
[stopOnRoutes setStart_route_id:p_route];
[stopOnRoutes setStart_stop_id:p_stop];
[stopOnRoutes setStop_time:c_time];
[stopOnRoutes setStop_route_id:c_route];
[stopOnRoutes setStop_stop_id:c_stop];
[stopOnRoutes calc];
[route1 addObject:stopOnRoutes];
}
BUT when I try to copy route1 and make a few changes, they change both in route1 and amTimes
NSMutableArray *amTimes = [[NSMutableArray alloc] init];
[amTimes addObjectsFromArray:route1];
for(int i = 0; i<amTimes.count; i++){
[[amTimes objectAtIndex:i] setStop_time:[[amTimes objectAtIndex:i] stop_time]-86400];
[[amTimes objectAtIndex:i] setStart_time:[[amTimes objectAtIndex:i] start_time]-86400];
}
How can I copy route1 to amTimes, so if I change an object in AM times, it won't change in route1.
The way arrays work they simply hold references (pointers) to the objects in the array. Therefore when you add objects, it does not create a new object and point to it, it simply points to that very same object. What you want to do is referred to as a deep copy array, which involves coping each object inside the array.
Which is what happens when you use
- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag
Each object in the array is send a copyWithZone, you can implement copyWithZone in each of the objects you want to add to your array, and then do
[myArray addObject[myObject copy]];
Or you can also use
[[NSArray alloc] initWithArray:otherArray copyItems:YES] //You need to implement copyWithZone in the items you want to copy.
This will give you the result that you want.
You need to have your StopOnRoutes class implement the NSCopying protocol and the copyWithZone: method. This method needs to create a copy of self.
Then you can do this:
NSMutableArray *amTimes = [[NSMutableArray alloc] init];
for (StopOnRoutes *obj in route1) {
[amTimes addObject:[obj copy]];
}
Container objects don't contain copies of the objects that are added to them, they contain pointers to the objects. If you add the same object to 2 arrays, it is a member of both arrays.
If you want to create an array of copies, you need to do several things:
You need to implement the NSCopying protocol for the objects in your array so you can copy them.
Then you'd need to create a new mutable array with room for the same number of elements as the first array, loop through the first array, copy each item, and add it to the second array.
If your array contains only standard system objects that support NSCopying then your work is simpler. If your array contains different kinds of custom objects or complex custom objects then you might have more work to do.
Try using setArray: i.e.[amTimes setArray:route1];

Objective-c adding to array same instance with different properties

I'm trying the following code to create an instance, assign properties, add to array.
Then, assigning new properties and adding again.
However array will contain 2 identical objects (equal to the second one added). The class Message simply has several (nonatomic, retain) NSStrings/Integer properties.
This probably has something to do with my understanding of pointer, can someone explain?
self.messages=[[NSMutableArray alloc]init];
Message *m=[[Message alloc]init];
m.cb=#"2402";
m.ck=1001;
m.msg=#"as";
[self.messages addObject:m];
m.cb=#"2422";
m.ck=1002;
m.msg=#"aadfsdsdfdssdklsdflkh";
[self.messages addObject:m];
NSLog(#"%#",self.messages);
When you add an object to an array, it does not add a copy of the object to the array, but instead just a reference to it. If you want two different objects, then you need to create two different objects instead of re-using the same one (or, as #Brendon points out, create a copy when you add it to your array).
To fix your example, the most common technique would be to add the following line right before you start modifying the properties for the second object:
m=[[Message alloc]init];
Or, use a second pointer and object instead of reusing m.
EDIT:
To add a copy, change [self.messages addObject:m]; to [self.messages addObject:[m copy]];, assuming that the Message class conforms to the NSCopying protocol.
Yes, after executing the posted code self.messages contains the Message object twice, at indexes 0 and 1. That's not a problem, though. Arrays can contain any object, even themselves.
It seems that you want two distict objects, so you would just create a second Message.
You can either implement the NSCopy protocol — as mentioned by lnafziger — or just create new instances quite easily in a for loop.
«Two or more, use a for»
— Edsger W. Dijkstra
self.messages=[[NSMutableArray alloc] init];
NSArray *dataArray = #[ #{#"cb": #"2402", #"ck": #(1001), #"msg": #"as"},
#{#"cb": #"2422", #"ck": #(1002), #"msg": #"aadfsdsdfdssdklsdflkh"}
];
for(NSDictionary *data in dataArray) {
Message *m=[[Message alloc] init];
m.cb = data[#"cb"];
m.ck = [data[#"ck"] integerValue];
m.msg = data[#"msg"];
[self.messages addObject:m];
}

Resources