Correct way to find out all the superclasses of a Class in Objective-C? - objective-c

I'm a novice, and I seem to be getting multiple errors on this.
All I want is a for or while loop to print out all the superclasses of a certain Class.
Here is the pseudocode of what I want:
IDontKnowWhatClass *superClassName;
while (superClassName != nil)
{
superClassName = [[superClassName class] superclass];
NSLog(print the name);
}

Try this
Class superClassName = [self class];
while (superClassName != nil)
{
superClassName = [superClassName superclass];
NSLog(#"%#",NSStringFromClass(superClassName));
}
If you know a class itself like NSString then,
Class superClassName = [NSString class];
You can store the class name to a string like
NSString *className = NSStringFromClass(superClassName);
And if you want to create an object of the class from which class name is stored in a string like
id object = [[NSClassFromString(className) alloc] init];

Use NSObject method names as :
objc_property_t class_getProperty(Class cls, const char *name)
//Returns a property with a given name of a given class.
Keep on finding till you get the NSObject as it is supermost class
Use this method to check for equality :
- (BOOL)isEqual:(id)object;
or
[object isKindOfClass:[SomeClass class]]
Documentation here
This will work

You can call superclass method on your current class until it gets equal to Nil (that will happen for root class, i.e. NSObject).
Class c = [IDontKnowWhatClass class];
while (c)
{
NSLog(#"%#", NSStringFromClass(c));
c = [c superclass];
}

NSString* classString = #"IDontKnowWhatClass";
Class class = NSClassFromString(classString);
while (class != nil){
NSLog(#"%#", [class description]);
class = [class superclass];
}

Related

iOS - Runtime Dynamic Class not key-value coding compliant

I've been getting up to speed on Dynamic Class creation at runtime in Objective-C and have hit an issue I can't quite seem to work around. After a good deal of searching around, I'm still puzzled.
The issue here is making a:
[runtimeClassInstance setValue:age forKey:#"age"];
call to an object instantiated from:
id runtimeClassInstance = [[NSClassFromString(#"Employee") alloc] init];
Which was created by
[NewClassMaker buildClassFromDictionary:#[#"FirstName", #"LastName",\
#"Age", #"Salary"]
withName:#"Employee"];
This is croaking with an NSUnknownKeyException:this class is not key value coding-compliant for the key age error.
If I use the following invocation approach, things work just fine:
SEL setAgeSelector = NSSelectorFromString(#"setAge");
NSInvocation *call = [NSInvocation invocationWithMethodSignature:[[NSClassFromString(#"Employee") class] instanceMethodSignatureForSelector:setAgeSelector]];
call.target = runtimeClassInstance;
call.selector = setAgeSelector;
NSNumber *age = #(25);
[call setArgument:&age atIndex:2];
[call invoke];
Now that static method to create the class is as such:
+(NSDictionary*)buildClassFromDictionary:(NSArray*)propNames withName:(NSString*)className
{
NSMutableDictionary* keys = [[NSMutableDictionary alloc]init];
Class newClass = NSClassFromString(className);
if(newClass == nil)
{
newClass = objc_allocateClassPair([NSObject class], [className UTF8String], 0);
// For each property name, add a new iVar, getter, and setter method for it.
for(NSString* key in propNames)
{
NSString* propName = [self propName: key];
NSString* iVarName = [self ivarName:propName];
class_addIvar(newClass, [iVarName UTF8String] , sizeof(NSObject*), log2(sizeof(NSObject*)), #encode(NSObject));
objc_property_attribute_t a1 = { "T", "#\"NSObject\"" };
objc_property_attribute_t a2 = { "&", "" };
objc_property_attribute_t a3 = { "N", "" };
objc_property_attribute_t a4 = { "V", [iVarName UTF8String] };
objc_property_attribute_t attrs[] = { a1, a2, a3, a4};
class_addProperty(newClass, [propName UTF8String], attrs, 4);
class_addMethod(newClass, NSSelectorFromString(propName), (IMP)getter, "##:");
class_addMethod(newClass, NSSelectorFromString([self setterName:propName]), (IMP)setter, "v#:#");
[keys setValue:key forKey:propName];
}
Class metaClass = object_getClass(newClass);
// This method is returning NO on purpose to find out why there is no key.
class_addMethod(metaClass, #selector(accessInstanceVariablesDirectly), (IMP)accessInstanceVariablesDirectly, "B#:");
// Auxilliary methods added to the new class instance so the accessor dynamic methods above can work.
// Not sure if the initial impl of this class maker class worked.
class_addMethod(newClass, #selector(ivarName:), (IMP)ivarNameForString, "##:#");
class_addMethod(newClass, #selector(propName:), (IMP)propNameForString, "##:#");
class_addMethod(newClass, #selector(propNameFromSetterName:), (IMP)propNameFromSetterNameString, "##:#");
// Add a customized description dynamic method to this class. It will dump out any list of properties added
// to the object during init here.
Method description = class_getInstanceMethod([NSObject class],
#selector(description));
const char *types = method_getTypeEncoding(description);
// now add
class_addMethod(newClass, #selector(description), (IMP)Description, types);
objc_registerClassPair(newClass);
}
return keys;
}
There are three possibilities here that I am stewing over:
I have missed some crucial step here to get these properties and
their dynamic accessors working in a key-value compliant fashion
If I do allow direct iVar access, is the retain for the value not
happening?
Is the NSInvocation approach the best way?
The setter method is named setAge. setValue:forKey: searches for setAge: with a colon. Add a colon at the end of the name of the setter method. In Objective-C the colons are part of the method names.

Creating NSString results in null

I am trying to initialize an NSString. I tried initWithString: but this becomes invalid because the NSString becomes an NSPlaceholderString. I switched to UTF8string, but it returns a null value.
code below:
- (void) test:(NSString*)thing {
id (*p_objc_msgSend)(id p, SEL sel, ...);
id (*p_objc_getClass)(const char *s);
Class class;
p_objc_getClass = objc_getClass;
p_objc_msgSend = objc_msgSend;
NSString *s;
class = (*p_objc_getClass)( "NSString");
//SEL init = #selector(initWithString:);
s = (*p_objc_msgSend)(class, #selector(alloc));
s = (*p_objc_msgSend)(s, #selector(initWithUTF8String:), "AAA");
NSLog(#"Value of %#", s);
}
NSString is a class cluster with multiple private subclasses. NSPlaceholderString is a subclass of NSString so you can continue to use that object as any other NSString.

How to test properties of class using TDD?

Why when I use method respondsToSelector: or instancesRespondToSelector: at line 43 I cannot bypass STAssertTrue?
//My test case code
- (void)testApiClass {
//Check object
NSString* classKey = #"Api";
id obj = NSClassFromString(classKey);
STAssertNotNil(obj, [NSString stringWithFormat:#"Model '%#' not found.", classKey]);
//Check properties
NSArray* properties =
#[
#"performSyncRequestWithUri::",
#"performAsyncRequestWithUri:::",
];
for (NSString* property in properties) {
SEL propertySel = NSSelectorFromString(property);
BOOL isRespondsToSel = [obj respondsToSelector:propertySel];
STAssertTrue(isRespondsToSel, [NSString stringWithFormat:#"Property '%#' not found on object of class name '%#'", property, [obj class]]);
}
}
#interface Api : NSObject
- (NSDictionary*)performSyncRequestWithUri:(NSString *)requestUri params:(NSDictionary *)params;
- (void)performAsyncRequestWithUri:(NSString *)requestUri params:(NSDictionary *)params completionHandler:(void (^)(NSDictionary *, NSError *))completionBlock;
#end
The string constants in your properties array don't match the selectors in your Api interface.
Also, neither of those selectors refers to a property. A property has two selectors: a getter, like stringValue, which has no colons, and a setter, like setStringValue:, which has one colon and (usually) starts with set.
Instead of embedding your selectors in strings, make an array of selectors:
SEL selectors[] = {
#selector(performSyncRequestWithUri:params:),
#selector(performAsyncRequestWithUri:params:completionHandler:),
NULL
};
for (size_t i = 0; selectors[i]; ++i) {
SEL selector = selectors[i];
BOOL respondsToSelector = [obj respondsToSelector:selector];
STAssertTrue(respondsToSelector, [NSString stringWithFormat:
#"Object %# doesn't respond to selector %s",
obj, sel_getName(selector)]);
}
The advantages here are that Xcode will autocomplete the selectors for you, and you can command-click the selectors to jump to their definitions.
The methods are called performAsyncRequestWithUri:params:completionHandler: and performSyncRequestWithUri:params:

Question about isKindOfClass

Please look at this code snipped
- (SoapRequest*)AddFlyData:(id)_target
action:(SEL)_action
sessionid:(int)sessionid
datasets:(FlyNetArrayOfDataSet*)datasets
{
if ([datasets isKindOfClass:[FlyNetArrayOfDataSet class]]) {
NSLog(#"Yeah");
} else {
NSLog(#"Not Yeah");
}
}
Why, when i look on my console, I get
2011-09-06 23:08:00.917 soap-test[2133:207] Not Yeah
I'm a beginner and I'm completely confused .. :s When I look in the Debugger, the variable type is SoapArray (who is the parent class of FlyNetArrayOfDataSet).
I used a method from SoapArray to initiate my instance of 'datasets', that means the class is automatically defined as Soap and not as FlyNetArrayOfDataSet ?!
Thank you
EDIT: I made a mistake, it's not NSArray but it inherits from SoapArray
This is the header file of the class FlyNetArrayOfDataSet
#import "Soap.h"
#interface FlyNetArrayOfDataSet : SoapArray
{
}
+ (NSMutableString*) serialize: (NSArray*) array;
#end
But that didn't explain me why isKindOfClass returns false ..
EDIT2: Ok I have the response of my question..
I used this method to initialize my instance
FlyNetArrayOfDataSet * arr = [FlyNetArrayOfDataSet arrayWithObject:data];
This is a static method of the superclass SoapArray that create an instance of SoapArray (Helper) .. but not an instance of FlyNetArrayOfDataSet (!)
Look at its implementation :
+ (id)arrayWithObjects:(id)firstObj, ...{
SoapArray* a = [SoapArray array];
id eachObject;
va_list argumentList;
if (firstObj) {
[a.items addObject: firstObj];
va_start(argumentList, firstObj);
while (eachObject = va_arg(argumentList, id)) {
[a.items addObject: eachObject];
}
va_end(argumentList);
}
return a;
}
If I initialize my instance like this
FlyNetArrayOfDataSet * arr = [[FlyNetArrayOfDataSet alloc] init];
It's work perfectly and the method isKindOfClass return true :-)
Suppose you have a class named "FlyNetArrayOfDataSet" which inherits from (=is a subclass of) NSArray.
If you instantiate a variable like:
FlyNetArrayOfDataSet *arr = [[FlyNetArrayOfDataSet alloc] init];
As you can see, I'm initializing the array with a method of NSArray. However, my "arr" object will be of kind FlyNetArrayOfDataSet, and NOT NSArray, because I called the FlyNetArrayOfDataSet class (see [FlyNetArrayOfDataSet arrayWithObject....).
NSLog(#"%d", [arr isKindOfClass:[FlyNetArrayOfDataSet class]]);
NSLog(#"%d", [arr isKindOfClass:[NSArray class]]);
Both will return "1", which means "true", because arr is an object of the FlyNetArrayOfDataSet class, which inherits from NSArray.
EDIT
Let's see if I can explain it better:
arr1 = [[FlyNetArrayOfDataSet alloc] init];
arr2 = [[NSArray alloc] init];
Both objects, arr1 and arr2, are created with the same method, which is defined in the class NSArray. However, in the first case the class which is being called is FlyNetArrayOfDataSet and in the second case is NSArray. Thus, arr1 will be an object of class FlyNetArrayOfDataSet, while arr2 will be of class NSArray.
The difference can be seen in this code:
NSLog(#"%d %d",
[arr1 isKindOfClass:[FlyNetArrayOfDataSet class]]
[arr1 isKindOfClass:[NSArray class]]
);
NSLog(#"%d %d",
[arr2 isKindOfClass:[FlyNetArrayOfDataSet class]]
[arr2 isKindOfClass:[NSArray class]]
);
The output of this code is:
1 1 ( = true true)
0 1 ( = false true)
that's because FlyNetArrayOfDataSet is a SoapArray and SoapArray is not a FlyNetArrayOfDataSet.
if datasets were an instance of SoapArray, you will see "Soap" in the following example:
- (SoapRequest*)addFlyData:(id)target
action:(SEL)action
sessionid:(int)sessionid
datasets:(FlyNetArrayOfDataSet*)datasets
{
if ([datasets isKindOfClass:[FlyNetArrayOfDataSet class]]) {
NSLog(#"Fly");
}
else if ([datasets isKindOfClass:[SoapArray class]]) {
NSLog(#"Soap");
}
else {
NSLog(#"???");
}
}
it's possible that an instance of SoapArray is also FlyNetArrayOfDataSet. the other possibilities are:
a SoapArray
subclass other than FlyNetArrayOfDataSet.

Anyway to get string from variable name?

Say I have my class
#interface Person : NSObject { NSString *name; }
I need to get the name of NSString's within my class
Person *person = [[Person alloc] init];
NSLog(#"Name of variable %s\n", _NameofVariable_(person->name));
Thanks for the answers, here's the solution I came up from the replies
//returns nil if property is not found
-(NSString *)propertyName:(id)property {
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
if ((object_getIvar(self, thisIvar) == property)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}
As easy as
#define VariableName(arg) (#""#arg)
Then you do:
NSObject *obj;
NSString *str = VariableName(obj);
NSLog(#"STR %#", str);//obj
You can get the names of a class's instance variables with the Objective-C runtime API function class_copyIvarList. However, this is rather involved, rarely done and almost never the best way to accomplish something. If you have a more specific goal in mind than mere curiosity, it might be a good idea to ask about how to accomplish it in Objective-C.
Also, incidentally, person.name doesn't specify an instance variable in Objective-C — it's a property call. The instance variable would be person->name.
You might use preprocessor stringification and a bit of string twiddling:
NSUInteger lastIndexAfter(NSUInteger start, NSString *sub, NSString *str) {
NSRange found = [str rangeOfString:sub options:NSBackwardsSearch];
if(found.location != NSNotFound) {
NSUInteger newStart = NSMaxRange(found);
if(newStart > start)
return newStart;
}
return start;
}
NSString *lastMember(NSString *fullName) {
if(!fullName) return nil;
NSUInteger start = 0;
start = lastIndexAfter(start, #".", fullName);
start = lastIndexAfter(start, #"->", fullName);
return [fullName substringFromIndex: start];
}
#define NSStringify(v) (##v)
#define _NameofVariable_(v) lastMember(NSStringify(v))
If the person object is exposed as a property of the class, you can use objc_msgSend to get the value.
So, if you could access person using
[object person]
You could also do
objc_msgSend(object, "person")
For more details on message sending, including how to pass arguments to methods, see the Objective-C Runtime Programming Guide section on Messaging
The following works as a macro:
#define STRINGIZE(x) #x