NSKeyedArchiver cannot archive arrays - objective-c

I am trying to develop a Cydia tweak, but now I have some problems.
I want to copy an NSMutableArray, so I use:
myArray = %orig;
NSMutableArray* myArray2;
myArray2 = myArray;
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myArray2];
(The original function returned an NSMutableArray, so I use myArray = %orig; to get it)
However, seems the archivedDataWithRootObject doesn't work, it always causes my device crash.
So, is there any limit for archivedDataWithRootObject ? I don't know whether the original NSMutableArray is encoded or not. Can I use archivedDataWithRootObject for a encoded NSMutableArra?
Thanks for any suggestions and replies

I don't understand this line of code in your code :
myArray = %orig;
And I can't see why you are doing this :
NSMutableArray* myArray2;
myArray2 = myArray;
If you got an NSArray or an NSMutableArray you will be able to encode it directly with the that line of code :
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myArray2];
BUT (there is always as but...)
What is store in your NSArray?
If you got custom object they must implement the NSCoding protocol to be able to participate in the Archiving dance.
AND finally you are saying :
I want to copy an NSMutableArray, so I use:
If you just want to copy the NSMutableArray you can do this :
[[NSMutableArray alloc] initWithArray:anArray];
I still don't get the % sign there.
If you need to add new object to it, why not just go simple, like create new object. If it's your own object you have 2 choice:
1st you implement the NSCopying protocol on those object an make them behave like you want, or
2nd choice : if the array contain one type of object you can create a Class method for those object that is like this : newObjectWithArray:(* NSArray)theArray and in that method you alloc init object that you initialize to the value of the receiving objet.

Related

Passing a simple NSMutableArray change original

