In my project I have a settings class with properties with custom setters that access NSUserDefaults to make everything simpler. The idea is that Settings class has
#property NSString *name
which has custom getter that gets the name value from NSUserDefaults and a setter that saves the new value there. In this way throughout the whole project I interact with the Settings class only to manage user defined preferences. The thing is that it seems way too repetitive to write all the getters and setters (I have about 50 properties), and would like to create one setter and one getter that would work for all variables. My only issue is how to get hold of the name of the variable within the setter.
The final question then is: is it possible to find out within a getter or setter for which property is the function being called?
If you have some other approach I would appreciate it too but considering that I would like to keep all the NSUserDefaults stuff in one class, I can't think of an alternative that doesnt include writing 50 getters and setters.
Thanks!
Another approach could be this.
No properties, just key value subscript.
#interface DBObject : NSObject<NSCoding>
+ (instancetype)sharedObject;
#end
#interface NSObject(SubScription)
- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;
#end
On the implementation file:
+ (instancetype)sharedObject {
static DBObject *sharedObject = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedObject = [[DBObject alloc] init];
});
return sharedObject;
}
- (id)objectForKeyedSubscript:(id)key {
return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key {
[[NSUserDefaults standardUserDefaults] setObject:obj forKeyedSubscript:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Now, you can use it like this:
// You saved it in NSUserDefaults
[DBObject sharedObject][#"name"] = #"John";
// You retrieve it from NSUserDefaults
NSLog(#"Name is: %#", [DBObject sharedObject][#"name"]);
I this this is the best approach and is what i will use in the future.
The setter and getter in this case is simple, you can do like this:
- (void)setName:(NSString *)name {
[[NSUserDefaults standardUserDefaults] setObject:name forKey:#"name"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (NSString *)name {
return [[NSUserDefaults standardUserDefaults] objectForKey:#"name"];
}
If you want to use a simple approach for all properties:
- (id)objectForKey:(NSString *)key {
return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}
- (void)setObject:(id)object forKey:(NSString *)key {
[[NSUserDefaults standardUserDefaults] setObject:object forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Instead of creating many properties, create many keys, each key is something you want to save or retrieve.
Example of keys:
static NSString *const kName = #"name";
static NSString *const kLastName = #"lastName";
I found your question very interesting and I said to myself "Challenge accepted!".
I've created this project on Github.
Basically, all you have to do is subclass the VBSettings class and then declare de properties, like this:
#interface MySettings : VBSettings
#property (strong, nonatomic) NSString *hello;
#end
The value of "hello" will be saved to NSUserDefaults with the key "hello". Example of usage:
MySettings settings = [[MySettings alloc] init];
settings.hello = "World!"; //The value is saved in NSUserDefaults
NSLog(#"%#", settings.hello); //The value is restored from NSUserDefaults.
One possibility would be to use KVO to detect when your properties change.
E.g.:
#interface Settings : NSObject
#property NSString *one;
#property NSString *two;
#end
#implementation Settings
- (instancetype)init
{
self = [super init];
if (self) {
[self addObserver:self forKeyPath:#"one" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:#"two" options:NSKeyValueObservingOptionNew context:NULL];
}
return self;
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:#"one"];
[self removeObserver:self forKeyPath:#"two"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSLog(#"Key: %#, Change: %#", keyPath, change);
}
#end
In a different class, use the standard property access:
Settings *settings = [[Settings alloc] init];
settings.one = #"something for one";
The Settings object logs:
Key: one, Change: {
kind = 1;
new = "something for one"; }
You could try to use dynamic Getter and Setter declarations as noted in this answer.
First create generic functions that you want all the properties to use:
- (id)_getter_
{
return [[NSUserDefaults standardUserDefaults] objectForKey:NSStringFromSelector(_cmd)];
}
- (void)_setter_:(id)value
{
//This one's _cmd name has "set" in it and an upper case first character
//This could take a little work to parse out the parameter name
[[NSUserDefaults standardUserDefaults] setObject:object forKey:YourParsedOutKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Then create the dynamic method generator:
+(void)synthesizeForwarder:(NSString*)getterName
{
NSString*setterName=[NSString stringWithFormat:#"set%#%#:",
[[getterName substringToIndex:1] uppercaseString],[getterName substringFromIndex:1]];
Method getter=class_getInstanceMethod(self, #selector(_getter_));
class_addMethod(self, NSSelectorFromString(getterName),
method_getImplementation(getter), method_getTypeEncoding(getter));
Method setter=class_getInstanceMethod(self, #selector(_setter_:));
class_addMethod(self, NSSelectorFromString(setterName),
method_getImplementation(setter), method_getTypeEncoding(setter));
}
Then set what strings you want to create dynamic getters and setters for:
+(void)load
{
for(NSString*selectorName in [NSArray arrayWithObjects:#"name", #"anything", #"else", #"you", #"want",nil]){
[self synthesizeForwarder:selectorName];
}
}
That will create getters and setters for any variable name that you add to the array. I'm not sure how well this will work when other classes try to call these methods, the compiler won't see them at compile time so may throw errors when you try to use them. I just combined 2 other
StackOverflow questions into this one answer for your situation.
Dynamic Getters and Setters.
Get current Method name.
As I understand it, you don't want the mental overhead of setObject:forKey: and objectForKey: method calls by the user of this class.
Here is how to get round it. I am leaving a lot of gaps for you to fill in.
Declare the property in the header file, so that callers can use it:
#property NSString *something;
#property NSString *somethingElse;
In the class file itself, declare that you are defining the properties, so that the compiler doesn't get upset:
#dynamic something,somethingElse;
In the class file, implement the methodSignatureForSelector function, like this:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{if (SelectorIsGetter(aSelector))
return [NSMethodSignature signatureWithObjCTypes:"##:"];
if (SelectorIsSetter(aSelector))
return [NSMethodSignature signatureWithObjCTypes:"v#:#"];
return nil;
}
This will tell the system to call forwardInvocation: for these selectors, and will also tell it the shape of the call that is being made.
Your implementation of SelectorIsGetter and SelectorIsSetter is up to you. You will probably use NSStringFromSelector(aSelector) to get the selector's name, and then look it up in a table of names to see if it matches any of the names of the selectors you are implementing: in this case something and somethingElse for the getters and setSomething: and setSomethingElse: for the setters.
In the class file, implement the forwardInvocation: function, like this:
-(void)forwardInvocation:(NSInvocation *)anInvocation
{if (SelectorIsGetter(anInvocation.selector))
{NSString *s=[self objectForKey:NSStringFromSelector(anInvocation.selector)];
[anInvocation setReturnValue:&s];
return;
};
if (SelectorIsSetter(anInvocation.selector))
{NSString *s;
[anInvocation getArgument:&s atIndex:2];
[self setObjectForKey:UnmangleName(NSStringFromSelector(anInvocation.selector))];
return;
};
[super forwardInvocation:anInvocation];
}
…where UnmangleName is a thoroughly tedious function that takes a string like "setSomething:" and turns it into a string like "something".
If you want to do more than just NSStrings, the extension is reasonably straightforward.
Related
I'm working on a app that can randomize love couples. Just a fun thing, okey!?!? :D
But the problem, or maybe not a problem but a thing that can be much better if I get this thing to be working. In the beginning you need to write in all the names. And thats takes some time... Should I use Core Date? I don't really knows what core data is so I'm not sure. I would love if a god come to me and wrote the full code that can remember an array even if the app and phone shuts down. I have done this in java, is that simpel that it is in java? That would be great!
//Thank, Anton
For Heavy, complex data structures you would want to use core data,
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdTechnologyOverview.html#//apple_ref/doc/uid/TP40009296-SW1
But seeing as you just want to store an array, You should look into NSUserDefaults.
NSUserDefaults will store given data as long as the app is not deleted. You will most likely want to create some kind of custom DataStorage class for this.
#interface DataStorage : NSObject <NSCoding>
#property (nonatomic, strong) NSMutableArray *arrayToStore;
+ (instancetype)sharedInstance;
- (void)save;
#end
Above is the .h file. As you can see, it follows NSCoding protocols. That provides access to methods which allow you to encode data. You will use the save method to write the data to disk.
#import "DataStorage.h"
#implementation DataStorage
#synthesize arrayOfPeople = _arrayToStore;
+ (DataStorage *)sharedInstance
{
static DataStorage *state = nil;
if ( !state )
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"DataStorageKey"];
if (data)
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[DataStorage alloc] init];
}
}
return state;
}
- (id)init{
if (self = [super init]) {
if (!_arrayToStore) {
_arrayToStore = [[NSMutableArray alloc] init];
}
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
if ([decoder decodeObjectForKey:#"DataStorageArrayToStore"]) {
_arrayToStore = [[decoder decodeObjectForKey:#"DataStorageArrayToStore"] mutableCopy];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_arrayToStore forKey:#"DataStorageArrayToStore"];
}
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#end
Here is the .m file, which pretty much evaluates to see if there is a saved instance of the class, and if not it will create one. [DataStorage sharedInstance]...
when you want to store some data, you will simply make the class available to said file, #import "DataStorage.m and then use
NSString *testData = [NSString stringWithFormat: #"Test Data String"];
[[DataStorage sharedInstance].arrayToStore addObject: testData];
[DataStorage sharedInstance] save];
Specifically, I have two different PlayingCardViewControllers that have two different cardGames. Both SetCardGameViewController and PlayingCardGameViewController inherit from a general CardVC.
Is it better practice to test in the CardVC if the class is either of its subclasses and then complete the actions needed for each subclasses? This would mean that there would be less code, but it seems like it might be confusing. Should best practice depend on just how similar the methods are between the two classes? I was asking with reference specifically using isKindOfClass:
For example if ([self isKindOfClass:[PlayingCardVC1 class]])
Since I was asked for a more specific question here's my more specific question. Would it be better to put the following method in the subclass the way I've defined it and not implement it in the subclasses? Or would it be better practice to set the method in the superclass CardVC just to nil, and simply implement it separately in each of the subclasses?
-(BOOL)insertHighScore:(HighScore*)testedHighScore
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *cardHighScores;
// check if class is SetCardVC
if ([self isKindOfClass:[SetCardGameViewController class]]){
if (![defaults objectForKey:#"setCardHighScores"]){ // if nil
cardHighScores = [[NSMutableArray alloc]init];
[defaults setObject:cardHighScores forKey:#"setCardHighScores"];
}
cardHighScores = [defaults objectForKey:#"setCardHighScores"];
}
// check if class is PlayingCardVC
else if([self isKindOfClass:[PlayingCardGameViewController class]]){
// completely ignored this thing
if (![defaults objectForKey:#"playingCardHighScores"]){ // if nil
cardHighScores = [[NSMutableArray alloc]init];
[defaults setObject:cardHighScores forKey:#"playingCardHighScores"];
}
NSLog(#"finding defaults count == %lu",(unsigned long)[(NSMutableArray *)[defaults objectForKey:#"playingCardHighScores"]count]);
cardHighScores = [defaults objectForKey:#"playingCardHighScores"];
}
//... rest of the code
Tommy's solution is a good one. Here's an alternative solution.
Declare the method in the superclass to take the key as a parameter. Then call the superclass method from the subclasses with different keys. The superclass implementation would look like this
- (BOOL)insertHighScore:(HighScore *)testedHighScore forKey:(NSString *)key
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *cardHighScores;
if (![defaults objectForKey:key]){
cardHighScores = [[NSMutableArray alloc]init];
[defaults setObject:cardHighScores forKey:key];
}
cardHighScores = [defaults objectForKey:key];
// ... rest of code
}
The subclass implementation would look like this
- (BOOL)insertHighScore:(HighScore *)testedHighScore
{
return [super insertHighScore:testedHighScore forKey:#"setCardHighScores"];
}
In general, code specific to the subclass goes in the subclass implementation, code common to all subclasses goes into the superclass implementation, and you can pass some subclass-specific information to the superclass through parameters. In the example above, the superclass doesn't need to know anything about the subclasses. All it knows is that the subclasses will give it a key to look up in NSUserDefaults.
Is it better practice to test in the CardVC if the class is either of
its subclasses and then complete the actions needed for each
subclasses?
No. That's called procedural programming.
The runtime has conditionality built into it. When you do something like:
[instance description]
... figuring out exactly which implementation of description to call based on the type of instance is automatic. So if you do something thing:
if([self isKindOfClass:[A class]])
{
... path A ...
}
if([self isKindOfClass:[B class]])
{
... path B ...
}
if([self isKindOfClass:[C class]])
{
... path C ...
}
// etc
... what you're doing is reimplementing what's already built into the runtime, but less flexible, and in an uglier and more verbose way. What you should have written was:
[self doThing]
... and let the subclasses do their own thing if and only if they want to.
So the question boils down to: is it better practice to reimplement what already exists but less elegantly? No, no it isn't.
As to your specific question, you'd probably do this:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *cardHighScores;
NSString *cardHighScoresKeyName = [self cardHighScoresKeyName];
if (![defaults objectForKey:cardHighScoresKeyName]){ // if nil
cardHighScores = [[NSMutableArray alloc]init];
[defaults setObject:cardHighScores forKey:cardHighScoresKeyName];
}
cardHighScores = [defaults objectForKey:cardHighScoresKeyName];
Or even:
...
cardHighScores = [defaults objectForKey:cardHighScoresKeyName];
if (!cardHighScores){ // if nil
cardHighScores = [[NSMutableArray alloc]init];
[defaults setObject:cardHighScores forKey:cardHighScoresKeyName];
}
Then implement - (NSString *)cardHighScoresKeyName on the subclasses.
(EDIT: though probably you want [[defaults objectForKey:cardHighScoresKeyName] mutableCopy]; if you put an array into the defaults then read it back you'll get a regular array — doesn't matter whether the original was mutable or not)
As the title says I need to use same variable in different viewcontllers.
Until this day when I want to use a variable in different viewcontllers I send that specific variable via segues but I don't think that is a good way it requires program to carry variables from one point to another and if there are 5 view controllers this can be messy.
When I search the internet I always found codes about sending variables from one to another by segues and I have read something about global variables but I am not sure that is the one.
Example:(3 view controller program)
User opens the app and at the first viewcontller clicks ''3'' button ( or 1-2-3-4-5) the int x variable become x=3.Later he choses to go on.
Second view controller writes on screen x+x user clicks to 3 viewcontroller.(code is written inside)
Third view controller writes on screen x*x user clicks to 3 view controller.(code is written inside)
As you can see I have used that x variable in different view controllers and please remember I don't want to transfer variables between them so any ideas for that and is this kind of a thing possible.
My Guess using pointers or taking some space in memory my malloc and pointing it from anotherviewcontroller might work.
Answer:As stated in answer below there are two ways this one is suitable for me.
1 view controller
int x =5;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInt:x] forKey:#"keyForTheValue"];
[defaults synchronize];
2 view controller
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
int y = [[defaults objectForKey:#"keyForTheValue"] intValue];
intyazi.text = [[NSString alloc] initWithFormat:#"%d", y];
There are several ways to avoid passing the variable from ViewController to ViewController.
But keep in mind, that in some cases it's better to pass it.
Singleton Datacontainer
One way would be using a Singleton Datacontainer:
Header (Threadsafe implementation):
#import <foundation/Foundation.h>
#interface MyManager : NSObject {
NSInteger *someProperty;
}
#property (nonatomic, retain) NSInteger *someProperty;
+ (id)sharedManager;
#end
Implementation:
#import "MyManager.h"
#implementation MyManager
#synthesize someProperty;
#pragma mark Singleton Methods
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
// Default value
someProperty = #0;
}
return self;
}
#end
NSUserDefaults
Or you could use NSUserDefaults. Which does store the value persistent in the user directory and is also available on the next startup.
Storing a value:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInt:yourIntValue] forKey:#"keyForTheValue"];
[defaults synchronize];
Loading the value:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
int yourIntValue = [[defaults objectForKey:#"keyForTheValue"] intValue];
I created a custom class to display in a TableView, I read a custom class object's array in TableView. Now, when I close the app I lose my date. I want to save that data permanently. But when I try to do so, I get the following error:
Attempt to set a non-property-list object as an NSUserDefaults
My code is:
#implementation CAChallangeListViewController
- (void)loadInitialData{
self.challangeItems = [[NSUserDefaults standardUserDefaults] objectForKey:#"challanges"];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] setObject:self.challangeItems forKey:#"challanges"];
}
Here, the self. Challange items is a NSMutable array which contains objects of type CAChallange which is a custom class with following interface.
#interface CAChallange : NSObject
#property NSString *itemName;
#property BOOL *completed;
#property (readonly) NSDate *creationDate;
#end
You can easily accomplish what you are doing by converting your object into a standard dictionary (and back when you need to read it).
NSMutableArray *itemsToSave = [NSMutableArray array];
for (CAChallange *ch in myTableItems) {
[itemsToSave addObject:#{ #"itemName" : ch.itemName,
#"completed" : #(ch.completed),
#"creationDate" : ch.creationDate }];
}
You can use NSKeyedArchiver to create an NSData representation of your object. Your class will need to conform to the NSCoding protocol, which can be a bit tedious, but the AutoCoding category can do the work for you. After adding that to your project, you can easily serialize your objects like this:
id customObject = // Your object to persist
NSData *customObjectData = [NSKeyedArchiver archivedDataWithRootObject:customObject];
[[NSUserDefaults standardUserDefaults] setObject:customObjectData forKey:#"PersistenDataKey"];
And deserialize it like this:
NSData *customObjectData = [[NSUserDefaults standardUserDefaults] objectForKey:#"PersistenDataKey"];
id customObject = [NSKeyedUnarchiver unarchiveObjectWithData:customObjectData];
Your CAChallange object has a pointer to a BOOL which is not a suitable type for a plist:-
#property BOOL *completed
This is probably causing the error. You cannot store raw pointers in a plist.
I am making a NSObjectClass that has a method in it that returns self.
This is what it looks like roughtly
storageclass.h
// storageclass vars go here
- (storageclass)assignData:(NSDictionary *)dictionary;
storageclass.m
//#synthesise everything
- (storageclass)assignData:(NSDictionary *)dictionary {
//assign values from dictionary to correct var types (i.e. NSString, Int, BOOL)
//example
Side = [dictionary valueForKey:#"Side"];
return self;
}
Then what I want to do is use this class by passing a NSDictionary var through its method to return a object of type storageclass that I can then use to access the vars using dot notation.
this is how I am trying to access this class at the moment
accessorViewController.h
storageclass *store;
#property (strong, nonatomic) storageclass *store;
accessorViewController.m
#synthesize store;
- (void)getstoreready {
[store assignData:someDictionary];
nslog(#"%#", store);
}
this NSLog returns nothing and in the debugger all of stores class vars are empty showing nothing has been assigned. I am 100% positive the dictionary vars being used in the assignData method have the correct valueForKey values.
I think it has something to do with how I am using it here [store assignData:someDictionary]; how do i catch the turned data so I can use it?
any help would be appreciated.
The store object is never initialized so it will be nil thats obvious isn't it. Initialize the store object first, then call its instance methods onto it. And by doing that, you'll have a storageclass object which is properly assigned with some dictionary already.
And if you want to have a storageclass object like your code shows, you should make your (storageclass)assignData:(NSDictionary *)dictionary method a class method instead of an instance method by putting a + sign
+(storageclass*)assignData:(NSDictionary *)dictionary;
Then properly initialize it and assign the data (dictionary to variables) accordingly and return it to the caller. For example :-
in .m file
+(storageclass*)assignData:(NSDictionary *)dictionary{
storageclass *test = [[storageclass alloc] init];
if (test) {
test.someDict = dictionary;
}
return test;
}
Then use this class method in your view controller as
- (void)getstoreready {
store = [storageClass assignData:someDictionary];
nslog(#"%#", store);
}
Also Do follow the naming convention for classes and instances. A class's name must start with a capital letter only and the opposite for any class instances.
In User.h
#interface User : NSObject
#property (nonatomic, copy) NSString *name;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)usersFromArray:(NSArray *)array;
#end
In User.m
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
if (dictionary)
{
self.name = dictionary[#"kUserName"];
}
}
return self;
}
+ (NSArray *)usersFromArray:(NSArray *)array
{
NSMutableArray *users = [NSMutableArray array];
for (NSDictionary *dict in array) {
User *user = [[User alloc]initWithDictionary:dict];
[users addObject:user];
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES];
return [users sortedArrayUsingDescriptors:#[descriptor]];
}
In ViewController.m
import "User.h"
self.currentArray = [User usersFromArray:array];