ObjC valueForKey doesn't work for NSNumber nor UDTs - objective-c

I am trying to deep-parse my own collection of objects into an NSDictionary (for JSON).
I have a base object class, which All of my models extend, and this baseobject in turn extends NSObject:
#interface BaseObj : NSObject <DICTMaker>
- (NSMutableDictionary *) toDICT;
#end
In the method, I use objc-runtime to get a list of properties. Any property which I can get a reference to using [self valueForKey:], I go ahead and insert into my dictionary the name and value of the property.
However, what I've noticed thus far is that NSNumbers and user defined classes are not added to the dictionary! My parser most assuredly identifies them, because I have it spitting out everything to the log; but [self valueForKey:] returns nil on all NSNumbers and user defined objects.
- (NSMutableDictionary *)toDICT {
NSMutableDictionary *props = [NSMutableDictionary dictionary];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
// Both of these work, I promise:
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
NSString *propertyType = [NSString stringWithUTF8String:getPropertyType(property)];
NSLog( #"%# of Type: %#", propertyName, propertyType );
id propertyValue = [self valueForKey:propertyName];
if ( [ propertyValue respondsToSelector:#selector(toDICT:) ] )
[ props setObject:[propertyValue toDICT] forKey:propertyName ];
else if ( propertyValue )
[ props setObject:propertyValue forKey:propertyName ];
else
NSLog( #"Unable to get ref to: %#", propertyName );
}
free(properties);
return props;
}
Here is a sample object I threw at the creator:
#interface UserRegistrationLocation : BaseObj {
NSString *address, *street, *street_2, *city;
NSNumber *addr_state, *addr_zip;
}
#interface UserRegistrationContact : BaseObj {
NSString *first_name, *last_name;
NSString *p_phone_area, *p_phone_first_3, *p_phone_last_4;
NSString *s_phone_area, *s_phone_first_3, *s_phone_last_4;
}
#interface UserRegistration : BaseObj {
NSString *email, *password, *password_confirm;
NSNumber *referral;
UserRegistrationContact *primary, *secondary;
UserRegistrationLocation *address;
}
NSMutableDictionary *mydict = [myUserRegistration toDICT];
The resulting dictionary only contained entries for email, password, and password_confirm:
[11012:f803] Unable to get ref to: referral
[11012:f803] Unable to get ref to: primary
[11012:f803] Unable to get ref to: secondary
[11012:f803] Unable to get ref to: address
[11012:f803] {"user":{"password":"haxme123","password_confirm":"haxme123","email":"my#email.com"}}
Any assistance please =} !