I am having hard time with a simple array that i want to pass .
I have a class with some NSMutableArray that i pass to another class(the array is global from singleton)
[mgzm saveWithArray:[Globals sharedGlobals].allImages];
To this function :
-(void)saveWithArray:(NSMutableArray*)currentArray
{
dataToSave=[[NSMutableArray alloc] init]; //local
dataToSave=[currentArray mutableCopy]; //copy
Than i saw that is is changing the original array which i don't want .
So i did this :
dataToSave=[[NSMutableArray alloc] initWithArray:currentArray copyItems:YES];
Which result in a situation that i can't change the dataToSave array get get a crash when trying to.(it needs to be changed).
Than i did this :
for(NSMutableDictionary *dic in currentArray)
[dataToSave addObject:dic];
Which again if i change dataToSave it change also the original array (?! )
Is there a way in this language to COPY array without changing the original one ????
When you make a copy of a mutable array, changing the array copy does not change the original array, not the objects inside the array. Here is what happens when you call [currentArray mutableCopy]:
The two arrays are pointing to the same objects. If you remove an object from the copy, the original array would still have it. However, if you modify the object itself (say, change the name of A to X) the change will reflect on the object in the original array, because it is the same object.
Here is what you want to happen:
Now the two arrays are completely independent of each other. This is the effect that you achieve when you call
dataToSave=[[NSMutableArray alloc] initWithArray:currentArray copyItems:YES];
However, there is a catch: in order for this to work, the objects inside the array must conform to NSCopying protocol, otherwise the code is going to crash.
To fix this, make sure that the objects inside NSMutableArray implement NSCopying. Here is an answer that explains how it is done.
The problem is although you are creating a new array the NSDictionary objects within the new array are still the same ones. So you need to make copies of the NSDictionary objects, you we're close but you need to do something like this...
-(void)saveWithArray:(NSMutableArray*)currentArray
{
dataToSave=[[NSMutableArray alloc] init]; //local
for(NSMutableDictionary *dic in currentArray)
[dataToSave addObject:[NSDictionary dictionaryWithDictionary:dic];
}

Is there any way to specify the class of objects of a NSMutableArray?

Im having the following problem:
I've made a NSMutableArray "array" that is going to contain objects of a class named "Class". At the start that array should be empty and it must be filled during the program's execution.
As I never actually told the compiler that my NSMutableArray will be holding elements of the class Class, when I try to write the appropriate methods the compiler wont let me do it.
This is my first experience on Objective-C and iPhone development. I used to code in C/C++ where I declared my arrays in the following way:
Class array[NUMBEROFELEMENTS];
Is there any way to do this in Objective-C?
Thanks!
The truth is that is doesn't matter to the NSMutableArray what type of object it is. NSMutableArray simply stores pointers to all the objects they contain, or reference.
The trick is when you pull the object back out of the array you need to create a new pointer based on the appropriate type:
MyObject *myObject = [myArray objectAtIndex:0];
Then you can use the object however you like:
[myObject doThatThingWithThisValue:10];
Or whatever you need.
Arrays in Objective-C Cocoa are objects (as well as other collections, sets, dictionaries). Arrays can contain references to objects of any type, so the type for the array is simply NSArray, NSMutableArray, etc...
Since they are objects, you can send them messages to manipulate their content.
I suggest you take a look at Apple's excellent Collections Programming Topics, which explain the rudiments of collections.
Here is a quick example :
// two objects of different types
NSNumber *n = [NSNumber numberWithInteger:10];
NSString *s = #"foo";
// alloc/init a new mutable array
NSMutableArray *a = [NSMutableArray arrayWithCapacity:10];
// add an object
[a addObject:n];
[a addObject:s];
// array a now contains a NSNumber and a NSString
Well, you can still have C-style arrays in Objective-C.
However, the characteristics of Objective-C (some people will call it strength, other will call it weakness) is that it has dynamic typing of objects and dynamic dispatch.
It has NSArray and NSMutableArray which are not specialized for the certain class. It can store objects of non-compatible classes.
You can use the following idiom: [obj isMemberOfClass: [Class type]] to make sure an array element is of the desired type and then cast to Class*.
You can also use for-each loop (aka Fast Enumeration):
NSMutableArray* array = //... initialize your array
for (Class* elm in array) {
elm.your_property = 10;
}

How to store the NSArray with customized Object?

I have an NSMutableArray, if this is store string, I can read, and write it successfully, using this method.
[array writeToFile:m_sApplicationPlistPath atomically:YES];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:m_sApplicationPlistPath];
but if my Array add something which is not a simple string, for example, I add a special Object to the array, like this:
[array addObject:[[SpecialObject alloc] init]];
I find that I can't read back the special object, how can I solve it, thank you.
Implement the NSCoding protocol. (encodeWithCoder and initWithCoder)
If you still cannot use the method writeToFile:atomically: then you have to serialize the NSArray to get data, and then write it to an file.
NSData *data = NSKeyedArchiver archivedDataWithRootObject:theArray];
I think you can't do that because [NSArray writeToFile:atomically:] method makes use of property lists, which are only available for certain data types (NSString, NSData, NSArray or NSDictionary).
In order for you to write the array to a file you can make SpecialObject comply to NSCoding protocol and then save the array using -[NSKeyedArchiver archiveRootObject:toFile:] using the array as root object.
You can get this to work by using methods defined in the NSKeyValueCoding protocol to convert the instances of your custom class to instances of NSDictionary before you write them. You could do a similar conversion after reading the plist file back in to recreate the objects.
Here's an example that converts an array of instances of a custom Book class to an array of dictionaries, using the dictionaryWithValuesForKeys: method declared in NSKeyValueCoding (and implemented by NSObject, so all objects inherit this behavior):
+ (NSArray *)dictionariesFromBooks:(NSArray *)books
{
NSMutableArray *bookDicts = [NSMutableArray arrayWithCapacity:[books count]];
for (Book *currBook in books)
{
NSDictionary *currDict = [currBook dictionaryWithValuesForKeys:[Book keys]];
[bookDicts addObject:currDict];
}
return bookDicts;
}
Similarly, here's a method that populates instances of Book using the NSKeyValueCoding method setValuesForKeysWithDictionary:
+ (NSArray *)booksFromDictionaries:(NSArray *)bookDicts
{
NSMutableArray *books = [NSMutableArray arrayWithCapacity:[bookDicts count]];
for (NSDictionary *currDict in bookDicts)
{
Book *currBook = [[Book alloc] init];
[currBook setValuesForKeysWithDictionary:currDict];
[books addObject:currBook];
[currBook release];
}
return books;
}
With a little work, this can be made to cascade to nested custom objects. For example, if Book contained a nested Author instance, you could override NSKeyValueCoding methods such as setValue:forKey: and dictionaryWithValuesForKeys: to convert the instance on the fly.
From docs:
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.

copy objects from one NSMutableArray to another NSMutableArray

