Outputting iVars from description method? - objective-c

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);
}

Related

Objective C - Get argument types of a method?

At runtime I need to be able to get the argument types of a method. The following is what gets printed:
I have read on other threads that at run-time time objective c treats all objects passed to a method as arguments as id. If this approach doesn't work any other suggestions on a way to read argument types?
Log
2014-02-07 15:47:08.962 OCInjection[55727:70b] #
2014-02-07 15:47:08.964 OCInjection[55727:70b] :
Code
Class class = NSClassFromString(injectionBinding);
unsigned int methodCount;
Method *methodList = class_copyMethodList(class, &methodCount);
for (int i = 0; i < methodCount; i++)
{
Method method = methodList[i];
SEL selector = method_getName(method);
NSMethodSignature *signature = [class instanceMethodSignatureForSelector:selector];
NSUInteger numberOfArguments = [signature numberOfArguments];
for (int i=0 ; i<numberOfArguments ; i++)
{
NSString *type = [NSString stringWithUTF8String:[signature getArgumentTypeAtIndex:i]];
NSLog(type);
}
}
According to
-getArgumentTypeAtIndex:
and
Decode Class from #encoded type string
I think there is no method to get the "real" argument type.
Doesn't seem like it's possible to do this. I ended up using a proxy object to send the message to, and capture it. Probably not the ideal way, but I haven't found a better solution.
#interface DIContructorInjectorProxy()
#property (nonatomic, strong) id realObject;
#end
#implementation DIContructorInjectorProxy
#define Inject(x) [DIContructorInjectorProxy _injectMacro:x]
- (id)initWithClass:(Class)class
{
self.realObject = [[class alloc] init];
}
+ (id)_injectMacro:(id)x
{
if ([x isKindOfClass:NSClassFromString(#"Protocol")])
return NSStringFromProtocol(x);
else
return NSStringFromClass(x);
}
- (id)withConstructor
{
// Just making the method call for defining a constructor more readable by a call to this method first
return self;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSMutableString *selectorName = [NSStringFromSelector(anInvocation.selector) mutableCopy];
NSUInteger numberOfColonsInMethodName = [selectorName replaceOccurrencesOfString:#":"
withString:#":"
options:NSLiteralSearch
range:NSMakeRange(0, selectorName.length)];
[anInvocation retainArguments];
NSMutableArray *argumentsPassedToSelector = [NSMutableArray array];
for (int i=2 ; i<numberOfColonsInMethodName+2 ; i++)
{
NSString *argument;
[anInvocation getArgument:&argument atIndex:i];
[argumentsPassedToSelector addObject:[NSString stringWithFormat:#"%#", argument]];
}
// Store arguments somewhere
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [self.realObject methodSignatureForSelector:aSelector];
}
#end
How the user uses this to define method arguments
[self bindProtocol:#protocol(DataStorage) toClass:[InMemoryDataStorage class]];
// withConstructor returns an appropriate proxy object
// Then when the init method is called, it calls forwardInvocation,
// and from there I save all the info I need about the method and arguments
(void)[[[self bindProtocol:#protocol(GoogleClient) toClass:[GoogleClientEngine class]] withConstructor]
initWithDataStorage:Inject(#protocol(DataStorage))];

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]);

ObjC valueForKey doesn't work for NSNumber nor UDTs

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

Return value through parameter NSInteger

I need to read 3 values from a database and return them in a method. I'm having trouble to understand how to return values using NSInteger types. This is the code:
NSString* GetVerbInfinitive(FMDatabase* mydb, NSString* myConjugation, NSInteger verbal_time, NSInteger verbal_person)
{
NSMutableString *ret = [[NSMutableString alloc] initWithString:#""];
FMResultSet *rs = [mydb executeQuery:#"select verb_text, conjugation_verbal_time, conjugation_verbal_person from verbs where verb_conjugation = ?",[[myConjugation lowercaseString] precomposedStringWithCanonicalMapping]];
if ([rs next])
{
if (![rs columnIndexIsNull:0])
{
[ret setString:[rs stringForColumn:#"verb_text"]];
verbal_time = [rs intForColumn:#"conjugation_verbal_time"];
verbal_person = [rs intForColumn:#"conjugation_verbal_person"];
}
else
{
NSLog(#"GetVerbInfinitive: verb '%#' has no infinitive defined", myConjugation);
}
}
[rs close];
return [ret autorelease];
}
The NSInteger values work only inside the method when I return to the calling method they are lost. I believe I should pass these NSInteger by reference, but I don't know how. I don't want to create a type structure for this.
Thanks,
Miguel
You have to dereference them as pointers. Here's an example (this goes in the code when you're calling the method):
NSInteger verbal_time, verbal_person;
GetVerbInfinitive(....., &verbal_time, &verbal_person);
Now in your getverbinfinitivemethod:
NSString* GetVerbInfinitive(FMDatabase* mydb, NSString* myConjugation, NSInteger *verbal_time, NSInteger *verbal_person)
{
...
...
*verbal_time = [rs intForColumn:#"conjugation_verbal_time"];
*verbal_person = [rs intForColumn:#"conjugation_verbal_person"];
...
...
}
Notice the change in the signature line to make those two NSIntegers pointers.

How can I inspect an objective c object?

In ruby, I can .inspect from an object to know the details. How can I do the similar thing in objective c? Thank you.
If you just want something to print you can use description as said before.
I'm not a Ruby guy myself, but if I understand this correctly .inspect in Ruby prints all the instance variables of an object. This is not something built into Cocoa. If you need this you can use the runtime system to query this information.
Here is a quick category I put together which does that:
#import <objc/objc-class.h>
#interface NSObject (InspectAsInRuby)
- (NSString *) inspect;
#end
#implementation NSObject (InspectAsInRuby)
- (NSString *) inspect;
{
NSMutableString *result = [NSMutableString stringWithFormat: #"<%#:%p", NSStringFromClass( [self class] ), self ];
unsigned ivarCount = 0;
Ivar *ivarList = class_copyIvarList( [self class], &ivarCount );
for (unsigned i = 0; i < ivarCount; i++) {
NSString *varName = [NSString stringWithUTF8String: ivar_getName( ivarList[i] )];
[result appendFormat: #" %#=%#", varName, [self valueForKey: varName]];
}
[result appendString: #">"];
free( ivarList );
return result;
}
#end
-[NSObject description] provides a basic description of an object (similar to toString in Java--I don't really know about .inspect in Ruby). description is automatically called in when you print an object in NSLog (e.g. NSLog(#"#%", myObject)).
For other introspection methods, I'd suggest looking at the NSObject reference. There are also a lot of things you can do directly with the Objective-C runtime.
Just print it out with NSLog
NSLog(#"%#", myObject);
It will automatically call the object's description method. If this is a class you created, you will want to define that (return an NSString with the info).
Take a look at this question.
The description method of NSObject is similar to inspect
In your NSObject's h file write this :
(NSDictionary *)dictionaryRepresentation;
In your NSObject's m file write this :
(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; }
(NSString *)description {
return [NSString stringWithFormat:#"%#", [self dictionaryRepresentation]]; }