Archiving objects within objects in objective-c - objective-c

Thankyou for reading,
PS: I am a beginner so I am not too good at this unfortunetaly, but any help would be very appreciated
So basically I want to archive a big array which contains Account objects, which they themselves contain:
1.a username in form of a NSString,
2.an encrypted password array filled with NSNumbers, and
3.a data array filled with service data objects.
The service data objects have the following:
encrypted serviceType (NSArray filled with NSNumbers) (whatever service the username and password is for)
encrypted username (NSArray filled with NSNumbers)
encrypted password (NSArray filled with NSNumbers)
Now weirdly when trying to archive and save this, I get two errors. One time it won't let me add service data objects to the data array in the Account class anymore, with the following error message (or at least they dont show up in the NSTableView I have, however it does say they exsist):
[<ServiceData 0x60000023bfa0> valueForUndefinedKey:]: this class is not key value
coding-compliant for the key service.
and two, when I try to login in the the username and password from the Account class, it retrieves the username and the first couple and last couple NSNumbers of my password correctly, but the rest of the NSNumbers for the password are in the trillions or something, so I'm wondering what is going wrong, any help would be greatly appreciated.
Here is the code for my instance variables, how I used the NSKeyedArchiver and unarchiver, and how I went about saving and loading the files. Again, please help me, I have been stuck on this for a while and this is kind-of my last resort. I have no idea what is happening!
ACCOUNT CLASS:
H file:
#interface Account : NSObject <NSCoding>
{
#private
NSString *username;
NSMutableArray *password;
NSMutableArray *accData;
}
#property NSString *username;
#property NSArray *password;
FULL M file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
SERVICEDATA CLASS:
H file:
#interface ServiceData : NSObject <NSCoding>
{
#private
NSArray* serviceData;
NSArray* usernameData;
NSArray* passwordData;
}
#property NSArray* serviceData;
#property NSArray* usernameData;
#property NSArray* passwordData;
M file:
#import "Account.h"
#import "Crypt.h"
NSMutableArray *accounts;
NSInteger accountNumber = -1;
#implementation Account
#synthesize username;
#synthesize password;
- (id)initWithUsername:(NSString *)name withPassword:(NSMutableArray *)key
{
self = [super init];
if (self)
{
username = name;
password = key;
accData = [[NSMutableArray alloc]init];
}
return self;
}
/*
setters and getters
*/
-(NSString*)getUsername;
{
return username;
}
-(NSArray*)getPassword;
{
return password;
}
-(void)changePassword:(NSMutableArray*)newPassword;
{
NSInteger sizeOldPass = [password count];
NSInteger sizeNewPass = [newPassword count];
int changeXObjects = (int)(sizeNewPass - sizeOldPass);
int changeSize = abs(changeXObjects);
//adjusts size differences
if (changeXObjects < 0)
{
for(int i = 0; i < changeSize; i++)
{
[password removeLastObject];
}
}
else if (changeXObjects > 0)
{
for(int i = 0; i < changeSize; i++)
{
NSNumber *value = [NSNumber numberWithInt:0];
[password addObject:value];
}
}
//change password
NSInteger sizePass = [password count];
for (int k = 0; k < sizePass; k++)
{
[password replaceObjectAtIndex:k withObject:newPassword[k]];
}
}
-(NSMutableArray*)getAccData;
{
return accData;
}
-(void)setAccData:(NSMutableArray*)input
{
[input setArray: accData];
}
+(NSMutableArray*)getAccounts
{
return accounts;
}
+(NSInteger)getAccountNumber
{
return accountNumber;
}
+(void)setAccounts:(id)accs
{
accounts = accs;
}
+(void)setAccountNumber:(NSInteger)number
{
accountNumber = number;
}
/*
other methods
*/
+(void)addAccount:(id)acc
{
[accounts addObject:acc];
}
+(void)deleteAccount:(NSInteger)index
{
[accounts removeObjectAtIndex:index];
}
-(void)addAccData:(id)input
{
[accData addObject:input];
}
-(void)deleteAccDataAt:(NSInteger)index
{
[accData removeObjectAtIndex:index];
}
+(bool)checkPassword:(NSString*)passwordIn accountNumber:(NSInteger)index
{
NSMutableArray *passwordInputCrypt = [Crypt encrypt:passwordIn];
NSMutableArray *passwordCrypt = [accounts[index] getPassword];
NSInteger lengthPassword = [passwordInputCrypt count];
bool correctPassword = true;
if([passwordCrypt count] == [passwordInputCrypt count])
{
for(int i = 0; i < lengthPassword; i++)
{
if(passwordCrypt[i]!=passwordInputCrypt[i])
correctPassword = false;
}
}
else
{
correctPassword = false;
}
if(correctPassword == true)
{
return true;
}
return false;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
#end
LOADING FILE(filePath is given):
NSData *data = [[NSFileManager defaultManager] contentsAtPath:filePath];
if(data != nil)
{
NSArray *arrayFromData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableArray *initArray = [NSMutableArray arrayWithArray:arrayFromData];
[Account setAccounts:initArray];
}
else
{
NSMutableArray *accountsInit = [[NSMutableArray alloc] init];
[Account setAccounts:accountsInit];
}
SAVING FILE:
NSArray *accounts = [Account getAccounts];
NSString *filePath = [AppController getFilePath];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:accounts];
[data writeToFile:filePath atomically:YES];

