How to create a key per initiated object using NSUserdefaults - objective-c

I have a custom NSObject and I instantiate this object using a manager.
My NSObjets are passed into a mutable array so that I can access their properties(name) via uipickerview once save method is initialised.
When I close my app, all my objects disappear as expected.
I would like to save my objects in the NSUserDefaults so I have implemented the NSUserdefaults in my manager. This is to save my objects so that they are loaded when the application is launched in the uipickerviewcontroller.
However my application crashes when I load the uipickerviewcontroller.
Here is the log after the crash:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PacsServer encodeWithCoder:]: unrecognized selector sent to instance 0x7fd0a2e7fa20'
PacsServer.h
#import <Foundation/Foundation.h>
#interface PacsServer : NSObject
#property ( nonatomic) NSString *pacsName;
#property (nonatomic) NSString*username;
#property ( nonatomic) NSString*password;
#property (nonatomic) NSString *wadoUrl;
#property ( nonatomic) NSString *apiDirectory;
#property ( nonatomic) NSString *dataArrayIncludesUsernamePasswordWadoLoginWebServiceAndSecurity;
#end
pacsserver.m
#import "PacsServer.h"
#implementation PacsServer
#end
manager.h
#import <Foundation/Foundation.h>
#interface PacsServerManager : NSObject
+(instancetype)pacsManager ;
#property NSMutableArray *pacsServers;
#end
Manager.m
#import "PacsServerManager.h"
#import "PacsServer.h"
#implementation PacsServerManager
+(instancetype)pacsManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
-(id) init {
if(self =[super init]){
PacsServer *server= [[PacsServer alloc] init];
_pacsServers = [NSMutableArray arrayWithObjects:server, nil];
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSData *serverData = [NSKeyedArchiver archivedDataWithRootObject:_pacsServers];
[standardUserDefaults setObject:serverData forKey:#"server"];
[standardUserDefaults setObject:_normalArray forKey:#"Servers" ];
[standardUserDefaults synchronize];
}
return self;
}
#end
Here in the save method below I get the username, password and other fields from the text fields. To be able to save all these into keychain.
-I first save these in an array then pass this array to NSData using datawithJsonObject.
Then save this array as NSString into keychain
Add nsobjects via manager
#import "AddPACSViewController.h"
#import "PacsServerManager.h"
#import "PacsServer.h"
#import <KeychainItemWrapper.h>
#interface AddPACSViewController ()
#end- (void)saveForAddPacs:(id)sender {
.
.
.
.
NSString *pacsDataArray = [keychain objectForKey:(__bridge id)kSecValueData];
PacsServer *newPacsServer = [[PacsServer alloc] init];
newPacsServer.pacsName= pacsNameFromAddPacsView;
newPacsServer.dataArrayIncludesUsernamePasswordWadoLoginWebServiceAndSecurity = pacsDataArray;
[[PacsServerManager pacsManager].pacsServers addObject:newPacsServer];
[self performSegueWithIdentifier:#"Save" sender:self];
}
Access the objects in uipickerview
pickerviewcontroller.m
- (NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
NSLog(#"Pacs data in settingsview:%#", [PacsServerManager pacsManager].pacsServers);
PacsServer *server = [[PacsServerManager pacsManager].pacsServers objectAtIndex:row];
NSString *name = server.pacsName;
NSLog(#"Servername:%#, and the data %#", server.pacsName, server.dataArrayIncludesUsernamePasswordWadoLoginWebServiceAndSecurity);
NSLog(#"%#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
return name;
}
Any help would be appreciated.
Thank you.

You need to address a couple of things here.
1) The exception is caused because of this line:
NSData *serverData = [NSKeyedArchiver archivedDataWithRootObject:_pacsServers];
When you archive any object, that object needs to implement the <NSCoding> protocol. When you archive aggregate objects (like NSArray and NSSet) each of the objects that are contained in the array or set need to implement the <NSCoding> protocol also. In this case PacsServer is being sent an encodeWithCoder: message which it doesn't implement.
See this tutorial on NSCoding.
2) Is it really a good idea to be persisting these objects in the NSUserDefaults? NSUserDefaults is really designed for small bits of information like whether you should show a help item, or is this the first time the user has launched your app. Perhaps you'd be better off saving this data in a separate .dat file or even using a different method like CoreData or sqlite (if you have a large amount of data).

Related

Can't bind NSArray with NSArrayController

I have an array (_websites) which returns 2 results (i can see the records using NSLog).
What I am trying to do is to display those 2 records in NSTableView that has 3 columns. I make numerous attempts to bind the content of my array with the NSArrayController, without any success.
Here is the .h file
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface CombinedViewController : NSViewController <NSTableViewDataSource>
#property (nonatomic,strong) NSManagedObjectContext *mObjContext;
#property AppDelegate *appDelegate;
#property (strong) IBOutlet NSArrayController *combinedRecordsArrayController;
#property (nonatomic,strong)NSArray *websites;
#property (weak) IBOutlet NSTableView *tableView;
#end
the .m file code:
#import "CombinedViewController.h"
#import "Website.h"
#import "Customer.h"
#import "Hosting.h"
#interface CombinedViewController ()
#end
#implementation CombinedViewController
- (void)viewDidLoad {
[super viewDidLoad];
_appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
self.mObjContext = _appDelegate.managedObjectContext;
[self getCombinedResutls];
}
-(NSArray *)getCombinedResutls {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Website" inManagedObjectContext:self.mObjContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [self.mObjContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Error:%#",error);
}
_websites = [_mObjContext executeFetchRequest:fetchRequest error:nil];
for (Website *ws in _websites) {
Customer *cust = ws.customerInfo;
Hosting *host = ws.hostingInfo;
NSLog(#"Website: %#, Customer: %#, Hosting Provider: %#",ws.websiteUrl, cust.customerName, host.hostingProvider);
}
return fetchedObjects;
}
#end
I am trying to learn how to do it both ways, using cocoa binding and programmatically, so any kind solution will be appreciated. Links with some up to date tutorial will be also very welcomed.
I just forgot to mention...I bind the NSArrayController with ContentArray in Controller Content, and then I bind the NSTableView with the NSArrayController, as well as my Table Column,but I am getting empty NSTableView...no error is shown in console whatsoever.
If you use direct iVar access-- _websites-- to write the websites property's iVar, the KVO notification that the binding depends upon never happens.
If you instead use self.websites = or more explicitly [self setWebsites: ..., then you will trigger a KVO notification to the array controller that the value of the websites property has been updated.
Instead, the array controller in the Xib is unarchived and bound to websites before viewDidLoad, so at that point, websites is nil. And subsequently, you never trigger any KVO notification about websites changing value because you explicitly avoid using the websites accessor setWebsites and instead use direct instance variable access. So the AC never knows that websites changes and the table never reflects any value for websites except nil.
In general never use the instance variable to access a property's value unless you have a very good reason to do so and fully understand why you're doing so.

Access Variables in different classes and Globals in objective c

in my app i have multiple classes :D, the thing is i need to access a variable from class a in class b. For this purpose i use synthesize, i declare my variable in my .h between interface brackets, then declare a property of the variable wich is a NSString. Then i use synthesize :). But when i access it in class b i have a NSString equal to nil :§. When i breakpoint to check if the NSString get filled it works the NSLog shows the right string in the classe a, but in class b i got nil :§. Secondly i would like to have your opinion : it is better to use a global like NSUserDefaults or a old school c global declaration or this "synthesize" way of doing it ? If anyone would like to help me please, thank you a lot :).
//polymorphViewController.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "LCYDataBackedTableView.h"
#import "RootViewController.h"
#import "LCYLockSettingsViewController.h"
#import "LockScreenAppDelegate.h"
#import "MyWebViewController.h"
#import "MemberViewController.h"
#import "SharedViewController.h"
#import "UploadedViewController.h"
#interface PolymorphViewController : LCYDataBackedTableView{
NSString *secondId;
}
#property (readwrite,nonatomic) NSString *secondId;
#end
//in class a .h
#interface firstViewController : UITableViewController <UISearchBarDelegate, UISearchDisplayDelegate>{
IBOutlet UITableView * newsTable;
UISearchBar *searchBar;
UISearchDisplayController *searchDisplayController;
...
NSString *idNumber;
}
#property (readwrite, retain) NSString *idNumber;
- (void)launchIt:(NSString *)typeofdata sharedOrUploaded:(BOOL)smogogo;
#end
//in class a .m
#implementation firstViewController
#synthesize idNumber
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
idNumber = [[stories objectAtIndex: storyIndex] objectForKey: #"sizing_id"];
PolymorphViewController *polymorph = [[PolymorphViewController alloc]init];
[[self navigationController] pushViewController:polymorph animated:YES];
[polymorph viewDidLoad];
}
//in class B .m
-(void)dataByChoice: (NSString *)command{
self.title = #"Test";
myWebView = [[UIWebView alloc] initWithFrame:self.view.frame];
[self.view addSubview:myWebView];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *defaults = [prefs stringForKey:#"myKey"];
NSString *defaults2 = [prefs stringForKey:#"mySecondKey"];
NSString *username = defaults;
NSString *password = defaults2;
firstViewController *WC = [[firstViewController alloc]initWithNibName:nil bundle:nil];
NSLog(WC.idNumber); <-- equals nil :(
NSString *idName = WC.idNumber;
NSString *partialUri = [#"http://www.google.com" stringByAppendingString: idName];
NSString *finalUri = [partialUri stringByAppendingString: command];
NSURL *url = [NSURL URLWithString:finalUri];
your class be should look like this .h file
#interface Class_B : UIViewController
#property (copy,nonatomic) NSString *someValue;
#end
.m file
#implementation Class_B
#synthesize someValue = _someValue;
- (void) viewDidLoad {
_someValue = #"This is my come value";
}
#end
and calling class (Class_A) it should be like this
- (void) viewDidLoad {
Class_B *cls_b = [[Class_B alloc] initWithNibName:#"Class_B_nib" bundle:nil];
NSLog(#"Some value echo :%#",cls_b.someValue);
}
Use NSUserDefaults when you need to store locally data among sessions of the app, I would not use it just to share variables among classes.
#synthesize directive is used to generate accessors methods for properties (nowadays, unless you want to change the name of setters and getters, you don't even need it) declared with #property directive.
If you just need to access the property from another object then a property declared in the interface file (.h) is perfect. If you need to share a property across the app, I am thinking about the singleton pattern but there are definitely many ways to do it.

NSMutableArray not retaining values outside of method

Ive been trying to solve this for the past one hour, but still had no luck
I have an NSMutableArray instance variable which holds objects in the following class:The array successfully gets populated in the method i populate it in, but it shows up as empty in all other methods/classes
.h file...
#import "RedditPostItem.h"
#interface RedditRepository : NSObject
#property (nonatomic, strong) NSMutableArray *redditPosts; //<<** this
is the problem array
#property (nonatomic, strong,readwrite) NSDictionary *allJSONData;
#property (nonatomic, strong,readwrite) NSMutableData *incomingData;
- (void)getPosts;
- (void)printAllTitles;
#end
.m file
#implementation RedditRepository . . #synthesize
redditPosts=_redditPosts;
. .
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
self.redditPosts = [[NSMutableArray alloc] initWithCapacity:20];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
//parse json data
_allJSONData = [NSJSONSerialization JSONObjectWithData:_incomingData options:0 error:nil];
NSDictionary *dataDictionary = [_allJSONData objectForKey:#"data"];
NSArray *arrayOfChildren = [dataDictionary objectForKey:#"children"];
for (NSDictionary *diction in arrayOfChildren) {
NSDictionary *childrenData = [diction objectForKey:#"data"];
RedditPostItem *postItem = [[RedditPostItem alloc]initWithAPIResponse:childrenData];
//******************************* add to array.....
[self.redditPosts addObject:postItem];
}
//******* if i iterate through 'self.redditPosts' i can see everything uptill this point and the array successfully gets
populated//
}
//********* if I execute any operation from any other method in this
class or any other class...the array shows up as empty!!***//
- (void)printAllTitles{
if(self.redditPosts == nil) {
NSLog(#"array is empty....."); ///always shows up as empty for some reason<<<<<< }
}
Your array is being populated asynchronously - you're downloading from a URL in the background while your user interface continues to run in the foreground. The reason you're seeing it as empty is because it hasn't been populated yet. Your app should be written in such a way that it can deal with data not being available instantaneously and being able to update itself when the data becomes available.
If that doesn't get you on the right track, please ask a more specific question about the problem you're trying to solve.

After setting one NSManaged object to another it returns Null

I am trying to pass the selected object in my coredata from the rootviewcontroller to the edit view. The selected object is being passed but is then becoming null after the theObject=selectedObject is being called. Anyone know what im doing wrong?
This is in the edit.h
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "LearningAppDelegate.h"
#interface edit : UIViewController <UITextViewDelegate, UITableViewDelegate, UITableViewDataSource, UIActionSheetDelegate>{
UITableView *tableView;
NSManagedObject *theObject;
UITextView *messageView;
}
#property(nonatomic, retain) IBOutlet UITableView *tableView;
#property(nonatomic, retain) IBOutlet UITextView *messageView;
#property(nonatomic, retain) NSManagedObject *theObject;
-(id)initWithObject:(NSManagedObject *)selectedObject;
#end
This is in the edit.m:
-(id)initWithObject:(NSManagedObject *)selectedObject {
self = [super init];
if (nil == self) {
return nil;
}
NSLog(#"selectedObject: %#", selectedObject);
NSLog(#"selecetedObject.message: %#", [[selectedObject valueForKey:#"message"] description]);
theObject=selectedObject;
NSLog(#"theObject 1: %#", theObject);
NSLog(#"theObject.message 1: %#", [[theObject valueForKey:#"message"] description]);
return self;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
NSLog(#"theObject 2: %#", theObject);
NSLog(#"theObject.message 2: %#", [[theObject valueForKey:#"message"] description]);
messageView.text=[[theObject valueForKey:#"message"] description];
[super viewDidLoad];
}
I am actually amazed that doesn't crash for you. You're assigning the variable selectedObject into the instance variable theObject without retaining it for your own use. By accessing the instance variable directly in the assignment 'theObject=selectedObject', you're bypassing the behavior granted by the #property declaration. This means that once selectedObject is finally dealloc'd, theObject will point to garbage memory.
The correct way to do this is to put theObject = [selectedObject retain]; in the -initWithObject: method and in -viewDidLoad to access it via self.theObject rather than just theObject.
In my own usage I prefer to give instance variables names different from the actual property name to avoid confusion. For example:
#interface SomeClass : NSObject
{
#private
NSManagedObject *_theObject;
}
#property (nonatomic, retain) NSManagedObject *theObject;
...
#end
#implementation SomeClass
#synthesize theObject = _theObject
...
- (void)dealloc
{
[_theObject release], _theObject = nil;
[super dealloc];
}
#end
I only have experience with Coredata on the desktop, but the problem looks like it would be with your initWithObject method. At no point do you actually insert the new object into the managed object context. You should be using this method to make new objects:
- (id)initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context
As an example in pseudocode:
NSManagedObject *newObject = [[NSManagedObject alloc] initWithEntity:NSENTITYDESCRIPTION insertIntoManagedObjectContext:MANAGEDOBJECTCONTEXT];
[newObject setValue:#"VALUE_OF_SELECTED_OBJECT" forKey:#"APPROPRIATE_KEY"];
//REPEAT AS NECESSARY
[MANAGEDOBJECTCONTEXT save];
*Code not tested, naming conventions are ignored, etc.
The save is important. If you don't do this the object won't persist.

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.