How to add a category to a "hidden" class - objective-c

Is there a way to add a category to a class whose header file you can't access?
For testing purposes, I want to add a category to UITableViewCellDeleteConfirmationControl, but the class is (as far as I can tell) part of a private framework.
How can I do that?
Elaboration (per mihirios's request):
I am trying to extend the Frank testing framework to simulate tapping the confirmation button (the big red "Delete" button) that appears when you try to delete a UITableViewCell. Frank adds a tap method to UIControl. For some reason, Frank's usual way of tapping a control does not work for the UITableViewCellDeleteConfirmationControl class (which subclasses UIControl).
I've create a workaround. I added a category to UITableViewCell, with the following method.
- (BOOL)confirmDeletion {
if (![self showingDeleteConfirmation]) {
return NO;
}
UITableView *tableView = (UITableView *)[self superview];
id <UITableViewDataSource> dataSource = [tableView dataSource];
NSIndexPath *indexPath = [tableView indexPathForCell:self];
[dataSource tableView:tableView
commitEditingStyle:UITableViewCellEditingStyleDelete
forRowAtIndexPath:indexPath];
return YES;
}
This finds the table's data source and invokes its tableView:commitEditingStyle:forRowAtIndexPath: method, which (according to the documentation for UITableView) is what the system does when the user taps the confirmation button.
This works, but I would prefer to make UITableViewCellDeleteConfirmationControl appear to be a tappable button by adding a tap method to it, overriding Frank's default one. The tap method would find the cell that contains the confirmation button, then invoke [cell confirmDeletion].
When I try to declare a category for UITableViewCellDeleteConfirmationControl, the compiler complains that it "can't resolve interface 'UITableViewCellDeleteConfirmationControl'."
When I try to use the header file that someone generated using class-dump, the linker complains that it can't find the symbol _OBJC_CLASS_$_UITableViewCellDeleteConfirmationControl.

For testing purposes, you can always get the class object using NSClassFromString and then use the class_replaceMethod runtime method to do whatever you need. See the Objective-C Runtime Reference for details.

As far as i know you can not use a Category, but you could add the methods manually during runtime.
A Possible way to do this is, to create a new class, implement the methods you want to, and send this methods to UITableViewCellDeleteConfirmationControl using the appropriate objc-runtime functions. There are some things to take care of, like storing the original functions for later use in case of overloading, also in your 'category'-class you have to pay attention when you want to call super, as this will not work, you have to use objc-runtime function objc_msgSendSuper instead.
As Long as you don't need to call super this will do fine:
#import <objc/runtime.h>
#import <objc/message.h>
void implementInstanceMethods(Class src, Class dest) {
unsigned int count;
Method *methods = class_copyMethodList(src, &count);
for (int i = 0; i < count; ++i) {
IMP imp = method_getImplementation(methods[i]);
SEL selector = method_getName(methods[i]);
NSString *selectorName = NSStringFromSelector(selector);
const char *types = method_getTypeEncoding(methods[i]);
class_replaceMethod(dest, selector, imp, types);
}
free(methods);
}
a good point to call the method is in main.m, for example:
#autoreleasepool {
implementInstanceMethods([MyCategory class], NSClassFromString(#"UITableViewCellDeleteConfirmationControl"));
return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class]));
}
But i don't know why you not just move the confirmation handling in the controller-class.

As long as the compiler can (eventually) link to the class in question you can create a category for it. The more important question will be how to design the category since it seems you do not have access to the source for the original class.

Related

Cocoa user interface loading problems

