I have defined a dictionary that I would like to pass around to various other objects. When they receive this dictionary, they need to know how it is defined so they can unpack it to get its values. I've been using #define's in my public header to define the keys. That way, I get edit-time compiler checking to ensure I don't use a bum key. But is there some other, more standard way to declare the interface to a defined dictionary so that other objects will get compile errors if they try to use undefined keys?
Better than #define is to use constant NSStrings. In your header:
extern NSString * const MyDictionaryFribbleKey;
(That's a constant pointer to an NSString.) And in your implementation:
NSString * const MyDictonaryFribbleKey = #"theFribble";
This is how the frameworks export constant strings. This won't stop the use of invalid keys, nothing will really do that (it's C, you can bypass anything), but it raises the bar higher.
Instead of using a dictionary why not use an object?
The example below shows that there really isn't much extra work involved. Plus you gain the advantages of actually using objects.
Object Set up
#interface MyClass : NSObject
#property (nonatomic, strong) NSString *myString;
#property (nonatomic, assign) CGFloat myFloat;
#end
#implementation MyClass
#synthesize myString = _myString;
#synthesize myFloat = _myFloat;
#end
Object use
MyClass *myClass = [[MyClass alloc] init];
// Set
myClass.myString = #"aa";
myClass.myFloat = 12.0f;
// Get
NSString *myString = myClass.myString
CGFloat myFloat = myClass.myFloat;
NSDictionary Set up
.h
extern NSString * const MYClassKeyMyString;
extern NSString * const MYClassKeyMyFloat;
.m
NSString * const MYClassKeyMyString = #"MYClassKeyMyString";
NSString * const MYClassKeyMyFloat = #"MYClassKeyMyFloat";
NSDictionary use
NSDictionary *myDict = [[NSMutableDictionary alloc] init];
// Set
[myDict setObject:#"aa" forKey:MYClassKeyMyString];
[myDict setObject:[NSNumber numberWithFloat:12.0f] forKey:MYClassKeyMyFloat];
// Get
NSString *myString = [myDict objectForKey:MYClassKeyMyString];
CGFloat myFloat = [[myDict objectForKey:MYClassKeyMyFloat] floatValue];
That's a pretty standard way. You can also enumerate the keys of a dictionary using the -enumerateKeys method.
Related
I'm building a class that sets properties of a subclass dynamically at runtime from a plist, that works like this:
Example
You declare your properties in a subclass to match the names of keys:
#import "PlistModel.h"
#interface CustomModel : PlistModel
#property (strong, nonatomic) NSString * StringPropertyKey;
#property (strong, nonatomic) NSDate * DatePropertyKey;
#property (strong, nonatomic) NSArray * ArrayPropertyKey;
#property (strong, nonatomic) NSDictionary * DictionaryPropertyKey;
#property int IntPropertyKey;
#property BOOL BoolPropertyKey;
#property float FloatPropertyKey;
#end
That's it! The values are automatically populated at runtime without any additional code:
[CustomModel plistNamed:#"CustomModel" inBackgroundWithBlock:^(PlistModel *plistModel) {
CustomModel * customModel = (CustomModel *)plistModel;
NSLog(#"StringProperty: %#", customModel.StringPropertyKey);
NSLog(#"DateProperty: %#", customModel.DatePropertyKey);
NSLog(#"ArrayProperty: %#", customModel.ArrayPropertyKey);
NSLog(#"DictionaryProperty: %#", customModel.DictionaryPropertyKey);
NSLog(#"IntProperty: %i", customModel.IntPropertyKey);
NSLog(#"BoolProperty: %#", customModel.BoolPropertyKey ? #"YES" : #"NO");
NSLog(#"FloatProperty: %f", customModel.FloatPropertyKey);
}];
Problem
I set the properties at runtime by generating a selector and calling it with the value I want to set like this:
SEL propertySetterSelector = NSSelectorFromString(#"set<#PropertyName#>:");
void (*func)(id, SEL, id) = (void *)imp;
func(self, propertySetterSelector, objectToSet);
But, if for some reason a property is readonly the selector won't exist, so I'm looking for an alternative. I've found a way to identify that a property is readonly here:
- (NSMutableArray *) getPropertyNames {
// Prepare Package
NSMutableArray * propertyNames = [NSMutableArray array];
// Fetch Properties
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
// Parse Out Properties
for (int i = 0; i < count; i++) {
objc_property_t property = properties[i];
const char * name = property_getName(property);
// NSLog(#"Name: %s", name);
const char * attributes = property_getAttributes(property);
NSLog(#"Attributes: %s", attributes);
NSString * attributeString = [NSString stringWithUTF8String:attributes];
NSArray * attributesArray = [attributeString componentsSeparatedByString:#","];
if ([attributesArray containsObject:#"R"]) {
// is ReadOnly
NSLog(#"%s is read only", name);
// -- CAN I SET THE PROPERTY HERE? -- //
// property = #"Set"; ?
}
// Add to our array
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
// Free our properties
free(properties);
// Send it off
return propertyNames;
}
Perhaps if there's a way to set a objc_property_t ref directly.
Update
Through comments, I've realized there's some confusion. I think the core of my question is whether or not its possible to set an unknown property at runtime another way besides calling the selector like I'm doing.
Resources
Full Project: Here!
CodeReview Post that prompted this question: Here!
SetValue: forKey: Update
I have a readonly property:
#property (readonly) NSString * readOnlyProperty;
I declare this in the class:
+ (BOOL) accessInstanceVariablesDirectly {
return YES;
}
I call this:
[self setValue:#"HI" forKey:[NSString stringWithUTF8String:name]];
// -- OR -- //
[self setValue:#"HI" forKey:[NSString stringWithFormat:#"_%#",[NSString stringWithUTF8String:name]]];
Either way, value is still null.
UPDATE
You can try using setValue:forKey: for a readonly property, if the target class has defines the class method accessInstanceVariablesDirectly to return YES and if the property stores its value in a conventionally-named instance variable. See “ Default Search Pattern for setValue:forKey:” in the Key-Value Coding Programming Guide. KVC will unbox a primitive value if necessary.
ORIGINAL
It is not possible to set a property except by calling the property's setter, because a property is defined as a getter and optionally a setter. You can use the setValue:forKey: method of Key-Value Coding (KVC), and that's simpler and more reliably than constructing the setter name yourself, but under the covers that still calls the property's setter.
It is possible to set an instance variable using the Objective-C runtime. Look at the class_getInstanceVariable, object_setInstanceVariable, and object_setIvar methods.
You can guess that a property's value is stored in an instance variable whose name is the property named with an _ prefix. However, this is only a convention. The compiler uses the convention for auto-synthesized properties, but the compiler does not enforce the convention.
I'm learning about Cocoa/Objective-C, and I'm trying to be able to modify a NSString and edit individual chars in the string. I've managed to be able to change it to an array of chars, and change it back, but when I try to do c[0] = 'b', it says, "read-only variable is not assignable". I've included the .m file and .h file.
This is the .m file:
import "AppDelegate.h"
#implementation AppDelegate
#synthesize textField, myLabel;
-(IBAction)changeLabel:(id)sender {
NSString *message = [[NSString alloc] initWithFormat:#"Hello, %#!", [textField stringValue]];
NSString *message2 = [[NSString alloc] initWithFormat:#"%# This part was added on afterwards.", message];
const char *c = [message2 UTF8String];
c[0] = 'b';
NSString *output;
output = [NSString stringWithCString:c encoding:NSASCIIStringEncoding];
[myLabel setStringValue:output];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
}
#end
This is the .h file:
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSTextField *textField;
#property (weak) IBOutlet NSTextField *myLabel;
-(IBAction)changeLabel:(id)sender;
#property (weak) IBOutlet NSTextField *changeButtonText;
#end
I'm assuming the .h file isn't relevant, but I though it'd include it.
NSString is a container for an immutable string. Taken from official reference:
The NSString class declares the programmatic interface for an object that manages immutable strings. An immutable string is a text string that is defined when it is created and subsequently cannot be changed.
Basically, by using just cocoa, you can generate new NSStrings by replacing characters in existing strings, for example with:
- (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
but you can't modify the same object. Exactly for this reason UTF8String returns a const char* which is an immutable pointer to a sequence of character. That's why you get the error.
You can use the provided NSString methods to generate new strings or copy the const char* C string to a char* (through, for example, strdup).
c is a const char* -> its initialising value is its value throughout the whole runtime. Maybe you should erase the const keyword.
In Objective-C an NSString instance is not modifiable, for a modifiable string you need to NSMutableString. Alternatively you can create a new string from an old one. Unfortunately NSMutableString/NSString does not have the inverse of characterAtIndex:, to change a single character you need to use stringByReplaceCharactersInRange:withString:/replaceCharactersInRange:withString: where you supply a range - location & length - of the characters to change. Here is part of your code re-written to use these, first creating a new string:
NSString *message2 = [[NSString alloc] initWithFormat:#"%# This part was added on afterwards.", message];
NSString *output = [message2 stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:#"b"];
This first option creates a new string. Using a modifiable string:
NSMutableString *message2 = [[NSMutableString alloc] initWithFormat:#"%# This part was added on afterwards.", message];
[message2 replaceCharactersInRange:NSMakeRange(0,1)
This second option changes the string referenced by message2.
Whether you create new strings or use a modifiable string will depend on how many changes you need to make and whether you need to keep the original string.
I'm steadily getting the hang of Objective-C, but am still very much a beginner and have a beginner-level question hopefully someone could shed some light on:
If I have a very simple project and want to set a constant that I'll use throughout—say, a NSDictionary with keys being month names and values being days in that month—how is this done? (I.e., what command form and where to put it?)
NOTE: If this example is already possible using built-in functions, perhaps we could just pretend it isn't for the purposes of this question ;)
The answer depends on the type of your constant. If all you need is an int or a double, you can use preprocessor and the #define CONST 123 syntax. For Objective C classes, however, you need to do a lot more work.
Specifically, you would need to hide the constant behind a class method or a free-standing function. You will also need to add a prototype of that method or function in the header file, provide a function-scoped static variable to store the constant, and add code to initialize it.
Here is an example using a simple NSDictionary:
Header: MyConstants.h
#interface MyConstants
+(NSDictionary*)getConstDictionary;
#end
Implementation: MyConstants.m
+(NSDictionary*)getConstDictionary {
static NSDictionary *inst = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
inst = #{
#"key1": #"value1",
#"key2": #"value2",
#"key3": #"value3"
};
});
return inst;
}
Usage:
NSString *val = [[MyConstants getConstDictionary] objectForKey:#"key2"];
The accepted answer is correct, but if you prefer operate with variable (not trough method). I can suggest this pattern:
#implementation MyClass
static NSSet *mySetOfObjects;
+ (void)initialize {
mySetOfObjects = [[NSSet alloc] initWithObjects:#"one", #"two", #"three", nil];
}
// Example usage:
+ (BOOL)isRecognizedString:(NSString *)searchItem {
return [mySetOfObjects containsObject:searchItem];
}
#end
As for me - it looks better.
For more details the source is here.
Let's assume you want to declare an NSString constant in your class that holds a url. In your header .h file you will need the following:
#import
extern NSString * const BaseURL;
#interface ClassName : NSObject {
You will then need to set it's value in your main .m file as follows:
#import "ClassName.h"
NSString * const BaseURL = #"http://some.url.com/path/";
#implementation ClassName
You can now access this constant throughout your class or subclasses. Here's an example of usage:
NSString *urlString = [NSString stringWithFormat:#"%#%#", BaseURL, #"filename.html"];
If your constants are strings then you can use this form:
MyObject.h:
extern NSString *const kJanuary;
....
extern NSString *const kDecember;
#interface MyObject : NSObject
{
...
}
#end
MyObject.m:
NSString *const kJanuary = #"January";
....
NSString *const kDecember = #"December";
#implementation MyObject
....
#end
You can then use the constant kJanuary, for example, from anywhere when using your class.
Is it possible to create something like a C struct for Objective-C? I need to be able to use it in an NSArray so it cannot be a traditional struct. Right now I am declaring a whole class just to accomplish this and I was wondering if there is a simpler way.
What I currently have:
#interface TextureFile : NSObject
#property NSString *name;
#property GLKTextureInfo *info;
#end
#implementation TextureFile
#synthesize name = _name;
#synthesize info = _info;
#end
NSMutableArray *textures;
What I want to do:
typedef struct {
NSString *name;
GLKTextureInfo *info;
} TextureFile;
NSMutable array *textures;
It depends what kind of data you're using, the example you are using in your question seems okay for a struct.
If you need to store a C struct in an NSArray, which requires an object, you can convert the C-struct to NSValue and store it like that, you then convert back to its C struct type when you read it.
Check the Apple Documentation.
Given this struct:
typedef struct {
NSString *name;
GLKTextureInfo *info;
} TextureFile;
To store it:
TextureFile myStruct;
// set your stuct values
NSValue *anObj = [NSValue value:&myStruct withObjCType:#encode(TextureFile)];
NSArray *array = [NSArray arrayWithObjects:anObj, nil];
To read it again:
NSValue *anObj = [array objectAtIndex:0];
TextureFile myStruct;
[anObj getValue:&myStruct];
I'd like to JSON-serialize my own custom classes. I'm working in Objective-C / iOS5.
I'd like something to do the following:
Person* person = [self getPerson ]; // Any custom object, NOT based on NSDictionary
NSString* jsonRepresentation = [JsonWriter stringWithObject:person ];
Person* clone = [JsonReader objectFromJson: jsonRepresentation withClass:[Person Class]];
It seems that NSJSONSerialization (and several other libraries) require the 'person' class to be based on NSDictionary etc. I want something that will serialize any custom object that I care to define (within reason).
Let's imagine Person.h looks like this:
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property NSString* firstname;
#property NSString* surname;
#end
I'd like the generated JSON for an instance to look similar to the following:
{"firstname":"Jenson","surname":"Button"}
My app uses ARC. I need something that will both serialise and deserialize using objects.
Many thanks.
This is a tricky one because the only data you can put into JSON are straight up simple objects (think NSString, NSArray, NSNumber…) but not custom classes or scalar types. Why? Without building all sorts of conditional statements to wrap all of those data types into those type of objects, a solution would be something like:
//at the top…
#import <objC/runtime.h>
NSMutableDictionary *muteDictionary = [NSMutableDictionary dictionary];
id YourClass = objc_getClass("YOURCLASSNAME");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(YourClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
SEL propertySelector = NSSelectorFromString(propertyName);
if ([classInstance respondsToSelector:propertySelector]) {
[muteDictionary setValue:[classInstance performSelector:propertySelector] forKey:propertyName];
}
}
NSError *jsonError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:muteDictionary options:0 error:&jsonError];
This is tricky, though because of what I stated before. If you have any scalar types or custom objects, the whole thing comes tumbling down. If it's really critical to get something like this going, I'd suggest looking into investing the time and looking at Ricard's links which allow you to see property types which would assist on the conditional statements needed to wrap the values into NSDictionary-safe objects.
Now you can solve this problem easily using JSONModel. JSONModel is a library that generically serialize/deserialize your object based on Class. You can even use non-nsobject based for property like int, short and float. It can also cater nested-complex JSON.
Deserialize example. By referring to your example, in header file:
#import "JSONModel.h"
#interface Person : JSONModel
#property (nonatomic, strong) NSString* firstname;
#property (nonatomic, strong) NSString* surname;
#end
in implementation file:
#import "JSONModelLib.h"
#import "yourPersonClass.h"
NSString *responseJSON = /*from somewhere*/;
Person *person = [[Person alloc] initWithString:responseJSON error:&err];
if (!err)
{
NSLog(#"%# %#", person.firstname, person.surname):
}
Serialize Example. In implementation file:
#import "JSONModelLib.h"
#import "yourPersonClass.h"
Person *person = [[Person alloc] init];
person.firstname = #"Jenson";
person.surname = #"Uee";
NSLog(#"%#", [person toJSONString]);
maybe this can help JLObjectStrip.
its the same as what jacob said but it iterates even to the property of the class. this will give you dictionary/array then just use sbjson/jsonkit or what ever you prefer to construct your json string.
Try this one BWJSONMatcher
It's really simple as well as convenient.
...
NSString *jsonString = #"{your-json-string}";
YourValueObject *dataModel = [YourValueObject fromJSONString:jsonString];
NSDictionary *jsonObject = #{your-json-object};
YourValueObject *dataModel = [YourValueObject fromJSONObject:jsonObject];
...
YourValueObject *dataModel = instance-of-your-value-object;
NSString *jsonString = [dataModel toJSONString];
NSDictionary *jsonObject = [dataModel toJSONObject];
...
What i do for my objects is i have a method called "toDict" that return a nsdictionary. IN this method i set all attributes i need/want into the dictionary for example
[user setObject:self.first_name forKey:#"first_name"];
[user setObject:self.last_name forKey:#"last_name"];
[user setObject:self.email forKey:#"email"];