How to specify the type of a Class type - objective-c

I am currently trying to define a method, that takes a Class as argument, that is a subclass of a specific type. My current implementation is:
- (void)methodThatNeedsAClassOfSpecialTypeAsInput:(Class)inClass {}
Taking NSString as an example base class, I have tried NSString.Class and [NSString class] to specify the arguments class, but
- (void)methodThatNeedsAClassOfSpecialTypeAsInput:([NSString class])inClass { }
does not compile with Parse issue: Expected a type.
So the question is: is is possible to make an Class arguments type explicit?
Or in other words: I want my methods signatures semantic to say: I can take a NSString class, an only a NSString class as argument.
EDIT:
The answer is: No.

You almost had it:
[NSString class]
EDIT (after question updated):
This is how the method is defined and declared:
- (void)methodThatNeedsAClassOfSpecialTypeAsInput:(Class)inClass { }
But it is called like this:
[someObject methodThatNeedsAClassOfSpecialTypeAsInput:[NSString class]];

You got it wrong. Method declaration was right
- (void)methodThatNeedsAClassOfSpecialTypeAsInput:(Class)inClass;
Than, in place you call it
[obj methodThatNeedsAClassOfSpecialTypeAsInput:[NSString class]];
If, in implementation you need to be guarantee, that your inClass is NSString, you can use
if([inClass isSubclassOfClass:[NSString class]]) {
//Do whatever you need
}

Related

Objective C - Override setter to accept objects of a different type

