Can't have an ivar with a name of description in Objective-C description method? - objective-c

I'm trying to implement the Objective-C description method for my NSObject-derived object.
However, my derived object has an ivar of name description. And for some reason this is causing a crash.
- (NSString *) description {
NSMutableString *output = [NSMutableString string];
[output appendFormat:#"MyObject.description = %#\n", self.description];
return output;
}
Why would this be an issue?

Short Answer: The crash is a result of a stack overflow because your -description method calls itself repeatedly. To do what you want to do (accessing the ivar from within the description method), you should not use the prefix self. in front of the ivar.
More Detail:
In Objective-C, self.description is shorthand for [self description]. Using the dot-syntax informs the compiler that you want to access a property named description, and not the ivar itself.

It's an issue because you're creating an infinite loop. self.description will call [self description], which is exactly the method you're within. Hence you have the method calling itself repeatedly.
- (NSString *) description {
NSMutableString *output = [NSMutableString string];
[output appendFormat:#"super's description = %#\n", [super description]];
[output appendFormat:#"MyObject.description = %#\n", description];
return output;
}
You can access the instance variable directly, rather than using self.description. Also, I added an extra line to show how you can call super's description method (which doesn't create an infinite loop).

Related

Using typecast for (void *) in Objective C

i am using the addToolTipRect: method to set a tooltip rect
- (NSToolTipTag)addToolTipRect:(NSRect)aRect owner:(id)anObject userData:(void *)userData
and method stringForToolTip: to obtain string value for tooltip.
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
However the above functions work fine if i send something like
[self addToolTipRect:someRect owner:self userData:#"Tool tip string"];
But doesn't work when i send the following string. Error: BAD_ACCESS
const NSString * tooltipStr = #"Tool tip string";
[self addToolTipRect:someRect owner:self userData:tooltipStr];
In both the cases, the stringForToolTip looks like:
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
{
id obj = (id)data;
NSString * str=nil;
if ([obj isKindOfClass:[SomeClass class]]) //This is my system defined class and works fine
{
SomeClass * someClassObj = (SomeClass *) data;
str = someClassObj.title;
}
else if([obj isKindOfClass:[NSString class]])
str = (NSString*)obj;
return str;
}
NOTE: In the stringForToolTip: method I also want to check for some other class example [obj isKindOF:[SomeClass class]] and i don't want to assert that value. The problem here is just in getting the string value by proper cast but I can't figure out how! Please tell me where I am going wrong?
edit:
What should be the right way to get the String value for tooltip in that case? should the point or tag be considered?
(void *) is not an object pointer.
That #"Tool tip string" worked was by coincidence based on the fact that is is a compile-time constant with a (essentially) permanent allocation and permanent address.
But in the code:
const NSString * tooltipStr = #"Tool tip string";
[self addToolTipRect:someRect owner:self userData:tooltipStr];
tooltipStr is an object that is kept in memory by a strong reference (retain count > 0). Since userData: does not handle objects it does not make a strong reference (does not increase the retain count) so it is released, will disappear soon becoming invalid.
Notes from the documentation:
The tooltip string is obtained from the owner. The owner must respond to one of two messages, view:stringForToolTip:point:userData: or description, use the latter. Note that NSString responds to description so you can pass an NSString for the value of owner. So, what you want is: [self addToolTipRect:someRect owner:tooltipStr userData:NULL];. There is still an issue that something must hole a strong reference to the NSString instance.
You can: [self addToolTipRect:someRect owner:#"Tool tip string" userData:NULL];
Probably the best way to go is to pass self as owner and NULL as data and implement the delegate method: view:stringForToolTip:point:userData: in the class.

Objective-C dynamic properties at runtime?

Is it possible to create an Objective-C class that can have an arbitrary number of dynamic properties at runtime?
I want to be able to call mySpecialClass.anyProperty and intercept this inside my class to be able to provide my own custom implementation that can then return an NSString (for instance) at runtime with raising an exception. Obviously this all has to compile.
Ideal would be if I could refer to my properties using something similar to the new literal syntax, e.g. mySpecialClass["anyProperty"].
I guess in a way I want to create something like a dynamic NSDictionary with no CFDictionary backing store, that executes 2 custom methods on property getting and setting respectively, with the property name passed in to these accessor methods so they can decide what to do.
There are at least two ways to do this.
Subscripting
Use objectForKeyedSubscript: and setObject:forKeyedSubscript:
#property (nonatomic,strong) NSMutableDictionary *properties;
- (id)objectForKeyedSubscript:(id)key {
return [[self properties] valueForKey:[NSString stringWithFormat:#"%#",key]];
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key {
[[self properties] setValue:object forKey:[NSString stringWithFormat:#"%#",key]];
}
Person *p = [Person new];
p[#"name"] = #"Jon";
NSLog(#"%#",p[#"name"]);
resolveInstanceMethod:
This is the objc_sendMsg executed by the runtime for all methods:
If you look at the bottom, you have the opportunity to resolveInstanceMethod:, which lets you redirect the method call to one of your choosing. To answer your question, you need to write a generic getter and setter that looks-up a value on a dictionary ivar:
// generic getter
static id propertyIMP(id self, SEL _cmd) {
return [[self properties] valueForKey:NSStringFromSelector(_cmd)];
}
// generic setter
static void setPropertyIMP(id self, SEL _cmd, id aValue) {
id value = [aValue copy];
NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy];
// delete "set" and ":" and lowercase first letter
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
NSString *firstChar = [key substringToIndex:1];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]];
[[self properties] setValue:value forKey:key];
}
And then implement resolveInstanceMethod: to add the requested method to the class.
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if ([NSStringFromSelector(aSEL) hasPrefix:#"set"]) {
class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v#:#");
} else {
class_addMethod([self class], aSEL,(IMP)propertyIMP, "##:");
}
return YES;
}
You could also do it returning a NSMethodSignature for the method, which is then wrapped in a NSInvocation and passed to forwardInvocation:, but adding the method is faster.
Here is a gist that runs in CodeRunner. It doesn't handle myClass["anyProperty"] calls.
You're asking different things. If you want to be able to use the bracket syntax mySpecialClass[#"anyProperty"] on instances of your class, it is very easy. Just implement the methods:
- (id)objectForKeyedSubscript:(id)key
{
return ###something based on the key argument###
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key
{
###set something with object based on key####
}
It will be called everytime you use the bracket syntax in your source code.
Otherwise if you want to create properties at runtime, there are different ways to proceed, take a look at NSObject's forwardInvocation: method, or look at the Objective-C Runtime Reference for functions to dynamically alter a class...
Guillaume is right. forwardInvocation: is the way to go. This answer gives some more details: method_missing-like functionality in objective-c (i.e. dynamic delegation at run time)
This has even more details: Equivalent of Ruby method_missing in Objective C / iOS
And these are some other lesser known Obj-C features that might help you: Hidden features of Objective-C
Enjoy!

Pass by value vs Pass by reference

I have been looking into some basics over the last couple days and I realized that i never truly understood why pass-by-reference for NSString/NSMutableString did not work.
- (void)testing{
NSMutableString *abc = [NSMutableString stringWithString:#"ABC"];
[self testing:abc];
NSLog(#"%#",abc); // STILL ABC
}
-(void)testing:(NSMutableString *)str {
str = [NSMutableString stringWithString:#"HELP"];
}
How do i go about this? I want my testing method to be able to manipulate the String from the main method. I have been using this with Mutable Arrays, dictionary etc and works fine. Feels strange that I never realized how this works with Strings.
But the value gets changed in something like this which is a reference to the first string
NSMutableString *string1;
NSMutableString *string2;
string1 = [NSMutableString stringWithString: #"ABC"];
string2 = string1;
[string2 appendString: #" HELP"];
NSLog (#"string1 = %#", string1); // ABC HELP
NSLog (#"string2 = %#", string2); // ABC HELP
Like Java, Objective-C has only passing and assigning by value. Also like Java, objects are always behind pointers (you never put the object itself into a variable).
When you assign or pass an object pointer, the pointer is copied and points to the same object as the original pointer. That means, if the object is mutable (i.e. it has some method that mutates its contents), then you can mutate it through one pointer and see the effects through the other one. Mutation is always achieved by calling a method, or assigning to a field directly.
-(void)testing:(NSMutableString *)str {
[str setString:#"HELP"];
}
Assigning to a pointer never mutates the object it points to; rather, it makes the pointer point to another object.
I cannot in good conscious let this wrong answer linger on the internet.
Pass by reference in objective c is POSSIBLE; which is why it is better than Java.
Here's how:
- (void)testing
{
NSMutableString *abc = [NSMutableString stringWithString:#"ABC"];
[self testingHelper:&abc];
NSLog(#"%#", abc); // NOW HELP
}
- (void)testingHelper:(NSMutableString **)str {
*str = [NSMutableString stringWithString:#"HELP"];
}

Fast Enumeration on NSArray of Different Types

I have this question here (as well other quesrtions on SO), and the Apple docs about Objective-C collections and fast enumeration. What is not made clear is if an NSArray populated with different types, and a loop is created like:
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
What exactly happens here? Will the loop skip over anything that is not an NSString? For example, if (for the sake of argument) a UIView is in the array, what would happen when the loop encounters that item?
Why would you want to do that? I think that would cause buggy and unintended behavior. If your array is populated with different elements, use this instead:
for (id object in myArray) {
// Check what kind of class it is
if ([object isKindOfClass:[UIView class]]) {
// Do something
}
else {
// Handle accordingly
}
}
What you are doing in your example is effectively the same as,
for (id object in myArray) {
NSString *string = (NSString *)object;
NSLog(#"%#\n", string);
}
Just because you cast object as (NSString *) doesn't mean string will actually be pointing to an NSString object. Calling NSLog() in this way will call the - (NSString *)description method according to the NSObject protocol, which the class being referenced inside the array may or may not conform to. If it conforms, it will print that. Otherwise, it will crash.
You have to understand that a pointer in obj-c has no type information. Even if you write NSString*, it's only a compilation check. During runtime, everything is just an id.
Obj-c runtime never checks whether objects are of the given class. You can put NSNumbers into NSString pointers without problems. An error appears only when you try to call a method (send a message) which is not defined on the object.
How does fast enumeration work? It's exactly the same as:
for (NSUInteger i = 0; i < myArray.count; i++) {
NSString* string = [myArray objectAtIndex:i];
[...]
}
It's just faster because it operates on lower level.
I just tried a quick example... Here is my code.
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:1];
NSNumber *number = [NSNumber numberWithInteger:6];
[array addObject:number];
[array addObject:#"Second"];
Now if I simply log the object, no problem. The NSNumber instance is being cast as an NSString, but both methods respond to -description, so its not a problem.
for (NSString *string in array)
{
NSLog(#"%#", string);
}
However, if I attempt to log -length on NSString...
for (NSString *string in array)
{
NSLog(#"%i", string.length);
}
... it throws an NSInvalidArgumentException because NSNumber doesn't respond to the -length selector. Long story short, Objective-C gives you a lot of rope. Don't hang yourself with it.
Interesting question. The most generic syntax for fast enumeration is
for ( NSObject *obj in myArray )
NSLog( #"%#\n", obj );
I believe that by doing
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
instead, you are simply casting each object as an NSString. That is, I believe the above is equivalent to
for ( NSObject *obj in myArray ) {
NSString *string = obj;
NSLog( #"%#\n", string );
}
I could not find precise mention of this in Apple's documentation for Fast Enumeration, but you can check it on an example and see what happens.
Since all NSObject's respond to isKindOfClass, you could still keep the casting to a minimum:
for(NSString *string in myArray) {
if (![string isKindOfClass:[NSString class]])
continue;
// proceed, knowing you have a valid NSString *
// ...
}

Replace array display method?

I am curious how I might override the description method that is used when you do the following (see below) for an object. I basically want to better format the output, but am unsure about how I might go about setting this up.
NSLog(#"ARRAY: %#", myArray);
many thanks
EDIT_001
Although subclassing NSArray would have worked I instead decided that I would add a category to NSArray (having not used one before) Here is what I added ...
// ------------------------------------------------------------------- **
// CATAGORY: NSArray
// ------------------------------------------------------------------- **
#interface NSArray (displayNSArray)
-(NSString*)display;
#end
#implementation NSArray (displayNSArray)
-(NSString*)display {
id eachIndex;
NSMutableString *outString = [[[NSMutableString alloc] init] autorelease];
[outString appendString:#"("];
for(eachIndex in self) {
[outString appendString:[eachIndex description]];
[outString appendString:#" "];
}
[outString insertString:#")" atIndex:[outString length]-1];
return(outString);
}
#end
gary
If you're doing this a lot, the easiest way to reformat the display of your array would be to add a new prettyPrint category to the NSArray class.
#interface NSArray ( PrettyPrintNSArray )
- (NSSTring *)prettyPrint;
#end
#implementation NSArray ( PrettyPrintNSArray )
- (NSString *)prettyPrint {
NSMutableString *outputString = [[NSMutableString alloc] init];
for( id item in self ) {
[outputString appendString:[item description]];
}
return outputString;
}
#end
Obviously you'd need to alter the for loop to get the formatting the way you want it.
I'm assuming that you myArray variable is an instance of the NSArray/NSMutableArray class.
When NSLog() encounters the # character in its format string, it calls the -description: method on the object. This is a method on the root class, NSObject from which all other Cocoa classes inherit. -description: returns an NSString allowing any object that implements this method to be passed into NSLog(#"#",anyObject) and have a nicely formatted output. The string returned can be anything you care to construct.
For your specific problem, you could subclass NSMutableArray and override the -description: method with your own implementation. Then utilise your subclass instead of NSMutableArray.
For more information on NSObject and -description: see Apple's docs.
From Formatting string objects:
NSString supports the format characters defined for the ANSI C functionprintf(), plus ‘#’ for any object. If the object responds to the descriptionWithLocale: message, NSString sends that message to retrieve the text representation, otherwise, it sends a description message.
So to customize array conversion to string you should change NSArray descriptionWithLocale: implementation. Here's an example of how you can replace object method in run-time.