You've only declared ivars for the values, not properties. class_copyPropertyList requires #property declarations. You could use the ivar accessing functions such as class_getInstanceVariable.
However, I think this approach is trying to be a bit too clever. I would implement a method that returns an array of keys to serialize, eg, -(NSArray *)keysForJSONSerialization. This would reveal intent more clearly and will allow you to prevent the serialzation of certain properties (which I suspect you'll want at some point).

Maybe I understood the problem wrong, but are your referral, primary and so not simply null?
If they are not in the keyvalue store, you would get an exception.
You else part is only called, if the property is found in the keyvalue store but has a nil assigned. At least from your example code, one cannot decide, if the values are set.
If I reduce your example to the code below, I get the following output
test with Value: (null)
Unable to get ref to: test
aNumber with Value: 5
test is null and will give your message "unable...". aNumber is correct. If I change test to some text, the "unable..." part vanishes. The additional member variable _noProp which is not a property does not occur here, as copyPropertyList copies only properties.
#interface TestValueForKey : NSObject {
NSString* _test;
NSString* _noProp;
NSNumber* _aNumber;
}
#property (retain) NSString* test;
#property (retain) NSNumber* aNumber;
-(void)myTest;
#implementation TestValueForKey
#synthesize test = _test;
#synthesize aNumber = _aNumber;
-(id)init
{
if( (self = [super init]) != nil) {
_test = nil;
_aNumber = [NSNumber numberWithInt:5];
}
return self;
}
-(void)myTest
{
NSMutableDictionary *props = [NSMutableDictionary dictionary];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for( i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
// Both of these work, I promise:
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
id propertyValue = [self valueForKey:propertyName];
NSLog( #"%# with Value: %#", propertyName, propertyValue );
if ( propertyValue )
[ props setObject:propertyValue forKey:propertyName ];
else
NSLog( #"Unable to get ref to: %#", propertyName );
}
free(properties);
}
#end

Related

Is it possible to use a wildcard in KVC?

I'm trying to use wildcard in KVC like this.
Is it possible?
Or Is there other ways to use a wildcard to indicate a member variable?
#interface MyClass : NSObject
#property(nonatomic, retain) NSNumber *test1;
#property(nonatomic, retain) NSNumber *test2;
#end
#implementation MyClass{
NSNumber * test1;
NSNumber * test2;
}
#synthesize test1;
#synthesize test2;
#end
using wildcard
MyClass *testClass = [[[MyClass alloc] init] autorelease];
testClass.test1 = #50;
NSLog(#"test value : %#", [testClass valueForKey:#"*1"]);
For detail codes.
A real reason i wanted is to indicate a member variable of instance by value of integer or nsnumber type.
If possible, it is easier to set values and read values of any instance.
For example of property part copy.
MyClass *testClass = [[[MyClass alloc] init] autorelease];
testClass.year_1 = #2012;
testClass.quarter_2 = #3;
testClass.month_3 = #8;
testClass.day_4 = #20;
testClass.week_5 = #4;
// copy propertys to other instance.
// Normal way
MyClass *testClassCopy = [[[MyClass alloc] init] autorelease];
testClassCopy.year_1 = testClass.year_1;
testClassCopy.quarter_2 = testClass.quarter_2;
testClassCopy.month_3 = testClass.month_3;
testClassCopy.day_4 = testClass.day_4;
// copy propertys by using wildcard
for (int j = 0; j < 4; j++) {
NSString *indicate = [NSString stringWithFormat:#"*%#", [NSNumber numberWithInteger:j + 1]];
NSNumber *sourceProperty = [testClass valueForKey:indicate];
[testClassCopy setValue:sourceProperty forKey:indicate];
}
I'll raise your wildcards by adding Regex, and by using categories:
To read about how regex works with this, please read the NSRegularExpression Class Reference.
Features:
Uses regex, for matching of a wide variety of keys
Uses a category that works on any instance
Caches key lists per class
Full KVC support (not just properties, but accessor methods & iVars too!)
Integrates flawlessly with current KVC methods (only uses the regex if the key wasn't found, improving performance)
Subclassing doesn't mess it up, like #JamesWebster's solution
Doesn't needlessly pollute the list of keys with NSObject's methods
Returns a NSDictionary of matched keys & values
Cons:
Uses regex, which is slower and more complex to understand
Slow initial lookup for a class (must iterate through all methods & iVars)
Automatically overwrites the -valueForUndefinedKey: method, so it's possible that this could break some existing code (move it to it's own method to fix).
Currently doesn't support setting of values (by design, that's a whole other bag of cats).
Can have duplicate keyPaths in the result (not the biggest of issues, but stems from the fact that KVC matching is complex, and I have to implement all of the rules)
Uses NSRegularExpression, which is only available in iOS 4 and later (not the largest of issues).
Version History:
1.0: Initial Release
So, here is the code:
NSObject+KVCRegex.h:
//
// NSObject+KVCRegex.h
// TestProj
//
// Created by Richard Ross on 8/20/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface NSObject (KVCRegex)
// custom implemenation
-(id) valueForUndefinedKey:(NSString *)key;
#end
NSObject+KVCRegex.m:
//
// NSObject+KVCRegex.m
// TestProj
//
// Created by Richard Ross on 8/20/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import "NSObject+KVCRegex.h"
#import <objc/runtime.h>
#implementation NSObject (KVCRegex)
static NSSet *keyPathsForClass(Class cls)
{
NSMutableSet *keys = [NSMutableSet set];
do
{
if (cls == [NSObject class])
{
// nothing good can come from trying to use KVC on NSObject methods
break;
}
unsigned count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i++) {
// make sure that the method returns a value
const char *methodName = sel_getName(method_getName(methods[i]));
char returnType[64];
method_getReturnType(methods[i], returnType, 64);
if (strcmp(returnType, "v") == 0)
continue;
// make sure that the method takes no args (except for self & _cmd)
if (method_getNumberOfArguments(methods[i]) == 2)
{
// add a duplicate entry for ones matching 'is'
if (strstr(methodName, "is") == methodName)
{
char *newStr = strdup(methodName + 2);
newStr[0] = tolower(newStr[0]);
[keys addObject:[NSString stringWithUTF8String:newStr]];
free(newStr);
}
[keys addObject:[NSString stringWithUTF8String:methodName]];
}
}
free(methods);
// now copy iVars
count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivars[i]);
if (strstr(ivarName, "_") == ivarName)
[keys addObject:[NSString stringWithUTF8String:ivarName + 1]]; // iVar name starting with _<key>
[keys addObject:[NSString stringWithUTF8String:ivarName]];
}
free(ivars);
} while ((cls = [cls superclass]));
return [NSSet setWithSet:keys];
}
// returns a dictionary based on 'key' as a regex
-(id) valueForUndefinedKey:(NSString *)key
{
// lookup for later use
static NSMutableDictionary *keyClassPairs;
if (!keyClassPairs)
keyClassPairs = [NSMutableDictionary dictionary];
if (!keyClassPairs[[self class]])
{
keyClassPairs[(id<NSCopying>)[self class]] = keyPathsForClass([self class]);
}
NSSet *keyPaths = keyClassPairs[[self class]];
// assume 'key' is a regex
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:key options:0 error:nil];
NSMutableArray *matches = [NSMutableArray array];
for (NSString *keyPath in keyPaths)
{
NSRange matchRange = [regex rangeOfFirstMatchInString:keyPath options:0 range:(NSRange) { 0, keyPath.length }];
if (matchRange.length == keyPath.length)
{
// we have a match
[matches addObject:keyPath];
}
}
if (matches.count)
return [self dictionaryWithValuesForKeys:matches];
else
[NSException raise:NSUndefinedKeyException format:#"Could not find a key that matches the regex in %#", key];
return nil;
}
#end
Example:
#interface MyObject : NSObject
{
#public
int normalIvar;
id _underscoreIvar;
}
#property id someProp;
#property BOOL isProperty;
#property int nativeProp;
-(void) notAKey;
-(id) aKey;
#end
#implementation MyObject
#synthesize someProp, isProperty, nativeProp;
-(void) notAKey
{
NSLog(#"Not a key!");
}
-(id) aKey
{
return #"Value";
}
#end
int main()
{
#autoreleasepool {
MyObject *obj = [MyObject new];
obj.someProp = #"a property";
obj.nativeProp = 15;
obj.isProperty = YES;
obj->normalIvar = 172;
obj->_underscoreIvar = #"Ivar";
NSString *regex = #"[a|s].*"; // match a key starting with 'a' or 's', then matching anything else after
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ aKey = 'Value', someProp = 'a property' }"
regex = #"_.*"; // match a key starting with '_', and then match anything else after
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ _underscoreIvar = 'Ivar' }"
regex = #".*"; // match any key declared for this object
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ "_underscoreIvar" = Ivar; aKey = Value; isProperty = 1; nativeProp = 15; normalIvar = 172; property = 1; someProp = "a property"; underscoreIvar = Ivar; }"
regex = #"(?i)[A-J].*"; // match (case insensitive) a key starting with A - J
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ aKey = value; isProperty = 1; }"
}
}
Though I couldn't find a way to support wildcards using the syntax you were attempting. I found this roundabout method using the Objective-C runtime!
First we get all of the properties of the class you'd like to use
#import <objc/runtime.h>
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([MyClass class], &outCount);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:outCount];
for (int i = 0; i < outCount; i++)
{
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName)
{
NSString *propertyName = [NSString stringWithUTF8String:propName];
[array addObject:propertyName];
}
}
free(properties);
Then filter out the ones you actually want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH '1'"];
[array filterUsingPredicate:predicate];
Then actually use them
for (NSString *key in array)
NSLog(#"%#", [testClass valueForKey:key]);

List of class properties in Objective-C

Is there a way to get an array of class properties of certain kind? For example if i have interface like this
#interface MyClass : NSObject
#property (strong,nonatomic) UILabel *firstLabel;
#property (strong,nonatomic) UILabel *secondLabel;
#end
can i get the reference to those labels in implementation without knowing their name?
#implementation MyClass
-(NSArray*)getListOfAllLabels
{
?????
}
#end
I know i can do it easily with [NSArray arrayWithObjects:firstLabel,secondLabel,nil], but i would like to do it with some kind of class enumeration like for (UILabel* oneLabel in ???[self objects]???)
So more precisely, you want dynamic, runtime observaion of the properties, if I got it correctly. Do something like this (implement this method on self, the class you want to introspect):
#import <objc/runtime.h>
- (NSArray *)allPropertyNames
{
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++)
{
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
free(properties);
return rv;
}
- (void *)pointerOfIvarForPropertyNamed:(NSString *)name
{
objc_property_t property = class_getProperty([self class], [name UTF8String]);
const char *attr = property_getAttributes(property);
const char *ivarName = strchr(attr, 'V') + 1;
Ivar ivar = object_getInstanceVariable(self, ivarName, NULL);
return (char *)self + ivar_getOffset(ivar);
}
Use it like this:
SomeType myProperty;
NSArray *properties = [self allPropertyNames];
NSString *firstPropertyName = [properties objectAtIndex:0];
void *propertyIvarAddress = [self getPointerOfIvarForPropertyNamed:firstPropertyName];
myProperty = *(SomeType *)propertyIvarAddress;
// Simpler alternative using KVC:
myProperty = [self valueForKey:firstPropertyName];
Hope this helps.
use attributeKeys method of NSObject.
for (NSString *key in [self attributeKeys]) {
id attribute = [self valueForKey:key];
if([attribute isKindOfClass:[UILabel class]])
{
//put attribute to your array
}
}
Check out this link. It is an objective c wrapper over objective C runtime.
You can use code like below
uint count;
objc_property_t* properties = class_copyPropertyList(self.class, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
You must include the runtime headers
#import<objc/runtime.h>
uint propertiesCount;
objc_property_t *classPropertiesArray = class_copyPropertyList([self class], &propertiesCount);
free(classPropertiesArray);
The answer by #user529758 won't work with ARC and it won't list the properties of any ancestor classes.
To fix this, you need to traverse up the class hierarchy, and use the ARC-compatible [NSObject valueForKey:] to get the property values.
Person.h:
#import <Foundation/Foundation.h>
extern NSMutableArray *propertyNamesOfClass(Class klass);
#interface Person : NSObject
#property (nonatomic) NSString *name;
#end
Person.m:
#import "Person.h"
#import <objc/runtime.h>
NSMutableArray *propertyNamesOfClass(Class klass) {
unsigned int count;
objc_property_t *properties = class_copyPropertyList(klass, &count);
NSMutableArray *rv = [NSMutableArray array];
for (unsigned int i = 0; i < count; i++)
{
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
free(properties);
return rv;
}
#implementation Person
- (NSMutableArray *)allPropertyNames {
NSMutableArray *classes = [NSMutableArray array];
Class currentClass = [self class];
while (currentClass != nil && currentClass != [NSObject class]) {
[classes addObject:currentClass];
currentClass = class_getSuperclass(currentClass);
}
NSMutableArray *names = [NSMutableArray array];
[classes enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Class currentClass, NSUInteger idx, BOOL *stop) {
[names addObjectsFromArray:propertyNamesOfClass(currentClass)];
}];
return names;
}
- (NSString*)description {
NSMutableArray *keys = [self allPropertyNames];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithCapacity:keys.count];
[keys enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
properties[key] = [self valueForKey:key];
}];
NSString *className = NSStringFromClass([self class]);
return [NSString stringWithFormat:#"%# : %#", className, properties];
}
Student.h:
#import "Person.h"
#interface Student : Person
#property (nonatomic) NSString *studentID;
#end
Student.m:
#import "Student.h"
#implementation Student
#end
main.m:
#import <Foundation/Foundation.h>
#import "Student.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
// insert code here...
Student *student = [[Student alloc] init];
student.name = #"John Doe";
student.studentID = #"123456789";
NSLog(#"student - %#", student);
}
return 0;
}
The solution of serhats is great unfortunately it doesn't work for iOS (as you mentioned) (and this question is tagged for iOS). A workaround would be to get a NSDictionary representation of the object and then access it normally as key-value pairs. I would recommend a category for NSObject:
Header-File:
#interface NSObject (NSDictionaryRepresentation)
/**
Returns an NSDictionary containing the properties of an object that are not nil.
*/
- (NSDictionary *)dictionaryRepresentation;
#end
Implementation-File:
#import "NSObject+NSDictionaryRepresentation.h"
#import <objc/runtime.h>
#implementation NSObject (NSDictionaryRepresentation)
- (NSDictionary *)dictionaryRepresentation {
unsigned int count = 0;
// Get a list of all properties in the class.
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:count];
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
NSString *value = [self valueForKey:key];
// Only add to the NSDictionary if it's not nil.
if (value)
[dictionary setObject:value forKey:key];
}
free(properties);
return dictionary;
}
#end
Borrowed from this article: http://hesh.am/2013/01/transform-properties-of-an-nsobject-into-an-nsdictionary/
This way you could do something similar as serhats mentioned:
for (NSString *key in objectDic.allKeys) {
if([objectDic[key] isKindOfClass:[UILabel class]])
{
//put attribute to your array
}
}