I'm a noob on objective C so I'm sorry if I don't use the right terms to describe my problem but I need a hand and SO is my least resource!
I'm trying to interface an Arduino balance with my mac with an objective C software in which I've got a Nib file and a controller one: to do that I use ORSSerialPort which runs ok.
I'm experiencing some problems while loading a NSCombobox (the one with I make the user able to choose the serial port) after the user interface loading: in my controller class I have a method called "InitializeView" which calls this method:
-(void)RefreshSerialPortsInComboBox{
//Clear all existing elements
for (int i = 0; i < [self.serialPortsComboBox numberOfItems]; i++) {
[self.serialPortsComboBox removeItemAtIndex:i];
}
//Reload the serial ports list
NSArray *availableSerialPorts = [[NSArray alloc] initWithArray:[serialPortManager availablePorts]];
//Reload the Combobox elements with the new serial ports list
for (int i = 0; i < [availableSerialPorts count]; i++) {
[self.serialPortsComboBox addItemWithObjectValue:[[availableSerialPorts objectAtIndex:i] valueForKey:#"_path"]];
[self InsertTextInBufferTextView:#"Elemento creato...\n"];
}
}
The problem is that if I call this method in my controller object's init method, this doesn't work and the combobox items list is still empty: take note that the controller is instantiated by Interface Builder.
I tried to link the above method to a button and it works so it seems to be a matter of loading priority, it seems that I'm going to call the method before loading the ui objects or making them ready to be worked on...or maybe something else but I don't know what.
Can someone help me?
Another similar alternative just for completeness is to implement the method awakeFromNib, this method is called for all objects represented in a nib file. Its defined in the informal protocol NSNibAwaking
You should call this method in loadView method:
- (void)loadView {
[super loadView];
[self RefreshSerialPortsInComboBox];
}
Your code doesn't work because view and all IBOutlets haven't not initialized in "init" method yet.

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.

How to call my method in cocoa, self doesn't work

Working on a program that will record some things from the webcam when a user presses physical buttons connected to the mac via phidgets. Have call methods on other places in my app simply doing [self method: input], but on one place it doesn't work. What could be wrong?
This is the method i want to run if i get inputchange in my program.
Also i do -(void)reportButton2:(NSInteger)inputVal:(NSInteger)inputInd; in my .h file.
-(void)reportButton2:(NSInteger)inputVal:(NSInteger)inputInd {
//NSLog(#"phidget för port = %%d med signal %%d", ind, val);
if(inputVal == 1)
{
NSError* error;
NSFileManager* deleteMgr = [NSFileManager defaultManager];
NSString* path = #"/Users/Shared/tempFile.mov";
[deleteMgr removeItemAtPath:path error:&error];
[mCaptureMovieFileOutput recordToOutputFileURL:[NSURL fileURLWithPath:#"/Users/Shared/tempFile.mov"]];
}
else if(inputVal == 0)
{
[mCaptureMovieFileOutput recordToOutputFileURL:nil];
}
}
The code below give me result if imput from the buttons change. Here i just can't seem to call reportbutton2.
If i try to use [self reportButton2..] in gives me "Use of undeclared identifier 'self'"
int gotInputChange(CPhidgetInterfaceKitHandle phid, void *context, int ind, int val) {
what to do here?
return 0;
}
The problem is that gotInputChange is a C function not a Objective C method and so has no udea what self is as it does not belong to a class.
for [self reportButton2... = to work it needs to be a method in your class
I usually put something like this at the top of callbacks like gotInputChange:
MyObject *self = (id)context;
Then I can use self throughout the function as if it were a method.
The other thing being in a function makes harder is asserting conditions. The regular assertion macros, such as NSAssert and NSParameterAssert, require both of the implicit arguments to every method (self is one of them) to exist. In a C function, you must use NSCAssert, NSCParameterAssert, etc. instead.
You can also use your delegate.
SomeNameAppDelegate *delegate = (SomeNameAppDelegate *)[[NSApplication sharedApplication] delegate];
[delegate yourMethodName];
It works like in example if your target method in AppDelegate class. But when you have an access to delegate, you can create a pointers to necessary classes and use them over delegate.
Quickest but not soo nice way is to make your Class a singleton and access it from the gotInputChange function.
Okay thanks! Din't have any clue that it was C would never have solved it by myself. Did some googeling and this one did the trick for me.
[(id)context performSelectorOnMainThread:#selector(reportButton2:)withObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:ind], [NSNumber numberWithInt:val], nil] waitUntilDone:NO];

Objective-C, how can i hook up a method in another class

Objective-C keeps all its methods in a huge hashtable - so shouldn't it possible to patch this table and replace an existing method with my own patched method (which then calls the original)?
I need a way to hook up the NSWindow KeyUp method in a window which i can't subclass cause it's already created.
I need some code or at least some keywords i can use for further searching.
You should NOT swizzle methods for this. This is deprecated behavior. This will affect ALL windows in your app not just the one you wanted to change. However, what you should do instead is to subclass NSWindow already and then change the class of that window at runtime. This can be done using this runtime function:
Class object_setClass(id object, Class cls)
Reference is here: http://developer.apple.com/mac/library/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/doc/uid/TP40001418-CH1g-SW12
Your code should then look like this:
object_setClass(theWindow, [MyWindowSubclass class]);
On problem you might experience is that window already being a subclass of NSWindow. If that's the case there are more complicated ways to achieve this. You can construct a class dynamically at runtime. Here's some more code. Given that window is the target window:
Class newWindowClass = objc_allocateClassPair([window class], "MyHackyWindowSubclass", 0);
Method upMethod = class_getInstanceMethod(newWindowClass, #selector(keyUp:));
method_setImplementation(upMethod, new_NSWindow_keyUp_);
object_setClass(window, newWindowClass);
I'm not totally sure this does not change the implementation of the superclass. The documentation is a bit unspecific about it. However, you should still try it. If it does not work, replace the second and third line by this one:
class_replaceMethod(newWindowClass, #selector(keyUp:), new_NSWindow_keyUp_, "v#:#");
In any case you need to define the new Method implementation. It could look like that (partially by KennyTM):
void new_NSWindow_keyUp_(NSWindow* self, SEL _cmd, NSEvent* evt) {
[super keyUp: evt];
... // do your changes
}
Of course it is possible. In fact, you don't even need to look into the hash table — there's standard API for this.
For example:
typedef void (*NSWindow_keyUp__IMP)(NSWindow* self, SEL _cmd, NSEvent* evt);
static NSWindow_keyUp__IMP original_NSWindow_keyUp_;
void replaced_NSWindow_keyUp_(NSWindow* self, SEL _cmd, NSEvent* evt) {
NSLog(#"Entering keyUp:. self = %#, event = %#", self, evt);
original_NSWindow_keyUp_(self, _cmd, evt);
NSLog(#"Leaving keyUp:. self = %#, event = %#", self, evt);
}
...
Method m = class_getInstanceMethod([NSWindow class], #selector(keyUp:));
original_NSWindow_keyUp_ = method_setImplementation(m, replaced_NSWindow_keyUp_);

Objective C run-time parameter binding

I'd like (at runtime) to bind a parameter to a function as you can do in boost::bind - a little like the following:
-(void)myFuncWithParameter:(NSString*)param {
NSLog(param);
}
-(void)init {
UIButton *helloButton = [UIButton buttonWithType:UIButtonTypeCustom];
[helloButton addTarget:self action:#selector(myFuncWithParameter:#"hello") forControlEvents:UIControlEventTouchUpInside];
}
So... I'm dynamically binding (at runtime) the value #"hello" to a parameter.
Obviously the above isn't the correct Syntax. Does anyone know if this is possible and the correct syntax?
Cheers,
Nick.
The short answer is no, or at least not at that level.
The long answer is that it is technically possible to build something akin to using NSInvocations (and/or forwardInvocation:), doing something clever in methodForSelector: and or by dynamically registering method implementations, but it is very tricky, especially if you care at all about speed.
If I had some code where building curried methods like that was really worthwhile, what I would do is something like this (written in this comment, untested);
//FIXME: In a real implementation you would do some mangling, this code will get confused if you have _s in the curried selector, and thus could be exploitable
//This method makes a unique selector by mangling the arguments
- (SEL) selectorForSelector:(SEL)bindSel withString:(NSString *)bindString {
NSString *mangle = [NSString *stringWithFormat:#"LGBind_%#_%#"], NSStringFromSelector(bindSel), bindString];
SEL retval = NSSelectorFromString(mangle);
//Register the imp. You probably want to check if it is already reg
if (![self respondsToSelector:retval]) {
class_addMethod([self class], retval, LGBind_IMP, "v#:")l
}
}
//Generic dispatcher imp
void LGBind_IMP(id self, SEL _cmd) {
NSString *selectorName = NSStringFromSelector(_cmd);
NSArray *array [selectorName componentsSeparatedByString:#"_"];
//Skip index 0; it is #"LGBind"
NSString *originalSelectorString = [array objectAtIndex:1];
NSString *originalArgString = [array objectAtIndex:2];
//Get our the SEL and the IMP
SEL originalSEL = NSSelectorFromString(originalSelectorString);
IMP originalIMP = [self methodForSelector:originalSEL];
//call the original imp
originalIMP([self class], originalSEL, originalArgString);
}
Obviously depending on your exact needs you could do things somewhere differently, for instance you could lazily by the imps in forwardInvocation, or stash data about the managled selector in a dict in the instance instead of just managling it into the selector name.
The general answer is that the target-action mechanism only allows for a target, a sender and a message that takes the sender; therefore, if you need to access data, you must get it from the target or the sender.
One option would be to create a class that represents the binding of a parameter value, a method and an object. This class would have an action that invokes the method on the object, passing the value. Use an instance of this class as the target. Here's a simplistic example:
#interface UnaryBinder : NSObject {
id target;
SEL selector;
id parameter;
}
#property id target;
#property SEL selector;
#property (retain) id parameter;
-(id)initWithTarget:(id)anObject selector:(SEL)aSelector param:(id)aParameter;
-(void)action:(id)sender;
#end
#implementation UnaryBinder
...
-(void)action:(id)sender {
[target performSelector:selector withObject:parameter];
}
#end
If you want to support an arbitrary number of parameters, you'd need to use NSInvocation (as Louis mentions) rather than performSelector:withObject. Of course, controls don't retain their targets, so you need some way of keeping the UnaryBinder around. At that point, you might as well skip the special class and just store the data in the control, as you mention in your comment about using KVP. Alternatively, factor out the action into a controller class and use an instance of that as the target. UnaryBinder and its ilk doesn't really offer any advantages when it comes to target-action. For related topics, google "higher order messaging".