A few things:
You should not be archiving password data to disk (even if you are encrypting it). That's what the keychain is for. Have a look at SSKeychain for a good wrapper class.
The Key-value coding error you are getting suggests you are trying to reference your serviceData array as just "service" somewhere. Check your valueForKey and setValueForKey statements.
Can you post the rest of the Account class? That method setAccounts looks like it might be relevant.
Also is there a reason you are using Keyed Archiving instead of Core Data?

Related

How to check if an object is the last object on NSArray

How can I check if an object in the last object on an NSArray?
I've tried:
if ([currentStore isEqual:[Stores lastObject]])
{
//Code
}
but it didn't work.
Any idea?
Thanks!
or try this
BOOL lastElement = false;
NSUInteger index = [stores indexOfObject:currentStore];
if (index != NSNotFound)
{
lastElement = (index == [stores count] - 1);
}
Bit modified try this:
NSUInteger index = [stores indexOfObject:currentStore];
if (index == ([stores count]-1))
{
NSLog(#"Yes its Last");
}
If you didn't override isEqual method, the base class implementation of NSObject::isEqual only check if both pointers points to the same address.
This excellent article http://nshipster.com/equality/ explain objc equality principles
The below sample logs - Testing Stores - works fine
#interface Stores : NSObject
#property (strong, nonatomic) NSString* name;
- (instancetype) initWithName:(NSString*) name;
#end
#implementation Stores
- (instancetype) initWithName:(NSString*) name;
{
_name = name;
return self;
}
- (BOOL)isEqualToStores:(Stores*) Stores
{
if (!Stores)
return NO;
if (![_name isEqualToString:Stores.name] )
return NO;
return YES;
}
- (BOOL)isEqual:(id)object
{
if (self == object)
{
return YES;
}
if (![object isKindOfClass:[Stores class]])
{
return NO;
}
return [self isEqualToStores:(Stores *)object];
}
#end
-(void) testStores
{
Stores* last = [[Stores alloc] initWithName:#"5"];
NSArray* arr = #[
[[Stores alloc] initWithName:#"1"],
[[Stores alloc] initWithName:#"2"],
[[Stores alloc] initWithName:#"3"],
[[Stores alloc] initWithName:#"4"],
[[Stores alloc] initWithName:#"5"]
//last
];
if ([last isEqual:[arr lastObject]])
{
NSLog(#"Testing Stores - works fine");
}
else
{
NSLog(#"Testing Stores - opps!?1?!?");
}
}

How to parse specific Json Array

I have a Json array like this:
{"Response":{"Token":"///","Name":"///","Surname":"///","Phone":"///","Street":"///","Interno":"///","PostalCode":"///","City":"///","Province":{"ID":"///","Code":"///","Name":"///"},"Email":"///#gmail.com"},"Error":false,"ErrorDetails":null}
How can I parse the values inside Response and inside Province using objective-c?
I tried with the following code:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the locations
if(_fproducts == nil)
{
_fproducts = [[NSMutableArray alloc] init];
}
// Parse the JSON that came in
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < jsonArray.count; i++)
{
NSDictionary *jsonElement = jsonArray[i];
// Create a new location object and set its props to JsonElement properties
LoginCredentials *newFProduct = [[LoginCredentials alloc] init];
newFProduct.token = jsonElement[#"Id"];
newFProduct.nomeUser = jsonElement[#"nome"];
NSLog(#"TOKEN:%#", newFProduct.token);
NSLog(#"NOME:%#", newFProduct.nomeUser);
// Add this question to the locations array
[_fproducts addObject:newFProduct];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownloaded:_fproducts];
}
}
But I can't parse the values inside Response.. Do i need to create an array of Response and then parse it?
Convert all in NSDictionary, not in NSArray, and then:
jsonDictionary = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
if(!error) {
NSDictionary* response = jsonDictionary[#"response"];
if(response) {
NSDictionary* province = response[#"province"];
NSLog("Province: %#", province);
/* here you can save all your values */
if(province) {
NSString* identificator = province[#"ID"];
NSString* code = province[#"Code"];
NSString* name = province[#"Name"];
}
}
}
An elegant way to do this is creating your custom interface:
Province.h
#interface Province : NSObject
- (id)initWithDictionary:(NSDictionary*)dictionary;
- (nonatomic, strong) NSString* identificator;
- (nonatomic, strong) NSString* code;
- (nonatomic, strong) NSString* name;
#end
Province.m
#implementation Province
- (id)initWithDictionary:(NSDictionary*)dictionary {
self = [super init];
self.identificator = dictionary[#"ID"];
self.code = dictionary[#"Code"];
self.name = dictionary[#"Name"];
return self;
}
#end
So your code becomes:
if(!error) {
NSDictionary* response = jsonDictionary[#"response"];
if(response) {
NSDictionary* provinceDictionary = response[#"province"];
if(province) {
Province* province = [Province initWithDictionary:provinceDictionary];
}
}
}

Custom single KeyValuePair class vs NSMutableDictionary

I came into a situation where I had to write a loop with a good amount if iterations and in this loop I had a NSData object that I had to associate with a key. This lead me to search for a simple objective-c _KeyValuePair_ class but coulnt not find one so I wrote my own. Now I'm curious to see if there is any benefit over just using an NSMutableDictinoary holding just 1 key and value. After trying both throughout my project I can't tell much difference on the App UI side or with Instruments Time Profiler.
So my questions are:
Could a single kvpair class be more efficient than a NSMutableDictionary
Does a NSMutableDict allocate any larger amount of space by default then this does
Is there actually a standard single key value pair class that I just missed
Some code:
for (int i = 0, count = [photoUrls count]; i < count; ++i) {
// Example usage of the kvp class
NSMutableDictionary *imageRequest = [[NSMutableDictionary alloc] init];
JHKeyValuePair *kvPair = [[JHKeyValuePair alloc] initWithKey:#"DAILY" andValue:[NSNumber numberWithInt:i];
[imageRequest setObject:self forKey:#"delegate"];
[imageRequest setObject:kvPair forKey:#"userInfo"];
[kvPair release];
[imageRequest setObject:[dailySpecialData objectForKey:#"IMAGE_URL"] forKey:#"url"];
[imageDownloader addDownloadRequestToQueue:imageRequest];
[imageRequest release];
}
JHKeyValuePair.h
#interface JHKeyValuePair : NSObject {
id key;
id value;
}
#property (nonatomic, retain) id key;
#property (nonatomic, retain) id value;
- (id)initWithKey:(id)aKey andValue:(id)aValue;
#end
JHKeyValuePair.m
#import "JHKeyValuePair.h"
#implementation JHKeyValuePair
#synthesize key;
#synthesize value;
- (id)initWithKey:(id)aKey andValue:(id)aValue {
if ((self = [super init])) {
key = [aKey retain];
value = [aValue retain];
}
return self;
}
- (void)dealloc {
[key release], key = nil;
[value release], value = nil;
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone {
JHKeyValuePair *copy = [[JHKeyValuePair allocWithZone:zone] init];
[copy setKey:self.key];
[copy setValue:self.value];
return copy;
}
- (BOOL)isEqual:(id)anObject {
BOOL ret;
if (self == anObject) {
ret = YES;
} else if (![anObject isKindOfClass:[JHKeyValuePair class]]) {
ret = NO;
} else {
ret = [key isEqual:((JHKeyValuePair *)anObject).key] && [value isEqual:((JHKeyValuePair *)anObject).value];
}
return ret;
}
#end
Edit to fix the initial explanation. Seems I got sidetracked mid-sentance and never came back to finish it.
If you really want to get speed you are doing a lot of unnecessary retain releases that probably aren't necessary every time you set your key/values. If you use a struct and some basic c code you can achieve something a little quicker but you sacrifice the simple and consistent memory management you get from doing it the objective c way.
typedef struct {
id key;
id value;
} Pair;
BOOL isEqual(Pair a, Pair b); //...
// You will need to clean up after yourself though:
void PairRelease(Pair p) {
[p.key release];
[p.value release];
}

Objective-c: NSMutableDictionary setObject not working

Not sure what I am doing wrong here. When I try to check the dictionary either by a specific key or allkeys I either get an error or null. (I know I'm using a string where I could be using a boolean for the conditional I just like having a check like that say true or false instead of YES and NO. Add that to my OCD list. :D ) activePlayer is set in an awakeFromNib method to 1, it can be switched using a popupbutton between P1 and P2.
- (IBAction)setPlayer:(id)sender {
haserror = #"false";
errmsg = [NSMutableString stringWithCapacity:0];
[errmsg retain];
[errmsg appendString: #"There was a problem setting your team up\n\n"];
thisTeamName = [txtTeamName stringValue];
thisTeamColor = [pdTeamColor itemTitleAtIndex:[pdTeamColor indexOfSelectedItem]];
//validate form
if ([thisTeamName isEqualToString:#""]) {
haserror = #"true";
[errmsg appendString: #"You must enter a team name\n\n"];
}
if ([thisTeamColor isEqualToString:#"Select A Color"]) {
haserror = #"true";
[errmsg appendString: #"You must select a team color\n\n"];
}
//check for errors
if (haserror == #"true") {
[self showAlert: errmsg];
} else {
//set up treasury
treasury = 1000;
//convert to string for display
[lblTreasury setStringValue: [NSString stringWithFormat:#"$%i", treasury] ];
//add items to dictionary
if (activePlayer == #"1") {
[p1TeamData setObject:thisTeamName forKey:#"teamName"];
[p1TeamData setObject:thisTeamColor forKey:#"teamColor"];
[p1TeamData setObject:[NSString stringWithFormat:#"%i", treasury] forKey:#"cash"];
} else {
[p2TeamData setObject:thisTeamName forKey:#"teamName"];
[p2TeamData setObject:thisTeamColor forKey:#"teamColor"];
[p2TeamData setObject:[NSString stringWithFormat:#"%i", treasury] forKey:#"cash"];
}
NSLog(#"%#", [p1TeamData allKeys]);
}
[errmsg release];
}
[Edit: here's the .h file]
#interface GameController :NSObject {
IBOutlet id btnSaveData;
IBOutlet id lblTreasury;
IBOutlet id pdPickPlayer;
IBOutlet id pdTeamColor;
IBOutlet id txtTeamName;
int activePlayer;
NSString* activePlayerName;
NSString* activePlayerTeamColor;
int treasury;
NSMutableDictionary* p1TeamData;
NSMutableDictionary* p2TeamData;
NSArray* players;
NSArray* teamColors;
NSArray* unittypes;
NSString* thisTeamName;
NSString* thisTeamColor;
NSMutableString* errmsg;
NSString* haserror;
}
-(void) awakeFromNib;
- (IBAction) getPlayer : (id)sender;
- (IBAction) setPlayer : (id)sender;
-(void) showAlert : (NSMutableString* ) m;
#end
Make sure you initialize the collections in the -initXXX method. If not, they will be assigned to nil.
-(id)initXXX:... {
if ((self = [super initYYY:...])) {
...
p1TeamData = [[NSMutableDictionary alloc] init];
p2TeamData = [[NSMutableDictionary alloc] init];
...
}
return self;
}
If all you want are "true" and "false", just define them yourself. It's not a reason to use string instead of BOOL. In fact, Foundation already defined TRUE and FALSE besides YES and NO.
Also, please use an integer for activePlayer.
You should always compare NSString with -isEqualToString:, not ==.
if ([haserror isEqualToString:#"true"])
...
if ([activePlayer isEqualToString:#"1"])
This should be the reason why p1TeamData is always nil, because activePlayer == #"1" is unreliable and there could be player-1 stuff assigned to p2TeamData.

What is the value of NSEnumerator?

when for . . . in . . . is available?
Specifically, when we can write:
NSArray *array;
// array allocated and initialized here.
for (id obj in array) {
// do something to the object here
}
Why would we ever use an NSEnumerator?
NSEnumerator was created before fast enumeration (for/in loop) was available. Think of it as backward-compatibility if you like.
But with NSEnumerator you can enumerate the collection in customized order, e.g. backwards:
NSEnumerator* enu = [array reverseObjectEnumerator];
id object;
while ((object = [enu nextObject])) {
...
}
(Of course, since NSEnumerator also supports for/in loop you can use a better way:
for (id object in [array reverseObjectEnumerator]) {
...
}
)
or define your own iterator class by subclassing NSEnumerator, e.g.
#import <Foundation/Foundation.h>
#interface RangeEnumerator : NSEnumerator {
int cur, len;
}
+(RangeEnumerator*)enumeratorWithLength:(int)length;
-(id)initWithLength:(int)length;
-(id)nextObject;
#end
#implementation RangeEnumerator
-(id)initWithLength:(int)length {
if ((self = [super init]))
len = length;
return self;
}
+(RangeEnumerator*)enumeratorWithLength:(int)length {
return [[(RangeEnumerator*)[self alloc] initWithLength:length] autorelease];
}
-(id)nextObject {
if (cur < len)
return [NSNumber numberWithInt:cur++];
else
return nil;
}
#end
int main () {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
for (NSNumber* num in [RangeEnumerator enumeratorWithLength:12])
printf("%d\n", [num intValue]);
[pool drain];
return 0;
}