I'm trying to override the setter of an NSManagedObject so that I can pass in an object of a different type, do a transformation and then set the property. Something like this:
- (void)setContentData:(NSData *)contentData
{
NSString *base64String;
// do some stuff to convert data to base64-encoded string
// ...
[self willChangeValueForKey:#"contentData"];
[self setPrimitiveValue:base64String forKey:#"contentData"];
[self didChangeValueForKey:#"contentData"];
}
So, in this case the contentData field of my NSManagedObject is an NSString *, and I want to allow the setter to accept an NSData * which I would then convert to an NSString * and save it to the model. However, if I try to do this I get warnings from the compiler about trying to assign an NSData * to an NSString *:
myObject.contentData = someNSData;
-> Incompatible pointer types assigning to 'NSString *' from 'NSData *__strong'
Is there a better way to go about this, or perhaps I should avoid the setters altogether and create custom "setters" that allow me to pass in the NSData * and set the NSString * field without a compiler warning?
I think this is an instance where your fighting with the tools and frameworks is a significant design smell. Retreat from this notion of trying to override the expected data type of a fundamental property for your class.
You didn't say whether the NSManagedObject you are subclassing is under your control. If it's going to be part of your design to have it be something of a template for management of other types of contentData than NSString, then declare it as type id in the root class and specialize in the subclasses. That should prevent the warning.
Probably, you want to follow a Cocoaism: don't subclass. Can you achieve whatever functionality you're looking for from the superclass by say extracting it into a helper class that is held as a property by each of the varying-behavior managed object classes?
following up on my "setContentData: (id) contentData" comment, try something like this:
- (void)setContentData:(id)thingToWorkWith
{
NSString * base64String = nil;
if(thingToWorkWith isKindOfClass: [NSData class])
{
// convert data to string
}
if(thingToWorkWith isKindOfClass: [NSString class])
{
// set up base64 string properly
}
if(base64String)
{
// do some stuff to convert data to base64-encoded string
// ...
[self willChangeValueForKey:#"contentData"];
[self setPrimitiveValue:base64String forKey:#"contentData"];
[self didChangeValueForKey:#"contentData"];
}
}
Make sure to get rid of the "#synthesize" bit for contentData in your .m file, create a "getter" method as well, and because you're using "id" for the setter parameter, you may have to adjust your "#property" declaration a bit. I haven't tried exactly what you are attempting to do (i.e. no warranties on this technique).

Objective-C & KeyValueCoding: How to avoid an exception with valueForKeyPath:?

I've got an object of type id and would like to know if it contains a value for a given keyPath:
[myObject valueForKeyPath:myKeyPath];
Now, I wrap it into a #try{ } #catch{} block to avoid exceptions when the given keypath isn't found. Is there a nicer way to do this? Check if the given keypath exists without handling exceptions?
Thanks a lot,
Stefan
You could try this:
if ([myObject respondsToSelector:NSSelectorFromString(myKeyPath)])
{
}
However, that may not correspond to the getter you have, especially if it is a boolean value. If this doesn't work for you, let me know and I'll write you up something using reflection.
For NSManagedObjects, an easy solution is to look at the object's entity description and see if there's an attribute with that key name. If there is, you can also take it to the next step and see what type of an attribute the value is.
Here's a simple method that given any NSManagedObject and any NSString as a key, will always return an NSString:
- (NSString *)valueOfItem:(NSManagedObject *)item asStringForKey:(NSString *)key {
NSEntityDescription *entity = [item entity];
NSDictionary *attributesByName = [entity attributesByName];
NSAttributeDescription *attribute = attributesByName[key];
if (!attribute) {
return #"---No Such Attribute Key---";
}
else if ([attribute attributeType] == NSUndefinedAttributeType) {
return #"---Undefined Attribute Type---";
}
else if ([attribute attributeType] == NSStringAttributeType) {
// return NSStrings as they are
return [item valueForKey:key];
}
else if ([attribute attributeType] < NSDateAttributeType) {
// this will be all of the NSNumber types
// return them as strings
return [[item valueForKey:key] stringValue];
}
// add more "else if" cases as desired for other types
else {
return #"---Unacceptable Attribute Type---";
}
}
If the key is invalid or the value can't be made into a string, the method returns an NSString error message (change those blocks to do whatever you want for those cases).
All of the NSNumber attribute types are returned as their stringValue representations. To handle other attribute types (e.g.: dates), simply add additional "else if" blocks. (see NSAttributeDescription Class Reference for more information).
If the object is a custom class of yours, you could override valueForUndefinedKey: on your object, to define what is returned when a keypath doesn't exist.
It should be possible to graft this behavior onto arbitrary classes reasonably simply. I present with confidence, but without warranty, the following code which you should be able to use to add a non-exception-throwing implementation of valueForUndefinedKey: to any class, with one, centralized line of code per class at app startup time. If you wanted to save even more code, you could make all the classes you wanted to have this behavior inherit from a common subclass of NSManagedObject and then apply this to that common class and all your subclasses would inherit the behavior. More details after, but here's the code:
Header (NSObject+ValueForUndefinedKeyAdding.h):
#interface NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler;
#end
Implementation (NSObject+ValueForUndefinedKeyAdding.m):
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import <objc/runtime.h>
#import <objc/message.h>
#implementation NSObject (ValueForUndefinedKeyAdding)
+ (void)addCustomValueForUndefinedKeyImplementation: (IMP)handler
{
Class clazz = self;
if (clazz == nil)
return;
if (clazz == [NSObject class] || clazz == [NSManagedObject class])
{
NSLog(#"Don't try to do this to %#; Really.", NSStringFromClass(clazz));
return;
}
SEL vfuk = #selector(valueForUndefinedKey:);
#synchronized([NSObject class])
{
Method nsoMethod = class_getInstanceMethod([NSObject class], vfuk);
Method nsmoMethod = class_getInstanceMethod([NSManagedObject class], vfuk);
Method origMethod = class_getInstanceMethod(clazz, vfuk);
if (origMethod != nsoMethod && origMethod != nsmoMethod)
{
NSLog(#"%# already has a custom %# implementation. Replacing that would likely break stuff.",
NSStringFromClass(clazz), NSStringFromSelector(vfuk));
return;
}
if(!class_addMethod(clazz, vfuk, handler, method_getTypeEncoding(nsoMethod)))
{
NSLog(#"Could not add valueForUndefinedKey: method to class: %#", NSStringFromClass(clazz));
}
}
}
#end
Then, in your AppDelegate class (or really anywhere, but it probably makes sense to put it somewhere central, so you know where to find it when you want to add or remove classes from the list) put this code which adds this functionality to classes of your choosing at startup time:
#import "MyAppDelegate.h"
#import "NSObject+ValueForUndefinedKeyAdding.h"
#import "MyOtherClass1.h"
#import "MyOtherClass2.h"
#import "MyOtherClass3.h"
static id ExceptionlessVFUKIMP(id self, SEL cmd, NSString* inKey)
{
NSLog(#"Not throwing an exception for undefined key: %# on instance of %#", inKey, [self class]);
return nil;
}
#implementation MyAppDelegate
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[MyOtherClass1 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass2 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
[MyOtherClass3 addCustomValueForUndefinedKeyImplementation: (IMP)ExceptionlessVFUKIMP];
});
}
// ... rest of app delegate class ...
#end
What I'm doing here is adding a custom implementation for valueForUndefinedKey: to the classes MyOtherClass1, 2 & 3. The example implementation I've provided just NSLogs and returns nil, but you can change the implementation to do whatever you want, by changing the code in ExceptionlessVFUKIMP. If you remove the NSLog, and just return nil, I suspect you'll get what you want, based on your question.
This code NEVER swizzles methods, it only adds one if it's not there. I've put in checks to prevent this from being used on classes that already have their own custom implementations of valueForUndefinedKey: because if someone put that method in their class, there's going to be an expectation that it will continue to get called. Also note that there may be AppKit code that EXPECTS the exceptions from the NSObject/NSManagedObject implementations to be thrown. (I don't know that for sure, but it's a possibility to consider.)
A few notes:
NSManagedObject provides a custom implementation for valueForUndefinedKey: Stepping through its assembly in the debugger, all it appears to do is throw roughly the same exception with a slightly different message. Based on that 5 minute debugger investigation, I feel like it ought to be safe to use this with NSManagedObject subclasses, but I'm not 100% sure -- there could be some behavior in there that I didn't catch. Beware.
Also, as it stands, if you use this approach, you don't have a good way to know if valueForKey: is returning nil because the keyPath is valid and the state happened to be nil, or if it's returning nil because the keyPath is invalid and the grafted-on handler returned nil. To do that, you'd need to do something different, and implementation specific. (Perhaps return [NSNull null] or some other sentinel value, or set some flag in thread-local storage that you could check, but at this point is it really all that much easier than #try/#catch?) Just something to be aware of.
This appears to work pretty well for me; Hope it's useful to you.
There's no easy way to solve this. Key Value Coding (KVC) isn't intended to be used that way.
One thing is for sure: using #try-#catch is really bad since you're very likely to leak memory etc. Exceptions in ObjC / iOS are not intended for normal program flow. They're also very expensive (both throwing and setting up the #try-#catch IIRC).
If you look at the Foundation/NSKeyValueCoding.h header, the comment / documentation for
- (id)valueForKey:(NSString *)key;
clearly states which methods need to be implemented for -valueForKey: to work. This may even use direct ivar access. You would have to check each one in the order described there. You need to take the key path, split it up based on . and check each part on each subsequent object. To access ivars, you need to use the ObjC runtime. Look at objc/runtime.h.
All of this is vary hacky, though. What you probably want is for your objects to implement some formal protocol and then check -conformsToProtocol: before calling.
Are your key paths random strings or are those strings under your control? What are you trying to achieve? Are you solving the wrong problem?
I don't believe this is possible in a safe way (i.e. without mucking with -valueForUndefinedKey: or something similar on other peoples' classes). I say that because on the Mac side of things, Cocoa Bindings—which can be set to substitute a default value for invalid key paths—simply catches the exceptions that result from bad key paths. If even Apple's engineers don't have a way to test if a key path is valid without trying it and catching the exception, I have to assume that such a way doesn't exist.

Objective C class problem

I have a problem with classes. This is my code:
self.shapeClass = [HWRectangle class];
if ([_shapeClass isKindOfClass:[HWRectangle class]]) {
NSLog(#"Class created as: %s", [_shapeClass description]);
}
I thought that the program will do the logging in this case, but it doesn't. Do you have any idea why not?
because: if ([_shapeClass isKindOfClass:[HWRectangle class]])
_shapeClass should be an instance of the class you are testing, unless you are really testing for class comparisons. So, this method is instance to class comparison, not class to class comparison.
For bonus points, your format string should be: NSLog(#"Class created as: %#", [_shapeClass description])
(unless you have overridden the NSObject method (which you should not do))
isKindOfClass checks the class of a variable. You say that shapeCalls = [HWRectangle class]. The result of [HWRectangle class] is of the class "Class". So if you compare this with the class of HWRectangle you will find that the two are not the same.

What is the Objective-C equivalent for "toString()", for use with NSLog?

Is there a method that I can override in my custom classes so that when
NSLog(#"%#", myObject)
is called, it will print the fields (or whatever I deem important) of my object? I guess I'm looking for the Objective-C equivalent of Java's toString().
It is the description instance method, declared as:
- (NSString *)description
Here's an example implementation (thanks to grahamparks):
- (NSString *)description {
return [NSString stringWithFormat: #"Photo: Name=%# Author=%#", name, author];
}
Add this to the #implementation of your Photo class:
- (NSString *)description {
return [NSString stringWithFormat:#"Photo: Name=%# Author=%#",name,author];
}
You can override the description method of NSObject:
- (NSString *)description
On the subject of logging I recommend this blog post for better logging in Objective-C.
There are two functions that you can use.
- (NSString*)description
This will be displayed when you put your object as, I.E. a parameter for NSLog. The other description function is:
- (NSString*)debugDescription
This will be called when you do po anInstanceOfYourClass in the debug command window. If your class doesn't have a debugDescription function, then just description will be called.
Note that the base class NSObject does have description implemented, but it is fairly bare-bones: it only displays the address of the object. This is why I recommend that you implement description in any class you want to get info out of, especially if you use the description method in your code. If you do use description in your code, I suggest you implement debugDescription as well, also making debugDescription more verbose.
This will output the available voices:
NSLog((#"speechVoices:%", [[AVSpeechSynthesisVoice speechVoices] description] ));
I think comment from #Nuthatch of overriding "description" with CoreData (i.e classes inheriting NSManagedObject) should be emphasized
https://developer.apple.com/documentation/coredata/nsmanagedobject?language=objc
Avoid overriding description—if this method fires a fault during a
debugging operation, the results may be unpredictable.

How to build a NSArray (or NSMutableArray) of class methods in Objective-C?

I'm trying to build a NSArray of methods in Objective-C.
(What I'm trying to accomplish here is something like the following in C)
typedef (void)(*handler)(int command);
void handleCommandA(void) { ... }
void handleCommandB(void) { ... }
static const handler handler_table[10] = {
handleCommandA, handleCommandB, handleCommandC
};
I have to port this to Objective-C and I don't know how to
build an array of function pointers (in Objective-c world,
class methods) at compile-time.
In Objective-C I have the following.
- (void)handleCommandA { ... }
- (void)handleCommandB { ... }
/* Now how to add above 2 functions into NSArray? */
NSArray *handler_table = [NSArray arrayWithObjects:... ]; /* This doesn't seem to work. */
The problem here is that to bind those functions you must use the selector keyword which returns a SEL type. This is a pointer type whereas NSArray stores objects.
You thus have three options;
Use a regular C-type array
Fold the functions into an NSObject derived class that will call them.
Use a protocol.
The second is likely the nicer and for this you can use the NSValue class to hold the selector results. E.g;
NSValue* selCommandA = [NSValue valueWithPointer:#selector(handleCommandA:)];
NSValue* selCommandB = [NSValue valueWithPointer:#selector(handleCommandB:)];
NSArray *handler_table = [NSArray arrayWithObjects:selCommandA, selCommandB, nil ];
When you have retrieved the correct entry from the array, to convert back you would do;
SEL mySelector = [selCommand pointerValue];
[someObject performSelector:mySelector];
(Note I'm assuming that from your objective-c syntax that these are intended to be used as methods on an object and not global functions. If you wish to use them globally then you should write them as you would in plain C.)
Another option is to formalize the command methods into a protocol. This allows you to write functionality that will work on any object which implements that protocol and the compiler will provide more checking than if you were just calling selectors.
E.g.
// some header
#protocol CommandHandler
#required
-(void) handleCommandA;
-(void) handleCommandB;
#end
// some other header
#interface someClass : NSObject<CommandHandler>
{
// you will receive compiler warnings if you do not implement the protocol functions
}
Your handling and dispatch code is then written to work with objects of type "CommandHandler". E.g
-(void) registerForCommands:(CommandHandler*)handler
Use NSValue.
For example:
NSArray* handlers = [NSArray arrayWithObjects:[NSValue valueWithPointer:handleA] ... ];
then to access :
handleptr* handle = (handlerptr*)[[handlers objectAtIndex:0] pointerValue];
handle(foo_bar);
In Objective-C, you don't pass around methods; you pass around selectors, which are basically the canonical names of methods. Then, to make an object respond to a selector message, you send it performSelector:. For example:
NSString *exampleString = [NSString stringWithString:#"Hello"];
SEL methodName = #selector(stringByAppendingString:);
// ^This is the selector. Note that it just represents the name of a
// message, and doesn't specify any class or implementation
NSString *combinedString = [exampleString performSelector:methodName withObject:#" world!"];
What you'll want is to make an array of NSStrings containing the names of the selectors you're interested in. You can use the function NSStringFromSelector() to do this. Then, when you want to use them, call NSSelectorFromString() on the strings to get the original selector back and pass it to the appropriate object's performSelector:. (As shown in the example above, the receiver isn't encoded in a selector — just the method name — so you might need to store the receiver as well.)