Write a complex array of custom structs to file Objective C

I need to save and load the contents of an array of structs, but I know that Objective C is very particular about which data types you can read/write with.
Here is my struct:
struct SCourse
{
NSMutableArray* holes; // holds integers (pars)
NSString* name;
int size;
BOOL inUse;
};
#interface CoursesManager : NSObject
{
struct SCourse courses[5];
}
What are the data types I'll need to use? Do they each have different methods needed in order to read/write? I'm just looking for a non-complex way to get all the data I need to and from a file. I could do this quite easily in a language I'm more familiar with (C++), but some of the particulars of Objective-c are still lost on me.
EDIT: Solution (thanks for the help, everyone)
-(void)applicationWillResignActive:(UIApplication *)application {
// save the courses
NSMutableArray* totalWriteArray = [[NSMutableArray alloc] initWithCapacity:MAX_COURSES];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse saveCourse = [coursesManager GetCourseAtIndex:i];
NSNumber* nInUse = [NSNumber numberWithBool:saveCourse.inUse];
NSNumber* nSize = [NSNumber numberWithInt:saveCourse.size];
NSMutableArray* writeArray = [[NSMutableArray alloc] initWithCapacity:4];
[writeArray addObject:nInUse];
[writeArray addObject:nSize];
[writeArray addObject:saveCourse.name];
[writeArray addObject:saveCourse.holes];
[totalWriteArray addObject:writeArray];
}
[totalWriteArray writeToFile:[self saveFilePath] atomically:YES];
}
And for the loading back in...
-(void)loadFile {
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists) {
NSMutableArray* totalReadArray = [[NSMutableArray alloc] initWithContentsOfFile:[self saveFilePath]];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse loadCourse = [coursesManager GetCourseAtIndex:i];
NSMutableArray* loadArray = [totalReadArray objectAtIndex:i];
NSNumber* nInUse = [loadArray objectAtIndex:0];
loadCourse.inUse = [nInUse boolValue];
NSNumber* nSize = [loadArray objectAtIndex:1];
loadCourse.size = [nSize integerValue];
NSString* inName = [loadArray objectAtIndex:2];
loadCourse.name = inName;
NSMutableArray* inHoles = [loadArray objectAtIndex:3];
loadCourse.holes = inHoles;
[coursesManager ReplaceCourseAtIndex:i With:loadCourse];
}
}
}
First thing first. You shouldn't use plain old C structures. The ARC memory management will not appreciate.
If you are familiar with C++, you should maybe use a C++ class instead, which will please the compiler and runtime. Depends on what you want to do.
Array. Use either NSArray or std::vector but please, no plain C arrays. Not sure how ARC will handle this but I suppose it will not appreciate much. Objective-C and C++ both provides all the tools you need to handle collections of whatever.
Serialization. You have several possibilities, one of them is NSCoder.
Last word, with the so called modern syntax, converting things into ObjC objects is quite easy.
BOOL b = YES;
int i = 10;
double d = 3.14;
char* s = "Pouf pouf";
You get the ObjC equivalents with the boxin' thingy:
NSNumber* bo = #( b );
NSNumber* io = #( i );
NSNumber* do = #( d );
NSString* so = #( s );
NSArray* ao = #[ #( i ), do ];
NSDictionary* = #{ #"num" : io, #"str" : #( s ) };
To write something in a file, in one gracious step:
[#{ #"bool" : bo, #"array" : #[ #"string", #10, #( 10 + 20 ) ] }
writeToFile: #"path.plist" atomically: YES];
But the question remains, what are you trying to accomplish?
One easy approach is to store these arrays in an NSMutableDictionary object and use the method:
[mutableDict writeToFile:#"path/to/file" atomically:YES];
To store the data and:
NSMutableDictionary *anotherDict = [NSMutableDictionary dictionaryWithContentsOfFile:#"path/to/file"];
To read the contents back in.
Here's what I'd suggest:
Make a custom class with the properties you want (.h file):
#import <Foundation/Foundation.h>
#interface CustomHolder : NSObject {
NSString *last;
NSString *first;
NSString *middle;
}
#property (strong, nonatomic) NSString *last;
#property (strong, nonatomic) NSString *first;
#property (strong, nonatomic) NSString *middle;
#end
And then set the .m file up so that you can encode/decode the object
#import "CustomHolder.h"
#implementation CustomHolder
#synthesize last, first, middle;
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:first forKey:#"first"];
[encoder encodeObject:last forKey:#"last"];
[encoder encodeObject:middle forKey:#"middle"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
self.first = [decoder decodeObjectForKey:#"first"];
self.last = [decoder decodeObjectForKey:#"last"];
self.middle = [decoder decodeObjectForKey:#"middle"];
}
return self;
}
#end
Then you can just
[NSKeyedArchiver archiveRootObject:obj toFile:[self saveFilePath]] to save and
[NSKeyedUnarchiver unarchiveObjectWithFile:[self saveFilePath]] to load
That's probably the most similar to using C-structs (especially because ARC doesn't let you use structs).

Is there a way to dynamically determine ivars of a class at runtime in Cocoa / Cocoa Touch?

Is there a way to obtain something like a dictionary of all key-value pairs of a class?
You'd have to roll your own using the Objective-C Runtime functions. Here's some very basic sample code. Note that getting the ivars of a class doesn't get the ivars of its superclass. You'd need to do that explicitly, but the functions are all there in the runtime.
#import <objc/objc-runtime.h>
#include <inttypes.h>
#include <Foundation/Foundation.h>
#interface Foo : NSObject
{
int i1;
}
#end
#implementation Foo
#end
#interface Bar : Foo
{
NSString* s1;
}
#end
#implementation Bar
#end
int main(int argc, char** argv)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
unsigned int count;
Ivar* ivars = class_copyIvarList([Bar class], &count);
for(unsigned int i = 0; i < count; ++i)
{
NSLog(#"%#::%s", [Bar class], ivar_getName(ivars[i]));
}
free(ivars);
[pool release];
}
I'm not sure for just ivars but if you have them defined as properties it is possible to access the available properties on a class.
I've been using SQLitePersistentObjects for a couple projects and it has some helpful code that gets the properties defined on the class to use when figuring out serialization to and from sqlite.
It uses the function class_copyPropertyList to get the available list of properties on a class.
More specifically:
+(NSDictionary *)propertiesWithEncodedTypes
{
// DO NOT use a static variable to cache this, it will cause problem with subclasses of classes that are subclasses of SQLitePersistentObject
// Recurse up the classes, but stop at NSObject. Each class only reports its own properties, not those inherited from its superclass
NSMutableDictionary *theProps;
if ([self superclass] != [NSObject class])
theProps = (NSMutableDictionary *)[[self superclass] propertiesWithEncodedTypes];
else
theProps = [NSMutableDictionary dictionary];
unsigned int outCount;
objc_property_t *propList = class_copyPropertyList([self class], &outCount);
int i;
// Loop through properties and add declarations for the create
for (i=0; i < outCount; i++)
{
objc_property_t * oneProp = propList + i;
NSString *propName = [NSString stringWithUTF8String:property_getName(*oneProp)];
NSString *attrs = [NSString stringWithUTF8String: property_getAttributes(*oneProp)];
NSArray *attrParts = [attrs componentsSeparatedByString:#","];
if (attrParts != nil)
{
if ([attrParts count] > 0)
{
NSString *propType = [[attrParts objectAtIndex:0] substringFromIndex:1];
[theProps setObject:propType forKey:propName];
}
}
}
free(propList);
return theProps;
}
This returns a dictionary of the properties - you'll need to do some investigating of the results you get back but you should be able to get what you need if you're using properties.
Yep, totally possible:
int numIvars = 0;
Ivar * ivars = class_copyIvarList([anInstanceOfAClass class], &numIvars);
NSMutableDictionary * pairs = [NSMutableDictionary dictionary];
for (int i = 0; i < numIvars; ++i) {
Ivar ivar = ivars[i];
NSString * ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];
id ivarValue = [anInstanceOfAClass valueForKey:ivarName];
[pairs setObject:ivarValue forKey:ivarName];
}
free(ivars);
NSLog(#"%#", pairs);

Outputting iVars from description method?

I am pretty sure I am just missing the point here and getting confused. Can anyone tell me how I might write a simple description for an object that will print out the value of its instance variables to the console.
Also: is there anyway to present the information as a block (i.e. if you had 10 iVars its going to be a pain getting them all to return one by one)
#interface CelestialBody : NSObject {
NSString *bodyName;
int bodyMass;
}
- (NSString *)description {
return (#"Name: %# Mass: %d", bodyName, bodyMass);
}
cheers -gary-
- (NSString*)description
{
return [NSString stringWithFormat:#"Name: %#\nMass: %d\nFoo: %#",
bodyName, bodyMass, foo];
}
Look at the answer to this question. The code is reproduced below:
unsigned int varCount;
Ivar *vars = class_copyIvarList([MyClass class], &varCount);
for (int i = 0; i < varCount; i++) {
Ivar var = vars[i];
const char* name = ivar_getName(var);
const char* typeEncoding = ivar_getTypeEncoding(var);
// do what you wish with the name and type here
}
free(vars);
As Jason wrote you should use stringWithFormat: to format strings with printf like syntax.
-(NSString*)description;
{
return [NSString stringWithFormat:#"Name: %# Mass: %d", bodyName, bodyMass];
}
To avoid writing this over and over again for many classes you could add a category on NSObject that allows you to inspect instance variables easily. This will be bad performance, but works for debugging purposes.
#implementation NSObject (IvarDictionary)
-(NSDictionary*)dictionaryWithIvars;
{
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
unsigned int ivarCount;
Ivar* ivars = class_copyIvarList([self class], &ivarCount);
for (int i = 0; i < ivarCount; i++) {
NSString* name = [NSString stringWithCString:ivar_getName(ivars[i])
encoding:NSASCIIStringEncoding];
id value = [self valueForKey:name];
if (value == nil) {
value = [NSNull null];
}
[dict setObject:value forKey:name];
}
free(vars);
return [[dict copy] autorelease];
}
#end
With this in place implementing description is also a piece of cake:
-(NSString*)description;
{
return [[self dictionaryWithIvars] description];
}
Do not add this description as a category on NSObject, or you might end up with infinite recursions.
That's not a bad idea what you had there, it's almost achievable too.
// choose a short name for the macro
#define _f(x,...) [NSString stringWithFormat:x,__VA_ARGS__]
...
- (NSString *) description
{
return _f(#"Name: %# Mass: %d", bodyName, bodyMass);
}