I am trying to understand copying objects from one NSMutableArray to another. Consider the following 2 scenarios:
1 - copying original to clone where changes in the clone will affect the original.
2 - copying original to clone where the changes in the close will NOT affect the original.
First, I am trying to produce scenario #1 first with the following code. From what I understand, when copying array not using 'mutablecopy', the clone array will just hold the pointer to the same string objects in the original. So if I were to change the first element of the clone to a different object, the first element of the original would change too right? ... but that's not the result I am getting. Why?
Matter of fact, when I use mutablecopy
[self.cloneArray addObject:[[self.originalArray objectAtIndex:i] mutableCopy]];
I get the same result. I am confused.
ArrayClass.h
#interface ArrayClass : NSObject {
NSMutableArray *_originalArray;
NSMutableArray *_cloneArray;
}
#property (nonatomic, retain) NSMutableArray *originalArray;
#property (nonatomic, retain) NSMutableArray *cloneArray;
ArrayClass.m
#synthesize originalArray = _originalArray;
#synthesize cloneArray = _cloneArray;
_originalArray = [[NSMutableArray alloc] initWithObjects: #"one", #"two", #"three", #"four", #"five", nil];
_cloneArray = [[NSMutableArray alloc] initWithCapacity:[self.originalArray count]];
for (int i=0; i<5; i++) {
[self.cloneArray addObject:[self.originalArray objectAtIndex:i]];
}
// make change to the first element of the clone array
[self.cloneArray replaceObjectAtIndex:0 withObject:#"blah"];
for (int n=0; n<5; n++) {
NSLog(#"Original:%# --- Clone:%#", [self.originalArray objectAtIndex:n], [self.cloneArray objectAtIndex:n]);
}
...
2011-03-27 03:23:16.637 StringTest[1751:207] Original:one --- Clone:blah
2011-03-27 03:23:16.638 StringTest[1751:207] Original:two --- Clone:two
2011-03-27 03:23:16.639 StringTest[1751:207] Original:three --- Clone:three
2011-03-27 03:23:16.642 StringTest[1751:207] Original:four --- Clone:four
2011-03-27 03:23:16.643 StringTest[1751:207] Original:five --- Clone:five
You are thinking about this way too hard.
In Objective-C, you have references to objects. An NSString *foo; simply defines a variable foo that refers to an NSString. If you say NSString *bar = foo;, then bar will have a reference to whatever object foo was referring to. No more, no less.
An NSArray is just a collection of object references. So, if you say:
NSArray *b = [NSArray arrayWithArray: a];
You are creating an array b that contains all of the same references to the exact same set of objects as a. If you modify an object referred to by a, that'll be the exact same object in b and the modification will be reflected.
When you copy an object, you are creating a new object that has the identical internal state as the original. I.e. when you say NSMutableString *foo = [barString mutableCopy];, then foo is a reference to a new string; a different one than barString.
So... when creating a new array, the question is do you want the array to contain the exact same contents as the original array or do you want it to contain a new set of objects that you can modify?
You have a misunderstanding of what's going on. The replaceObjectAtIndex:withObject: call isn't modifying objects in the array, it's modifying the array itself. After this line:
[self.cloneArray replaceObjectAtIndex:0 withObject:#"blah"];
you've replaced the object in your clone array, but you haven't changed the original array at all. If you actually modified the NSString object you put in the arrays, you might be able to get the behaviour you were expecting. You won't be able to do it with the objects you've put into the original array in your example, though, since they're immutable string objects. If you stuck mutable strings in there, used the same loop to 'clone' your array, and then did something along the lines of:
[[self.cloneArray objectAtIndex:0] appendString:#"some junk to append"];
you would actually modify the string object at index 0. Since both arrays still contain that same object, you'd get the 'modify original array by changing the objects in the clone array' behaviour.

Table view not updating according to bindings

This is a very newbie question, and this is something I have done many times before, but there's something I'm missing this time.
In my AppDelegate.h file I declare an NSArray and set it as a property:
#interface AppDelegate : NSObject {
NSArray *lines;
}
#property(readwrite, retain) NSArray *lines;
#end
And then in the AppDelegate.m file in the awakeFromNib method I alloc it:
lines = [[NSArray alloc] init];
Then I have a method that sets the lines array:
NSString *fileContents = [NSString stringWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:#"Desktop/sometextfile.txt"] encoding:NSUTF8StringEncoding error:NULL];
lines = [fileContents componentsSeparatedByString:#"\n"];
I have an array controller thats bound to AppDelegate.self.lines then I have a table column bound to Array Controller.arrangedObjects. I can confirm that the array is being updated (tested using NSLog) however the contents of the table are not being update (it remains blank).
Is there something obvious I'm missing here?
You don't need a data source if you're using Bindings. One or the other.
I have an array controller thats bound to "AppDelegate.self.lines" …
Why self?
#property(readwrite, retain) NSArray *lines;
No, use copy here. Otherwise, you'll find yourself retaining someone else's mutable array, which they will then mutate. Then “your” array will have changed without you knowing it.
Then I have a method that sets the lines array:
lines = [fileContents componentsSeparatedByString:#"\n"];
This is why the table doesn't show anything. You're not going through the property, you're accessing the instance variable directly. Direct instance variable accesses do not cause KVO notifications, so the array controller never finds out about the change.
Even worse, you're leaking the old array (since you simply assign over it without releasing it) and under-retaining this new array. Because you're not retaining the new array, that instance variable will hold a dead object shortly. The automatic retaining is done by the setLines: method, which only gets called when you call it.
You need to go through the property:
self.lines = [fileContents componentsSeparatedByString:#"\n"];
A property access is an implicit accessor message, so this both retains the array (or copies it, once you correct the #property as I suggested above) and posts KVO notifications.
When you say you have a arrangedObjects bound to the column do you mean you set the tablview datasource? If not you to set the tableview datasource to the lines array
You might want to read through this, it's got some good diagrams and explanations. What ennuikiller is saying is correct, I think it's a problem with your datasource. This is done by calling
[aTable setDelegate:aDelegate];